| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.content.browser; |
| |
| import android.annotation.SuppressLint; |
| import android.annotation.TargetApi; |
| import android.app.assist.AssistStructure.ViewNode; |
| import android.content.ClipData; |
| import android.content.ClipDescription; |
| import android.content.ClipboardManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.graphics.Bitmap; |
| import android.graphics.Rect; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.ResultReceiver; |
| import android.os.SystemClock; |
| import android.util.Pair; |
| import android.view.ActionMode; |
| import android.view.DragEvent; |
| import android.view.HapticFeedbackConstants; |
| 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.ViewStructure; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.accessibility.AccessibilityManager; |
| import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; |
| import android.view.accessibility.AccessibilityNodeProvider; |
| import android.view.animation.AnimationUtils; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputConnection; |
| import android.view.inputmethod.InputMethodManager; |
| |
| import org.chromium.base.CommandLine; |
| import org.chromium.base.ObserverList; |
| import org.chromium.base.ObserverList.RewindableIterator; |
| import org.chromium.base.TraceEvent; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.content.browser.accessibility.BrowserAccessibilityManager; |
| import org.chromium.content.browser.accessibility.captioning.CaptioningBridgeFactory; |
| import org.chromium.content.browser.accessibility.captioning.SystemCaptioningBridge; |
| import org.chromium.content.browser.accessibility.captioning.TextTrackSettings; |
| import org.chromium.content.browser.input.AnimationIntervalProvider; |
| import org.chromium.content.browser.input.ImeAdapter; |
| import org.chromium.content.browser.input.InputMethodManagerWrapper; |
| import org.chromium.content.browser.input.JoystickScrollProvider; |
| import org.chromium.content.browser.input.JoystickZoomProvider; |
| 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.common.ContentSwitches; |
| import org.chromium.content_public.browser.AccessibilitySnapshotCallback; |
| import org.chromium.content_public.browser.AccessibilitySnapshotNode; |
| import org.chromium.content_public.browser.ActionModeCallbackHelper; |
| import org.chromium.content_public.browser.GestureStateListener; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.browser.WebContentsObserver; |
| import org.chromium.device.gamepad.GamepadList; |
| import org.chromium.ui.base.DeviceFormFactor; |
| import org.chromium.ui.base.ViewAndroidDelegate; |
| import org.chromium.ui.base.WindowAndroid; |
| import org.chromium.ui.base.ime.TextInputType; |
| import org.chromium.ui.display.DisplayAndroid; |
| import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Provides a Java-side 'wrapper' around a WebContent (native) instance. |
| * Contains all the major functionality necessary to manage the lifecycle of a ContentView without |
| * being tied to the view system. |
| */ |
| @JNINamespace("content") |
| public class ContentViewCore implements AccessibilityStateChangeListener, DisplayAndroidObserver, |
| SystemCaptioningBridge.SystemCaptioningBridgeListener, |
| WindowAndroidProvider { |
| private static final String TAG = "cr_ContentViewCore"; |
| |
| // Used to avoid enabling zooming in / out if resulting zooming will |
| // produce little visible difference. |
| private static final float ZOOM_CONTROLS_EPSILON = 0.007f; |
| |
| 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 |
| // side would prevent garbage collection of the ContentViewCore (as that strong ref would |
| // create a new GC root). |
| // For that reason, we store only a weak reference to the interface object on the |
| // 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 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 |
| // navigated. This includes interface objects that have been removed |
| // via the removeJavaScriptInterface API and transient objects returned from methods |
| // on the interface object. Note we use HashSet rather than Set as the native side |
| // expects HashSet (no bindings for interfaces). |
| private final HashSet<Object> mRetainedJavaScriptObjects = new HashSet<Object>(); |
| |
| /** |
| * A {@link WebContentsObserver} that listens to frame navigation events. |
| */ |
| private static class ContentViewWebContentsObserver extends WebContentsObserver { |
| // Using a weak reference avoids cycles that might prevent GC of WebView's WebContents. |
| private final WeakReference<ContentViewCore> mWeakContentViewCore; |
| |
| ContentViewWebContentsObserver(ContentViewCore contentViewCore) { |
| super(contentViewCore.getWebContents()); |
| mWeakContentViewCore = new WeakReference<ContentViewCore>(contentViewCore); |
| } |
| |
| @Override |
| public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode, |
| String description, String failingUrl, boolean wasIgnoredByHandler) { |
| // 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, int statusCode) { |
| if (!isNavigationToDifferentPage) return; |
| resetPopupsAndInput(); |
| } |
| |
| @Override |
| public void renderProcessGone(boolean wasOomProtected) { |
| resetPopupsAndInput(); |
| ContentViewCore contentViewCore = mWeakContentViewCore.get(); |
| if (contentViewCore == null) return; |
| contentViewCore.mImeAdapter.resetAndHideKeyboard(); |
| } |
| |
| @Override |
| public void navigationEntryCommitted() { |
| determinedProcessVisibility(); |
| } |
| |
| private void resetPopupsAndInput() { |
| ContentViewCore contentViewCore = mWeakContentViewCore.get(); |
| if (contentViewCore == null) return; |
| contentViewCore.mIsMobileOptimizedHint = false; |
| contentViewCore.hidePopupsAndClearSelection(); |
| contentViewCore.resetScrollInProgress(); |
| } |
| |
| private void determinedProcessVisibility() { |
| ContentViewCore contentViewCore = mWeakContentViewCore.get(); |
| if (contentViewCore == null) return; |
| // 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. See crbug.com/421041 for more details. |
| ChildProcessLauncher.determinedVisibility(contentViewCore.getCurrentRenderProcessId()); |
| } |
| } |
| |
| /** |
| * Returns interval between consecutive animation frames. |
| */ |
| // TODO(crbug.com/635567): Fix this properly. |
| @SuppressLint("ParcelCreator") |
| private static class SystemAnimationIntervalProvider implements AnimationIntervalProvider { |
| @Override |
| public long getLastAnimationFrameInterval() { |
| return AnimationUtils.currentAnimationTimeMillis(); |
| } |
| } |
| |
| /** |
| * {@ResultReceiver} passed in InputMethodManager#showSoftInput}. We need this to scroll to the |
| * editable node at the right timing, which is after input method window shows up. |
| */ |
| // TODO(crbug.com/635567): Fix this properly. |
| @SuppressLint("ParcelCreator") |
| private static class ShowKeyboardResultReceiver extends ResultReceiver { |
| |
| // Unfortunately, the memory life cycle of ResultReceiver object, once passed in |
| // showSoftInput(), is in the control of Android's input method framework and IME app, |
| // so we use a weakref to avoid tying CVC's lifetime to that of ResultReceiver object. |
| private final WeakReference<ContentViewCore> mContentViewCore; |
| |
| public ShowKeyboardResultReceiver(ContentViewCore contentViewCore, Handler handler) { |
| super(handler); |
| mContentViewCore = new WeakReference<>(contentViewCore); |
| } |
| |
| @Override |
| public void onReceiveResult(int resultCode, Bundle resultData) { |
| ContentViewCore contentViewCore = mContentViewCore.get(); |
| if (contentViewCore == null) return; |
| contentViewCore.onShowKeyboardReceiveResult(resultCode); |
| } |
| } |
| |
| /** |
| * Interface that consumers of {@link ContentViewCore} must implement to allow the proper |
| * dispatching of view methods through the containing view. |
| * |
| * <p> |
| * All methods with the "super_" prefix should be routed to the parent of the |
| * implementing container view. |
| */ |
| @SuppressWarnings("javadoc") |
| public interface InternalAccessDelegate { |
| /** |
| * @see View#onKeyUp(keyCode, KeyEvent) |
| */ |
| boolean super_onKeyUp(int keyCode, KeyEvent event); |
| |
| /** |
| * @see View#dispatchKeyEvent(KeyEvent) |
| */ |
| boolean super_dispatchKeyEvent(KeyEvent event); |
| |
| /** |
| * @see View#onGenericMotionEvent(MotionEvent) |
| */ |
| boolean super_onGenericMotionEvent(MotionEvent event); |
| |
| /** |
| * @see View#onConfigurationChanged(Configuration) |
| */ |
| void super_onConfigurationChanged(Configuration newConfig); |
| |
| /** |
| * @see View#onScrollChanged(int, int, int, int) |
| */ |
| void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix); |
| |
| /** |
| * @see View#awakenScrollBars() |
| */ |
| boolean awakenScrollBars(); |
| |
| /** |
| * @see View#awakenScrollBars(int, boolean) |
| */ |
| boolean super_awakenScrollBars(int startDelay, boolean invalidate); |
| } |
| |
| /** |
| * An interface for controlling visibility and state of embedder-provided zoom controls. |
| */ |
| public interface ZoomControlsDelegate { |
| /** |
| * Called when it's reasonable to show zoom controls. |
| */ |
| void invokeZoomPicker(); |
| |
| /** |
| * Called when zoom controls need to be hidden (e.g. when the view hides). |
| */ |
| void dismissZoomPicker(); |
| |
| /** |
| * Called when page scale has been changed, so the controls can update their state. |
| */ |
| void updateZoomControls(); |
| } |
| |
| /** |
| * An interface that allows the embedder to be notified when the results of |
| * extractSmartClipData are available. |
| */ |
| public interface SmartClipDataListener { |
| public void onSmartClipDataExtracted(String text, String html, Rect clipRect); |
| } |
| |
| private final Context mContext; |
| private final String mProductVersion; |
| private ViewGroup mContainerView; |
| private InternalAccessDelegate mContainerViewInternals; |
| private WebContents mWebContents; |
| private WebContentsObserver mWebContentsObserver; |
| |
| private ContentViewClient mContentViewClient; |
| |
| // Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit(). |
| private long mNativeContentViewCore = 0; |
| |
| private boolean mAttachedToWindow; |
| private final ObserverList<GestureStateListener> mGestureStateListeners; |
| private final RewindableIterator<GestureStateListener> mGestureStateListenersIterator; |
| private ZoomControlsDelegate mZoomControlsDelegate; |
| |
| private PopupZoomer mPopupZoomer; |
| private SelectPopup mSelectPopup; |
| private long mNativeSelectPopupSourceFrame = 0; |
| |
| private OverscrollRefreshHandler mOverscrollRefreshHandler; |
| |
| private Runnable mFakeMouseMoveRunnable = null; |
| |
| // Only valid when focused on a text / password field. |
| private ImeAdapter mImeAdapter; |
| |
| // 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 mTopControlsHeightPix; |
| private int mBottomControlsHeightPix; |
| private boolean mTopControlsShrinkBlinkSize; |
| |
| // Cached copy of all positions and scales as reported by the renderer. |
| private final RenderCoordinates mRenderCoordinates; |
| |
| // Provides smooth gamepad joystick-driven scrolling. |
| private final JoystickScrollProvider mJoystickScrollProvider; |
| |
| // Provides smooth gamepad joystick-driven zooming. |
| private JoystickZoomProvider mJoystickZoomProvider; |
| |
| private boolean mIsMobileOptimizedHint; |
| |
| private SelectionPopupController mSelectionPopupController; |
| private boolean mPreserveSelectionOnNextLossOfFocus; |
| |
| // Whether native accessibility, i.e. without any script injection, is allowed. |
| private boolean mNativeAccessibilityAllowed; |
| |
| // Whether native accessibility, i.e. without any script injection, has been enabled. |
| private boolean mNativeAccessibilityEnabled; |
| |
| // Handles native accessibility, i.e. without any script injection. |
| private BrowserAccessibilityManager mBrowserAccessibilityManager; |
| |
| // System accessibility service. |
| private final AccessibilityManager mAccessibilityManager; |
| |
| // If true, the web contents are obscured by another view and we shouldn't |
| // return an AccessibilityNodeProvider or process touch exploration events. |
| private boolean mIsObscuredByAnotherView; |
| |
| // Notifies the ContentViewCore when platform closed caption settings have changed |
| // if they are supported. Otherwise does nothing. |
| private final SystemCaptioningBridge mSystemCaptioningBridge; |
| |
| // 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; |
| |
| // Temporary notification to tell onSizeChanged to focus a form element, |
| // because the OSK was just brought up. |
| private final Rect mFocusPreOSKViewportRect = new Rect(); |
| |
| // Store the x, y coordinates of the last touch or mouse event. |
| private float mLastFocalEventX; |
| private float mLastFocalEventY; |
| |
| // 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; |
| |
| // 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 SmartClipDataListener mSmartClipDataListener = null; |
| |
| /** |
| * PID used to indicate an invalid render process. |
| */ |
| // 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 ViewAndroidDelegate mViewAndroidDelegate; |
| |
| // A flag to determine if we enable hover feature or not. |
| private Boolean mEnableTouchHover; |
| |
| // NOTE: This object will not be released by Android framework until the matching |
| // ResultReceiver in the InputMethodService (IME app) gets gc'ed. |
| private ShowKeyboardResultReceiver mShowKeyboardResultReceiver; |
| |
| // The list of observers that are notified when ContentViewCore changes its WindowAndroid. |
| private final ObserverList<WindowAndroidChangedObserver> mWindowAndroidChangedObservers; |
| |
| /** |
| * @param webContents The {@link WebContents} to find a {@link ContentViewCore} of. |
| * @return A {@link ContentViewCore} that is connected to {@code webContents} or |
| * {@code null} if none exists. |
| */ |
| public static ContentViewCore fromWebContents(WebContents webContents) { |
| return nativeFromWebContentsAndroid(webContents); |
| } |
| |
| /** |
| * Constructs a new ContentViewCore. Embedders must call initialize() after constructing |
| * a ContentViewCore and before using it. |
| * |
| * @param context The context used to create this. |
| */ |
| public ContentViewCore(Context context, String productVersion) { |
| mContext = context; |
| mProductVersion = productVersion; |
| mRenderCoordinates = new RenderCoordinates(); |
| mJoystickScrollProvider = new JoystickScrollProvider(this); |
| mAccessibilityManager = (AccessibilityManager) |
| getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); |
| mSystemCaptioningBridge = CaptioningBridgeFactory.getSystemCaptioningBridge(mContext); |
| mGestureStateListeners = new ObserverList<GestureStateListener>(); |
| mGestureStateListenersIterator = mGestureStateListeners.rewindableIterator(); |
| |
| mWindowAndroidChangedObservers = new ObserverList<WindowAndroidChangedObserver>(); |
| } |
| |
| /** |
| * @return The context used for creating this ContentViewCore. |
| */ |
| @CalledByNative |
| public Context getContext() { |
| return mContext; |
| } |
| |
| /** |
| * @return The ViewGroup that all view actions of this ContentViewCore should interact with. |
| */ |
| public ViewGroup getContainerView() { |
| return mContainerView; |
| } |
| |
| /** |
| * @return The WebContents currently being rendered. |
| */ |
| public WebContents getWebContents() { |
| return mWebContents; |
| } |
| |
| /** |
| * @return The WindowAndroid associated with this ContentViewCore. |
| */ |
| @Override |
| public WindowAndroid getWindowAndroid() { |
| if (mNativeContentViewCore == 0) return null; |
| return nativeGetJavaWindowAndroid(mNativeContentViewCore); |
| } |
| |
| /** |
| * @return The SelectionPopupController that handles select action mode on web contents. |
| */ |
| @VisibleForTesting |
| public SelectionPopupController getSelectionPopupControllerForTesting() { |
| return mSelectionPopupController; |
| } |
| |
| @VisibleForTesting |
| public void setSelectionPopupControllerForTesting(SelectionPopupController actionMode) { |
| mSelectionPopupController = actionMode; |
| } |
| |
| @Override |
| public void addWindowAndroidChangedObserver(WindowAndroidChangedObserver observer) { |
| mWindowAndroidChangedObservers.addObserver(observer); |
| } |
| |
| @Override |
| public void removeWindowAndroidChangedObserver(WindowAndroidChangedObserver observer) { |
| mWindowAndroidChangedObservers.removeObserver(observer); |
| } |
| |
| /** |
| * |
| * @param browserControlsHeightPix The height of the browser controls in pixels. |
| * @param browserControlsShrinkBlinkSize The Y amount in pixels to shrink the viewport by. This |
| * specifies how much smaller the Blink layout size should be |
| * relative to the size of this View. |
| */ |
| public void setTopControlsHeight(int topControlsHeightPix, boolean topControlsShrinkBlinkSize) { |
| if (topControlsHeightPix == mTopControlsHeightPix |
| && topControlsShrinkBlinkSize == mTopControlsShrinkBlinkSize) { |
| return; |
| } |
| |
| mTopControlsHeightPix = topControlsHeightPix; |
| mTopControlsShrinkBlinkSize = topControlsShrinkBlinkSize; |
| if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore); |
| } |
| |
| /** |
| * Sets the height of the bottom controls. If necessary, triggers a renderer resize. |
| */ |
| public void setBottomControlsHeight(int bottomControlHeightPix) { |
| if (mBottomControlsHeightPix == bottomControlHeightPix) return; |
| mBottomControlsHeightPix = bottomControlHeightPix; |
| if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore); |
| } |
| |
| @VisibleForTesting |
| public void setImeAdapterForTest(ImeAdapter imeAdapter) { |
| mImeAdapter = imeAdapter; |
| } |
| |
| @VisibleForTesting |
| public ImeAdapter getImeAdapterForTest() { |
| return mImeAdapter; |
| } |
| |
| private ImeAdapter createImeAdapter() { |
| return new ImeAdapter( |
| new InputMethodManagerWrapper(mContext), new ImeAdapter.ImeAdapterDelegate() { |
| @Override |
| public void onImeEvent() { |
| mPopupZoomer.hide(true); |
| getContentViewClient().onImeEvent(); |
| if (isFocusedNodeEditable()) mWebContents.dismissTextHandles(); |
| } |
| |
| @Override |
| public void onKeyboardBoundsUnchanged() { |
| assert mWebContents != null; |
| mWebContents.scrollFocusedEditableNodeIntoView(); |
| } |
| |
| @Override |
| public boolean performContextMenuAction(int id) { |
| assert mWebContents != null; |
| switch (id) { |
| case android.R.id.selectAll: |
| mWebContents.selectAll(); |
| return true; |
| case android.R.id.cut: |
| mWebContents.cut(); |
| return true; |
| case android.R.id.copy: |
| mWebContents.copy(); |
| return true; |
| case android.R.id.paste: |
| mWebContents.paste(); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| @Override |
| public View getAttachedView() { |
| return mContainerView; |
| } |
| |
| @Override |
| public ResultReceiver getNewShowKeyboardReceiver() { |
| return ContentViewCore.this.getNewShowKeyboardReceiver(); |
| } |
| }); |
| } |
| |
| /** |
| * |
| * @param viewDelegate Delegate to add/remove anchor views. |
| * @param internalDispatcher Handles dispatching all hidden or super methods to the |
| * containerView. |
| * @param webContents A WebContents instance to connect to. |
| * @param windowAndroid An instance of the WindowAndroid. |
| */ |
| // Perform important post-construction set up of the ContentViewCore. |
| // We do not require the containing view in the constructor to allow embedders to create a |
| // ContentViewCore without having fully created its containing view. The containing view |
| // is a vital component of the ContentViewCore, so embedders must exercise caution in what |
| // they do with the ContentViewCore before calling initialize(). |
| // We supply the nativeWebContents pointer here rather than in the constructor to allow us |
| // to set the private browsing mode at a later point for the WebView implementation. |
| // Note that the caller remains the owner of the nativeWebContents and is responsible for |
| // deleting it after destroying the ContentViewCore. |
| public void initialize(ViewAndroidDelegate viewDelegate, |
| InternalAccessDelegate internalDispatcher, WebContents webContents, |
| WindowAndroid windowAndroid) { |
| mViewAndroidDelegate = viewDelegate; |
| setContainerView(viewDelegate.getContainerView()); |
| long windowNativePointer = windowAndroid.getNativePointer(); |
| assert windowNativePointer != 0; |
| |
| mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE; |
| |
| final float dipScale = windowAndroid.getDisplay().getDipScale(); |
| |
| mRenderCoordinates.reset(); |
| mRenderCoordinates.setDeviceScaleFactor(dipScale, windowAndroid.getContext()); |
| |
| mNativeContentViewCore = nativeInit(webContents, mViewAndroidDelegate, windowNativePointer, |
| dipScale, mRetainedJavaScriptObjects); |
| mWebContents = nativeGetWebContentsAndroid(mNativeContentViewCore); |
| |
| setContainerViewInternals(internalDispatcher); |
| |
| initPopupZoomer(mContext); |
| mImeAdapter = createImeAdapter(); |
| attachImeAdapter(); |
| |
| mSelectionPopupController = new SelectionPopupController(mContext, windowAndroid, |
| webContents, viewDelegate.getContainerView(), mRenderCoordinates, mImeAdapter); |
| mSelectionPopupController.setCallback(ActionModeCallbackHelper.EMPTY_CALLBACK); |
| mSelectionPopupController.setContainerView(getContainerView()); |
| |
| mWebContentsObserver = new ContentViewWebContentsObserver(this); |
| } |
| |
| /** |
| * Updates the native {@link ContentViewCore} with a new window. This moves the NativeView and |
| * attached it to the new NativeWindow linked with the given {@link WindowAndroid}. |
| * @param windowAndroid The new {@link WindowAndroid} for this {@link ContentViewCore}. |
| */ |
| public void updateWindowAndroid(WindowAndroid windowAndroid) { |
| removeDisplayAndroidObserver(); |
| long windowNativePointer = windowAndroid == null ? 0 : windowAndroid.getNativePointer(); |
| nativeUpdateWindowAndroid(mNativeContentViewCore, windowNativePointer); |
| |
| // TODO(yusufo): Rename this call to be general for tab reparenting. |
| // Clean up cached popups that may have been created with an old activity. |
| mSelectPopup = null; |
| destroyPastePopup(); |
| |
| addDisplayAndroidObserverIfNeeded(); |
| |
| for (WindowAndroidChangedObserver observer : mWindowAndroidChangedObservers) { |
| observer.onWindowAndroidChanged(windowAndroid); |
| } |
| } |
| |
| /** |
| * Set {@link ActionMode.Callback} used by {@link SelectionPopupController}. |
| * @param callback ActionMode.Callback instance. |
| */ |
| public void setActionModeCallback(ActionMode.Callback callback) { |
| mSelectionPopupController.setCallback(callback); |
| } |
| |
| private void addDisplayAndroidObserverIfNeeded() { |
| if (!mAttachedToWindow) return; |
| WindowAndroid windowAndroid = getWindowAndroid(); |
| if (windowAndroid != null) { |
| DisplayAndroid display = windowAndroid.getDisplay(); |
| display.addObserver(this); |
| onRotationChanged(display.getRotation()); |
| onDIPScaleChanged(display.getDipScale()); |
| } |
| } |
| |
| private void removeDisplayAndroidObserver() { |
| WindowAndroid windowAndroid = getWindowAndroid(); |
| if (windowAndroid != null) { |
| windowAndroid.getDisplay().removeObserver(this); |
| } |
| } |
| |
| /** |
| * 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) { |
| try { |
| TraceEvent.begin("ContentViewCore.setContainerView"); |
| if (mContainerView != null) { |
| assert mOverscrollRefreshHandler == null; |
| hideSelectPopupWithCancelMessage(); |
| mPopupZoomer.hide(false); |
| } |
| |
| mContainerView = containerView; |
| mContainerView.setClickable(true); |
| if (mSelectionPopupController != null) { |
| mSelectionPopupController.setContainerView(containerView); |
| } |
| } finally { |
| TraceEvent.end("ContentViewCore.setContainerView"); |
| } |
| } |
| |
| @CalledByNative |
| private void onNativeContentViewCoreDestroyed(long nativeContentViewCore) { |
| assert nativeContentViewCore == mNativeContentViewCore; |
| mNativeContentViewCore = 0; |
| } |
| |
| /** |
| * Set the Container view Internals. |
| * @param internalDispatcher Handles dispatching all hidden or super methods to the |
| * containerView. |
| */ |
| public void setContainerViewInternals(InternalAccessDelegate internalDispatcher) { |
| mContainerViewInternals = internalDispatcher; |
| } |
| |
| @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) { |
| mContainerViewAtCreation.post(new Runnable() { |
| @Override |
| public void run() { |
| if (mContainerViewAtCreation.indexOfChild(zoomer) == -1) { |
| mContainerViewAtCreation.addView(zoomer); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void onPopupZoomerHidden(final PopupZoomer zoomer) { |
| mContainerViewAtCreation.post(new Runnable() { |
| @Override |
| public void run() { |
| 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) { |
| mContainerViewAtCreation.requestFocus(); |
| if (mNativeContentViewCore != 0) { |
| 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()); |
| } |
| 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 |
| * other methods may be called on this ContentView after this method has |
| * been called. |
| * Warning: destroy() is not guranteed to be called in Android WebView. |
| * Any object that relies solely on destroy() being called to be cleaned up |
| * will leak in Android WebView. If appropriate, consider clean up in |
| * onDetachedFromWindow() which is guaranteed to be called in Android WebView. |
| */ |
| public void destroy() { |
| removeDisplayAndroidObserver(); |
| if (mNativeContentViewCore != 0) { |
| nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore); |
| } |
| mWebContentsObserver.destroy(); |
| mWebContentsObserver = null; |
| setSmartClipDataListener(null); |
| setZoomControlsDelegate(null); |
| mImeAdapter.resetAndHideKeyboard(); |
| // TODO(igsolla): address TODO in ContentViewClient because ContentViewClient is not |
| // currently a real Null Object. |
| // |
| // Instead of deleting the client we use the Null Object pattern to avoid null checks |
| // in this class. |
| mContentViewClient = new ContentViewClient(); |
| mWebContents = null; |
| mOverscrollRefreshHandler = null; |
| mNativeContentViewCore = 0; |
| mJavaScriptInterfaces.clear(); |
| mRetainedJavaScriptObjects.clear(); |
| for (mGestureStateListenersIterator.rewind(); mGestureStateListenersIterator.hasNext();) { |
| mGestureStateListenersIterator.next().onDestroyed(); |
| } |
| mGestureStateListeners.clear(); |
| hidePopupsAndPreserveSelection(); |
| destroyPastePopup(); |
| |
| // See warning in javadoc before adding more clean up code here. |
| } |
| |
| /** |
| * Returns true initially, false after destroy() has been called. |
| * It is illegal to call any other public method after destroy(). |
| */ |
| public boolean isAlive() { |
| return mNativeContentViewCore != 0; |
| } |
| |
| public void setContentViewClient(ContentViewClient client) { |
| if (client == null) { |
| throw new IllegalArgumentException("The client can't be null."); |
| } |
| mContentViewClient = client; |
| } |
| |
| @VisibleForTesting |
| public ContentViewClient getContentViewClient() { |
| if (mContentViewClient == null) { |
| // We use the Null Object pattern to avoid having to perform a null check in this class. |
| // We create it lazily because most of the time a client will be set almost immediately |
| // after ContentView is created. |
| mContentViewClient = new ContentViewClient(); |
| // We don't set the native ContentViewClient pointer here on purpose. The native |
| // implementation doesn't mind a null delegate and using one is better than passing a |
| // Null Object, since we cut down on the number of JNI calls. |
| } |
| return mContentViewClient; |
| } |
| |
| @CalledByNative |
| private void onBackgroundColorChanged(int color) { |
| getContentViewClient().onBackgroundColorChanged(color); |
| } |
| |
| /** |
| * @return Viewport width in physical pixels as set from onSizeChanged. |
| */ |
| @CalledByNative |
| public int getViewportWidthPix() { |
| return mViewportWidthPix; |
| } |
| |
| /** |
| * @return Viewport height in physical pixels as set from onSizeChanged. |
| */ |
| @CalledByNative |
| public int getViewportHeightPix() { |
| return mViewportHeightPix; |
| } |
| |
| /** |
| * @return Viewport height when the OSK is hidden in physical pixels as set from onSizeChanged. |
| */ |
| @CalledByNative |
| public int getViewportHeightWithOSKHiddenPix() { |
| return mViewportHeightPix + getContentViewClient().getSystemWindowInsetBottom(); |
| } |
| |
| /** |
| * @return Width of underlying physical surface. |
| */ |
| @CalledByNative |
| private int getPhysicalBackingWidthPix() { |
| return mPhysicalBackingWidthPix; |
| } |
| |
| /** |
| * @return Height of underlying physical surface. |
| */ |
| @CalledByNative |
| private int getPhysicalBackingHeightPix() { |
| return mPhysicalBackingHeightPix; |
| } |
| |
| /** |
| * @return The amount that the viewport size given to Blink is shrunk by the URL-bar.. |
| */ |
| @CalledByNative |
| public boolean doBrowserControlsShrinkBlinkSize() { |
| return mTopControlsShrinkBlinkSize; |
| } |
| |
| @CalledByNative |
| public int getTopControlsHeightPix() { |
| return mTopControlsHeightPix; |
| } |
| |
| @CalledByNative |
| public int getBottomControlsHeightPix() { |
| return mBottomControlsHeightPix; |
| } |
| |
| /** |
| * @return Current device scale factor (maps DIP pixels to physical pixels). |
| */ |
| @VisibleForTesting |
| public float getDeviceScaleFactor() { |
| return mRenderCoordinates.getDeviceScaleFactor(); |
| } |
| |
| /** |
| * @return Current page scale factor (maps CSS pixels to DIP pixels). |
| */ |
| @VisibleForTesting |
| public float getPageScaleFactor() { |
| return mRenderCoordinates.getPageScaleFactor(); |
| } |
| |
| /** |
| * @see android.webkit.WebView#getContentHeight() |
| */ |
| public float getContentHeightCss() { |
| return mRenderCoordinates.getContentHeightCss(); |
| } |
| |
| /** |
| * @see android.webkit.WebView#getContentWidth() |
| */ |
| public float getContentWidthCss() { |
| return mRenderCoordinates.getContentWidthCss(); |
| } |
| |
| /** |
| * @return The selected text (empty if no text selected). |
| */ |
| @VisibleForTesting |
| public String getSelectedText() { |
| return mSelectionPopupController.getSelectedText(); |
| } |
| |
| /** |
| * @return Whether the current focused node is editable. |
| */ |
| public boolean isFocusedNodeEditable() { |
| return mSelectionPopupController.isSelectionEditable(); |
| } |
| |
| /** |
| * @return Whether the HTML5 gamepad API is active. |
| */ |
| public boolean isGamepadAPIActive() { |
| return GamepadList.isGamepadAPIActive(); |
| } |
| |
| /** |
| * @see View#onTouchEvent(MotionEvent) |
| */ |
| public boolean onTouchEvent(MotionEvent event) { |
| final boolean isTouchHandleEvent = false; |
| return onTouchEventImpl(event, isTouchHandleEvent); |
| } |
| |
| /** |
| * Called by PopupWindow-based touch handles. |
| * @param event the MotionEvent targeting the handle. |
| */ |
| public boolean onTouchHandleEvent(MotionEvent event) { |
| final boolean isTouchHandleEvent = true; |
| return onTouchEventImpl(event, isTouchHandleEvent); |
| } |
| |
| private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) { |
| TraceEvent.begin("onTouchEvent"); |
| try { |
| int eventAction = event.getActionMasked(); |
| |
| if (eventAction == MotionEvent.ACTION_DOWN) { |
| cancelRequestToScrollFocusedEditableNodeIntoView(); |
| } |
| |
| if (SPenSupport.isSPenSupported(mContext)) { |
| eventAction = SPenSupport.convertSPenEventAction(eventAction); |
| } |
| if (!isValidTouchEventActionForNative(eventAction)) return false; |
| |
| if (mNativeContentViewCore == 0) return false; |
| |
| // 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; |
| } |
| |
| final int pointerCount = event.getPointerCount(); |
| |
| float[] touchMajor = {event.getTouchMajor(), |
| pointerCount > 1 ? event.getTouchMajor(1) : 0}; |
| float[] touchMinor = {event.getTouchMinor(), |
| pointerCount > 1 ? event.getTouchMinor(1) : 0}; |
| |
| for (int i = 0; i < 2; i++) { |
| if (touchMajor[i] < touchMinor[i]) { |
| float tmp = touchMajor[i]; |
| touchMajor[i] = touchMinor[i]; |
| touchMinor[i] = tmp; |
| } |
| } |
| |
| 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, |
| touchMajor[0], touchMajor[1], |
| touchMinor[0], touchMinor[1], |
| event.getOrientation(), pointerCount > 1 ? event.getOrientation(1) : 0, |
| event.getAxisValue(MotionEvent.AXIS_TILT), |
| pointerCount > 1 ? event.getAxisValue(MotionEvent.AXIS_TILT, 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"); |
| } |
| } |
| |
| @CalledByNative |
| private void requestDisallowInterceptTouchEvent() { |
| mContainerView.requestDisallowInterceptTouchEvent(true); |
| } |
| |
| 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; |
| } |
| |
| /** |
| * @return Whether a scroll targeting web content is in progress. |
| */ |
| public boolean isScrollInProgress() { |
| return mTouchScrollInProgress || mPotentiallyActiveFlingCount > 0; |
| } |
| |
| private void setTouchScrollInProgress(boolean inProgress) { |
| if (mTouchScrollInProgress == inProgress) return; |
| mTouchScrollInProgress = inProgress; |
| mSelectionPopupController.updateActionModeVisibility(inProgress); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onFlingStartEventConsumed() { |
| mPotentiallyActiveFlingCount++; |
| setTouchScrollInProgress(false); |
| for (mGestureStateListenersIterator.rewind(); |
| mGestureStateListenersIterator.hasNext();) { |
| mGestureStateListenersIterator.next().onFlingStartGesture( |
| computeVerticalScrollOffset(), computeVerticalScrollExtent()); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onFlingCancelEventAck() { |
| updateGestureStateListener(GestureEventType.FLING_CANCEL); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onScrollBeginEventAck() { |
| setTouchScrollInProgress(true); |
| hidePastePopup(); |
| mZoomControlsDelegate.invokeZoomPicker(); |
| updateGestureStateListener(GestureEventType.SCROLL_START); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onScrollUpdateGestureConsumed() { |
| mZoomControlsDelegate.invokeZoomPicker(); |
| for (mGestureStateListenersIterator.rewind(); |
| mGestureStateListenersIterator.hasNext();) { |
| mGestureStateListenersIterator.next().onScrollUpdateGestureConsumed(); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onScrollEndEventAck() { |
| setTouchScrollInProgress(false); |
| updateGestureStateListener(GestureEventType.SCROLL_END); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onPinchBeginEventAck() { |
| updateGestureStateListener(GestureEventType.PINCH_BEGIN); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onPinchEndEventAck() { |
| updateGestureStateListener(GestureEventType.PINCH_END); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onSingleTapEventAck(boolean consumed) { |
| for (mGestureStateListenersIterator.rewind(); |
| mGestureStateListenersIterator.hasNext();) { |
| mGestureStateListenersIterator.next().onSingleTap(consumed); |
| } |
| hidePastePopup(); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onShowUnhandledTapUIIfNeeded(int x, int y) { |
| mSelectionPopupController.onShowUnhandledTapUIIfNeeded(x, y); |
| } |
| |
| /** |
| * Called just prior to a tap or press gesture being forwarded to the renderer. |
| */ |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private boolean filterTapOrPressEvent(int type, int x, int y) { |
| if (type == GestureEventType.LONG_PRESS && offerLongPressToEmbedder()) { |
| return true; |
| } |
| updateForTapOrPress(type, x, y); |
| return false; |
| } |
| |
| @VisibleForTesting |
| public void sendDoubleTapForTest(long timeMs, int x, int y) { |
| if (mNativeContentViewCore == 0) return; |
| nativeDoubleTap(mNativeContentViewCore, timeMs, x, y); |
| } |
| |
| /** |
| * Flings the viewport with velocity vector (velocityX, velocityY). |
| * @param timeMs the current time. |
| * @param velocityX fling speed in x-axis. |
| * @param velocityY fling speed in y-axis. |
| */ |
| public void flingViewport(long timeMs, int velocityX, int velocityY) { |
| if (mNativeContentViewCore == 0) return; |
| nativeFlingCancel(mNativeContentViewCore, timeMs); |
| nativeScrollBegin(mNativeContentViewCore, timeMs, 0, 0, velocityX, velocityY, true); |
| nativeFlingStart(mNativeContentViewCore, timeMs, 0, 0, velocityX, velocityY, true); |
| } |
| |
| /** |
| * 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. |
| */ |
| public void addGestureStateListener(GestureStateListener listener) { |
| mGestureStateListeners.addObserver(listener); |
| } |
| |
| /** |
| * Removes a listener that was added to watch for gesture state changes. |
| * @param listener Listener to remove. |
| */ |
| public void removeGestureStateListener(GestureStateListener listener) { |
| mGestureStateListeners.removeObserver(listener); |
| } |
| |
| void updateGestureStateListener(int gestureType) { |
| for (mGestureStateListenersIterator.rewind(); |
| mGestureStateListenersIterator.hasNext();) { |
| GestureStateListener listener = mGestureStateListenersIterator.next(); |
| switch (gestureType) { |
| case GestureEventType.PINCH_BEGIN: |
| listener.onPinchStarted(); |
| break; |
| case GestureEventType.PINCH_END: |
| listener.onPinchEnded(); |
| break; |
| case GestureEventType.FLING_END: |
| listener.onFlingEndGesture( |
| computeVerticalScrollOffset(), |
| computeVerticalScrollExtent()); |
| break; |
| case GestureEventType.SCROLL_START: |
| listener.onScrollStarted( |
| computeVerticalScrollOffset(), |
| computeVerticalScrollExtent()); |
| break; |
| case GestureEventType.SCROLL_END: |
| listener.onScrollEnded( |
| computeVerticalScrollOffset(), |
| computeVerticalScrollExtent()); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| /** |
| * To be called when the ContentView is shown. |
| */ |
| public void onShow() { |
| assert mWebContents != null; |
| mWebContents.onShow(); |
| setAccessibilityState(mAccessibilityManager.isEnabled()); |
| restoreSelectionPopupsIfNecessary(); |
| } |
| |
| /** |
| * @return The ID of the renderer process that backs this tab or |
| * {@link #INVALID_RENDER_PROCESS_PID} if there is none. |
| */ |
| public int getCurrentRenderProcessId() { |
| return nativeGetCurrentRenderProcessId(mNativeContentViewCore); |
| } |
| |
| /** |
| * To be called when the ContentView is hidden. |
| */ |
| public void onHide() { |
| assert mWebContents != null; |
| hidePopupsAndPreserveSelection(); |
| mWebContents.onHide(); |
| } |
| |
| private void hidePopupsAndClearSelection() { |
| mSelectionPopupController.destroyActionModeAndUnselect(); |
| hidePastePopup(); |
| hideSelectPopupWithCancelMessage(); |
| mPopupZoomer.hide(false); |
| } |
| |
| @CalledByNative |
| private void hidePopupsAndPreserveSelection() { |
| mSelectionPopupController.destroyActionModeAndKeepSelection(); |
| hidePastePopup(); |
| hideSelectPopupWithCancelMessage(); |
| mPopupZoomer.hide(false); |
| } |
| |
| private void restoreSelectionPopupsIfNecessary() { |
| mSelectionPopupController.restoreSelectionPopupsIfNecessary(); |
| } |
| |
| /** |
| * Hide action mode and put into destroyed state. |
| */ |
| public void destroySelectActionMode() { |
| mSelectionPopupController.finishActionMode(); |
| } |
| |
| public boolean isSelectActionBarShowing() { |
| return mSelectionPopupController.isActionModeValid(); |
| } |
| |
| private void resetGestureDetection() { |
| if (mNativeContentViewCore == 0) return; |
| nativeResetGestureDetection(mNativeContentViewCore); |
| } |
| |
| /** |
| * @see View#onAttachedToWindow() |
| */ |
| @SuppressWarnings("javadoc") |
| public void onAttachedToWindow() { |
| mAttachedToWindow = true; |
| addDisplayAndroidObserverIfNeeded(); |
| setAccessibilityState(mAccessibilityManager.isEnabled()); |
| updateTextSelectionUI(true); |
| GamepadList.onAttachedToWindow(mContext); |
| mAccessibilityManager.addAccessibilityStateChangeListener(this); |
| mSystemCaptioningBridge.addListener(this); |
| mImeAdapter.onViewAttachedToWindow(); |
| } |
| |
| /** |
| * Update the text selection UI depending on the focus of the page. This will hide the selection |
| * handles and selection popups if focus is lost. |
| * TODO(mdjones): This was added as a temporary measure to hide text UI while Reader Mode or |
| * Contextual Search are showing. This should be removed in favor of proper focusing of the |
| * panel's ContentViewCore (which is currently not being added to the view hierarchy). |
| * @param focused If the ContentViewCore currently has focus. |
| */ |
| public void updateTextSelectionUI(boolean focused) { |
| setTextHandlesTemporarilyHidden(!focused); |
| if (focused) { |
| restoreSelectionPopupsIfNecessary(); |
| } else { |
| hidePopupsAndPreserveSelection(); |
| } |
| } |
| |
| /** |
| * @see View#onDetachedFromWindow() |
| */ |
| @SuppressWarnings("javadoc") |
| @SuppressLint("MissingSuperCall") |
| public void onDetachedFromWindow() { |
| mAttachedToWindow = false; |
| mImeAdapter.onViewDetachedFromWindow(); |
| mZoomControlsDelegate.dismissZoomPicker(); |
| removeDisplayAndroidObserver(); |
| GamepadList.onDetachedFromWindow(); |
| mAccessibilityManager.removeAccessibilityStateChangeListener(this); |
| |
| // 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. |
| updateTextSelectionUI(false); |
| mSystemCaptioningBridge.removeListener(this); |
| } |
| |
| /** |
| * @see View#onVisibilityChanged(android.view.View, int) |
| */ |
| public void onVisibilityChanged(View changedView, int visibility) { |
| if (visibility != View.VISIBLE) { |
| mZoomControlsDelegate.dismissZoomPicker(); |
| } |
| } |
| |
| /** |
| * @see View#onCreateInputConnection(EditorInfo) |
| */ |
| public InputConnection onCreateInputConnection(EditorInfo outAttrs) { |
| return mImeAdapter.onCreateInputConnection(outAttrs); |
| } |
| |
| /** |
| * @see View#onCheckIsTextEditor() |
| */ |
| public boolean onCheckIsTextEditor() { |
| return mImeAdapter.hasTextInputType(); |
| } |
| |
| /** |
| * @see View#onConfigurationChanged(Configuration) |
| */ |
| @SuppressWarnings("javadoc") |
| public void onConfigurationChanged(Configuration newConfig) { |
| try { |
| TraceEvent.begin("ContentViewCore.onConfigurationChanged"); |
| mImeAdapter.onKeyboardConfigurationChanged(newConfig); |
| mContainerViewInternals.super_onConfigurationChanged(newConfig); |
| // 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(); |
| } finally { |
| TraceEvent.end("ContentViewCore.onConfigurationChanged"); |
| } |
| } |
| |
| /** |
| * @see View#onSizeChanged(int, int, int, int) |
| */ |
| @SuppressWarnings("javadoc") |
| public void onSizeChanged(int wPix, int hPix, int owPix, int ohPix) { |
| if (getViewportWidthPix() == wPix && getViewportHeightPix() == hPix) return; |
| |
| mViewportWidthPix = wPix; |
| mViewportHeightPix = hPix; |
| if (mNativeContentViewCore != 0) { |
| nativeWasResized(mNativeContentViewCore); |
| } |
| |
| updateAfterSizeChanged(); |
| } |
| |
| /** |
| * Called when the underlying surface the compositor draws to changes size. |
| * This may be larger than the viewport size. |
| */ |
| public void onPhysicalBackingSizeChanged(int wPix, int hPix) { |
| if (mPhysicalBackingWidthPix == wPix && mPhysicalBackingHeightPix == hPix) return; |
| |
| mPhysicalBackingWidthPix = wPix; |
| mPhysicalBackingHeightPix = hPix; |
| |
| if (mNativeContentViewCore != 0) { |
| nativeWasResized(mNativeContentViewCore); |
| } |
| } |
| |
| private void updateAfterSizeChanged() { |
| mPopupZoomer.hide(false); |
| |
| // Execute a delayed form focus operation because the OSK was brought |
| // up earlier. |
| if (!mFocusPreOSKViewportRect.isEmpty()) { |
| Rect rect = new Rect(); |
| getContainerView().getWindowVisibleDisplayFrame(rect); |
| if (!rect.equals(mFocusPreOSKViewportRect)) { |
| // Only assume the OSK triggered the onSizeChanged if width was preserved. |
| if (rect.width() == mFocusPreOSKViewportRect.width()) { |
| assert mWebContents != null; |
| mWebContents.scrollFocusedEditableNodeIntoView(); |
| } |
| cancelRequestToScrollFocusedEditableNodeIntoView(); |
| } |
| } |
| } |
| |
| 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) { |
| mImeAdapter.onWindowFocusChanged(hasWindowFocus); |
| if (!hasWindowFocus) resetGestureDetection(); |
| mSelectionPopupController.onWindowFocusChanged(hasWindowFocus); |
| for (mGestureStateListenersIterator.rewind(); mGestureStateListenersIterator.hasNext();) { |
| mGestureStateListenersIterator.next().onWindowFocusChanged(hasWindowFocus); |
| } |
| } |
| |
| public void onFocusChanged(boolean gainFocus) { |
| mImeAdapter.onViewFocusChanged(gainFocus); |
| mJoystickScrollProvider.setEnabled(gainFocus && !isFocusedNodeEditable()); |
| |
| if (gainFocus) { |
| restoreSelectionPopupsIfNecessary(); |
| } else { |
| cancelRequestToScrollFocusedEditableNodeIntoView(); |
| if (mPreserveSelectionOnNextLossOfFocus) { |
| mPreserveSelectionOnNextLossOfFocus = false; |
| hidePopupsAndPreserveSelection(); |
| } else { |
| hidePopupsAndClearSelection(); |
| // 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. |
| clearSelection(); |
| } |
| } |
| if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus); |
| } |
| |
| /** |
| * @see View#onKeyUp(int, KeyEvent) |
| */ |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| if (mPopupZoomer.isShowing() && keyCode == KeyEvent.KEYCODE_BACK) { |
| mPopupZoomer.hide(true); |
| return true; |
| } |
| return mContainerViewInternals.super_onKeyUp(keyCode, event); |
| } |
| |
| /** |
| * @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 (mImeAdapter.dispatchKeyEvent(event)) return true; |
| |
| return mContainerViewInternals.super_dispatchKeyEvent(event); |
| } |
| |
| /** |
| * @see View#onHoverEvent(MotionEvent) |
| * Mouse move events are sent on hover enter, hover move and hover exit. |
| * They are sent on hover exit because sometimes it acts as both a hover |
| * move and hover exit. |
| */ |
| public boolean onHoverEvent(MotionEvent event) { |
| TraceEvent.begin("onHoverEvent"); |
| |
| MotionEvent offset = createOffsetMotionEvent(event); |
| try { |
| if (mBrowserAccessibilityManager != null && !mIsObscuredByAnotherView) { |
| 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; |
| } |
| |
| // TODO(lanwei): Remove this switch once experimentation is complete - |
| // crbug.com/418188 |
| if (event.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER) { |
| if (mEnableTouchHover == null) { |
| mEnableTouchHover = |
| CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_TOUCH_HOVER); |
| } |
| if (!mEnableTouchHover.booleanValue()) return false; |
| } |
| |
| mContainerView.removeCallbacks(mFakeMouseMoveRunnable); |
| if (mNativeContentViewCore != 0) { |
| nativeSendMouseMoveEvent(mNativeContentViewCore, offset.getEventTime(), |
| offset.getX(), offset.getY(), event.getToolType(0)); |
| } |
| return true; |
| } finally { |
| offset.recycle(); |
| TraceEvent.end("onHoverEvent"); |
| } |
| } |
| |
| /** |
| * @see View#onGenericMotionEvent(MotionEvent) |
| */ |
| public boolean onGenericMotionEvent(MotionEvent event) { |
| if (GamepadList.onGenericMotionEvent(event)) return true; |
| if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { |
| mLastFocalEventX = event.getX(); |
| mLastFocalEventY = event.getY(); |
| switch (event.getAction()) { |
| case MotionEvent.ACTION_SCROLL: |
| if (mNativeContentViewCore == 0) return false; |
| |
| nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(), |
| event.getX(), event.getY(), |
| event.getAxisValue(MotionEvent.AXIS_HSCROLL), |
| event.getAxisValue(MotionEvent.AXIS_VSCROLL), |
| mRenderCoordinates.getWheelScrollFactor()); |
| |
| mContainerView.removeCallbacks(mFakeMouseMoveRunnable); |
| // Send a delayed onMouseMove event so that we end |
| // up hovering over the right position after the scroll. |
| final MotionEvent eventFakeMouseMove = MotionEvent.obtain(event); |
| mFakeMouseMoveRunnable = new Runnable() { |
| @Override |
| public void run() { |
| onHoverEvent(eventFakeMouseMove); |
| eventFakeMouseMove.recycle(); |
| } |
| }; |
| mContainerView.postDelayed(mFakeMouseMoveRunnable, 250); |
| return true; |
| } |
| } else if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { |
| if (mJoystickScrollProvider.onMotion(event)) return true; |
| if (mJoystickZoomProvider == null) { |
| mJoystickZoomProvider = |
| new JoystickZoomProvider(this, new SystemAnimationIntervalProvider()); |
| } |
| if (mJoystickZoomProvider.onMotion(event)) return true; |
| } |
| return mContainerViewInternals.super_onGenericMotionEvent(event); |
| } |
| |
| /** |
| * Sets the current amount to offset incoming touch events by (including MotionEvent and |
| * DragEvent). 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 setCurrentTouchEventOffsets(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() |
| * are overridden, so that View's mScrollX and mScrollY will be unchanged at |
| * (0, 0). This is critical for drawing ContentView correctly. |
| */ |
| public void scrollBy(float dxPix, float dyPix, boolean useLastFocalEventLocation) { |
| if (mNativeContentViewCore == 0) return; |
| if (dxPix == 0 && dyPix == 0) return; |
| long time = SystemClock.uptimeMillis(); |
| // It's a very real (and valid) possibility that a fling may still |
| // be active when programatically scrolling. Cancelling the fling in |
| // such cases ensures a consistent gesture event stream. |
| if (mPotentiallyActiveFlingCount > 0) nativeFlingCancel(mNativeContentViewCore, time); |
| // x/y represents starting location of scroll. |
| final float x = useLastFocalEventLocation ? mLastFocalEventX : 0f; |
| final float y = useLastFocalEventLocation ? mLastFocalEventY : 0f; |
| nativeScrollBegin( |
| mNativeContentViewCore, time, x, y, -dxPix, -dyPix, !useLastFocalEventLocation); |
| nativeScrollBy(mNativeContentViewCore, time, x, y, dxPix, dyPix); |
| nativeScrollEnd(mNativeContentViewCore, time); |
| } |
| |
| /** |
| * @see View#scrollTo(int, int) |
| */ |
| public void scrollTo(float xPix, float yPix) { |
| if (mNativeContentViewCore == 0) return; |
| final float xCurrentPix = mRenderCoordinates.getScrollXPix(); |
| final float yCurrentPix = mRenderCoordinates.getScrollYPix(); |
| final float dxPix = xPix - xCurrentPix; |
| final float dyPix = yPix - yCurrentPix; |
| scrollBy(dxPix, dyPix, false); |
| } |
| |
| // NOTE: this can go away once ContentView.getScrollX() reports correct values. |
| // see: b/6029133 |
| public int getNativeScrollXForTest() { |
| return mRenderCoordinates.getScrollXPixInt(); |
| } |
| |
| // NOTE: this can go away once ContentView.getScrollY() reports correct values. |
| // see: b/6029133 |
| public int getNativeScrollYForTest() { |
| return mRenderCoordinates.getScrollYPixInt(); |
| } |
| |
| /** |
| * @see View#computeHorizontalScrollExtent() |
| */ |
| @SuppressWarnings("javadoc") |
| public int computeHorizontalScrollExtent() { |
| return mRenderCoordinates.getLastFrameViewportWidthPixInt(); |
| } |
| |
| /** |
| * @see View#computeHorizontalScrollOffset() |
| */ |
| @SuppressWarnings("javadoc") |
| public int computeHorizontalScrollOffset() { |
| return mRenderCoordinates.getScrollXPixInt(); |
| } |
| |
| /** |
| * @see View#computeHorizontalScrollRange() |
| */ |
| @SuppressWarnings("javadoc") |
| public int computeHorizontalScrollRange() { |
| return mRenderCoordinates.getContentWidthPixInt(); |
| } |
| |
| /** |
| * @see View#computeVerticalScrollExtent() |
| */ |
| @SuppressWarnings("javadoc") |
| public int computeVerticalScrollExtent() { |
| return mRenderCoordinates.getLastFrameViewportHeightPixInt(); |
| } |
| |
| /** |
| * @see View#computeVerticalScrollOffset() |
| */ |
| @SuppressWarnings("javadoc") |
| public int computeVerticalScrollOffset() { |
| return mRenderCoordinates.getScrollYPixInt(); |
| } |
| |
| /** |
| * @see View#computeVerticalScrollRange() |
| */ |
| @SuppressWarnings("javadoc") |
| public int computeVerticalScrollRange() { |
| return mRenderCoordinates.getContentHeightPixInt(); |
| } |
| |
| // End FrameLayout overrides. |
| |
| /** |
| * @see View#awakenScrollBars(int, boolean) |
| */ |
| @SuppressWarnings("javadoc") |
| public boolean awakenScrollBars(int startDelay, boolean invalidate) { |
| // For the default implementation of ContentView which draws the scrollBars on the native |
| // side, calling this function may get us into a bad state where we keep drawing the |
| // scrollBars, so disable it by always returning false. |
| if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) { |
| return false; |
| } else { |
| return mContainerViewInternals.super_awakenScrollBars(startDelay, invalidate); |
| } |
| } |
| |
| private void updateForTapOrPress(int type, float xPix, float yPix) { |
| if (type != GestureEventType.SINGLE_TAP_CONFIRMED |
| && type != GestureEventType.SINGLE_TAP_UP |
| && type != GestureEventType.LONG_PRESS |
| && type != GestureEventType.LONG_TAP) { |
| return; |
| } |
| |
| if (mContainerView.isFocusable() && mContainerView.isFocusableInTouchMode() |
| && !mContainerView.isFocused()) { |
| mContainerView.requestFocus(); |
| } |
| |
| if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix); |
| mLastFocalEventX = xPix; |
| mLastFocalEventY = yPix; |
| } |
| |
| public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) { |
| if (zoomControlsDelegate == null) { |
| mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE; |
| return; |
| } |
| mZoomControlsDelegate = zoomControlsDelegate; |
| } |
| |
| public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) { |
| if (mNativeContentViewCore == 0) return; |
| nativeSetMultiTouchZoomSupportEnabled(mNativeContentViewCore, supportsMultiTouchZoom); |
| } |
| |
| public void updateDoubleTapSupport(boolean supportsDoubleTap) { |
| if (mNativeContentViewCore == 0) return; |
| nativeSetDoubleTapSupportEnabled(mNativeContentViewCore, supportsDoubleTap); |
| } |
| |
| public void selectPopupMenuItems(int[] indices) { |
| if (mNativeContentViewCore != 0) { |
| nativeSelectPopupMenuItems(mNativeContentViewCore, mNativeSelectPopupSourceFrame, |
| indices); |
| } |
| mNativeSelectPopupSourceFrame = 0; |
| mSelectPopup = null; |
| } |
| |
| /** |
| * Send the screen orientation value to the renderer. |
| */ |
| @VisibleForTesting |
| void sendOrientationChangeEvent(int orientation) { |
| if (mNativeContentViewCore == 0) return; |
| |
| nativeSendOrientationChangeEvent(mNativeContentViewCore, orientation); |
| } |
| |
| public ActionModeCallbackHelper getActionModeCallbackHelper() { |
| return mSelectionPopupController; |
| } |
| |
| private void showSelectActionMode(boolean allowFallback) { |
| if (!mSelectionPopupController.showActionMode(allowFallback)) clearSelection(); |
| } |
| |
| public void clearSelection() { |
| mSelectionPopupController.clearSelection(); |
| } |
| |
| /** |
| * Ensure the selection is preserved the next time the view loses focus. |
| */ |
| public void preserveSelectionOnNextLossOfFocus() { |
| mPreserveSelectionOnNextLossOfFocus = true; |
| } |
| |
| @CalledByNative |
| private void onSelectionEvent( |
| int eventType, int xAnchor, int yAnchor, int left, int top, int right, int bottom) { |
| mSelectionPopupController.onSelectionEvent(eventType, xAnchor, yAnchor, |
| left, top, right, bottom, isScrollInProgress(), mTouchScrollInProgress); |
| } |
| |
| private void setTextHandlesTemporarilyHidden(boolean hide) { |
| if (mNativeContentViewCore == 0) return; |
| nativeSetTextHandlesTemporarilyHidden(mNativeContentViewCore, hide); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void updateFrameInfo(float scrollOffsetX, float scrollOffsetY, float pageScaleFactor, |
| float minPageScaleFactor, float maxPageScaleFactor, float contentWidth, |
| float contentHeight, float viewportWidth, float viewportHeight, |
| float browserControlsHeightDp, float browserControlsShownRatio, |
| float bottomControlsHeightDp, float bottomControlsShownRatio, |
| boolean isMobileOptimizedHint, boolean hasInsertionMarker, |
| boolean isInsertionMarkerVisible, float insertionMarkerHorizontal, |
| float insertionMarkerTop, float insertionMarkerBottom) { |
| TraceEvent.begin("ContentViewCore:updateFrameInfo"); |
| mIsMobileOptimizedHint = isMobileOptimizedHint; |
| // 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, |
| mViewportWidthPix / (deviceScale * pageScaleFactor)); |
| contentHeight = Math.max(contentHeight, |
| mViewportHeightPix / (deviceScale * pageScaleFactor)); |
| final float topBarShownPix = |
| browserControlsHeightDp * deviceScale * browserControlsShownRatio; |
| final float bottomBarShownPix = bottomControlsHeightDp * deviceScale |
| * bottomControlsShownRatio; |
| |
| final boolean contentSizeChanged = |
| contentWidth != mRenderCoordinates.getContentWidthCss() |
| || contentHeight != mRenderCoordinates.getContentHeightCss(); |
| final boolean scaleLimitsChanged = |
| minPageScaleFactor != mRenderCoordinates.getMinPageScaleFactor() |
| || maxPageScaleFactor != mRenderCoordinates.getMaxPageScaleFactor(); |
| final boolean pageScaleChanged = |
| pageScaleFactor != mRenderCoordinates.getPageScaleFactor(); |
| final boolean scrollChanged = |
| pageScaleChanged |
| || scrollOffsetX != mRenderCoordinates.getScrollX() |
| || scrollOffsetY != mRenderCoordinates.getScrollY(); |
| final boolean topBarChanged = Float.compare(topBarShownPix, |
| mRenderCoordinates.getContentOffsetYPix()) != 0; |
| final boolean bottomBarChanged = Float.compare(bottomBarShownPix, mRenderCoordinates |
| .getContentOffsetYPixBottom()) != 0; |
| |
| final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged; |
| final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged; |
| |
| if (needHidePopupZoomer) mPopupZoomer.hide(true); |
| |
| if (scrollChanged) { |
| mContainerViewInternals.onScrollChanged( |
| (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetX), |
| (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetY), |
| (int) mRenderCoordinates.getScrollXPix(), |
| (int) mRenderCoordinates.getScrollYPix()); |
| } |
| |
| mRenderCoordinates.updateFrameInfo( |
| scrollOffsetX, scrollOffsetY, |
| contentWidth, contentHeight, |
| viewportWidth, viewportHeight, |
| pageScaleFactor, minPageScaleFactor, maxPageScaleFactor, |
| topBarShownPix, bottomBarShownPix); |
| |
| if (scrollChanged || topBarChanged) { |
| for (mGestureStateListenersIterator.rewind(); |
| mGestureStateListenersIterator.hasNext();) { |
| mGestureStateListenersIterator.next().onScrollOffsetOrExtentChanged( |
| computeVerticalScrollOffset(), |
| computeVerticalScrollExtent()); |
| } |
| } |
| |
| if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls(); |
| |
| if (topBarChanged) { |
| float topBarTranslate = topBarShownPix - browserControlsHeightDp * deviceScale; |
| getContentViewClient().onTopControlsChanged(topBarTranslate, topBarShownPix); |
| } |
| if (bottomBarChanged) { |
| float bottomBarTranslate = bottomControlsHeightDp * deviceScale - bottomBarShownPix; |
| getContentViewClient().onBottomControlsChanged(bottomBarTranslate, bottomBarShownPix); |
| } |
| |
| if (mBrowserAccessibilityManager != null) { |
| mBrowserAccessibilityManager.notifyFrameInfoInitialized(); |
| } |
| |
| mImeAdapter.onUpdateFrameInfo(mRenderCoordinates, hasInsertionMarker, |
| isInsertionMarkerVisible, insertionMarkerHorizontal, insertionMarkerTop, |
| insertionMarkerBottom); |
| |
| TraceEvent.end("ContentViewCore:updateFrameInfo"); |
| } |
| |
| @CalledByNative |
| private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType, |
| int textInputFlags, String text, int selectionStart, int selectionEnd, |
| int compositionStart, int compositionEnd, boolean showImeIfNeeded, |
| boolean isNonImeChange) { |
| try { |
| TraceEvent.begin("ContentViewCore.updateImeAdapter"); |
| boolean focusedNodeEditable = (textInputType != TextInputType.NONE); |
| boolean focusedNodeIsPassword = (textInputType == TextInputType.PASSWORD); |
| |
| mImeAdapter.attach(nativeImeAdapterAndroid); |
| mImeAdapter.updateKeyboardVisibility( |
| textInputType, textInputFlags, showImeIfNeeded); |
| mImeAdapter.updateState(text, selectionStart, selectionEnd, compositionStart, |
| compositionEnd, isNonImeChange); |
| |
| boolean editableToggled = (focusedNodeEditable != isFocusedNodeEditable()); |
| mSelectionPopupController.updateSelectionState(focusedNodeEditable, |
| focusedNodeIsPassword); |
| if (editableToggled) { |
| mJoystickScrollProvider.setEnabled(!focusedNodeEditable); |
| getContentViewClient().onFocusedNodeEditabilityChanged(focusedNodeEditable); |
| } |
| } finally { |
| TraceEvent.end("ContentViewCore.updateImeAdapter"); |
| } |
| } |
| |
| @CalledByNative |
| private void forceUpdateImeAdapter(long nativeImeAdapterAndroid) { |
| mImeAdapter.attach(nativeImeAdapterAndroid); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void setTitle(String title) { |
| getContentViewClient().onUpdateTitle(title); |
| } |
| |
| /** |
| * Called (from native) when the <select> popup needs to be shown. |
| * @param anchorView View anchored for popup. |
| * @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. |
| * @param selectedIndices Indices of selected items. |
| */ |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void showSelectPopup(View anchorView, long nativeSelectPopupSourceFrame, String[] items, |
| int[] enabled, boolean multiple, int[] selectedIndices, boolean rightAligned) { |
| 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])); |
| } |
| if (DeviceFormFactor.isTablet(mContext) && !multiple && !isTouchExplorationEnabled()) { |
| mSelectPopup = new SelectPopupDropdown( |
| this, anchorView, popupItems, selectedIndices, rightAligned); |
| } else { |
| if (getWindowAndroid() == null) return; |
| Context windowContext = getWindowAndroid().getContext().get(); |
| if (windowContext == null) return; |
| mSelectPopup = new SelectPopupDialog( |
| this, windowContext, popupItems, multiple, selectedIndices); |
| } |
| mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame; |
| mSelectPopup.show(); |
| } |
| |
| /** |
| * Called when the <select> popup needs to be hidden. |
| */ |
| @CalledByNative |
| private void hideSelectPopup() { |
| if (mSelectPopup == null) return; |
| mSelectPopup.hide(false); |
| mSelectPopup = null; |
| mNativeSelectPopupSourceFrame = 0; |
| } |
| |
| /** |
| * Called when the <select> popup needs to be hidden. This calls |
| * nativeSelectPopupMenuItems() with null indices. |
| */ |
| private void hideSelectPopupWithCancelMessage() { |
| if (mSelectPopup != null) mSelectPopup.hide(true); |
| } |
| |
| /** |
| * @return The visible select popup being shown. |
| */ |
| @VisibleForTesting |
| public SelectPopup getSelectPopupForTest() { |
| return mSelectPopup; |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) { |
| mPopupZoomer.setBitmap(zoomedBitmap); |
| mPopupZoomer.show(targetRect); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private MotionEventSynthesizer createMotionEventSynthesizer() { |
| return new MotionEventSynthesizer(this); |
| } |
| |
| /** |
| * Initialize the view with an overscroll refresh handler. |
| * @param handler The refresh handler. |
| */ |
| public void setOverscrollRefreshHandler(OverscrollRefreshHandler handler) { |
| assert mOverscrollRefreshHandler == null || handler == null; |
| mOverscrollRefreshHandler = handler; |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private boolean onOverscrollRefreshStart() { |
| if (mOverscrollRefreshHandler == null) return false; |
| return mOverscrollRefreshHandler.start(); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onOverscrollRefreshUpdate(float delta) { |
| if (mOverscrollRefreshHandler != null) mOverscrollRefreshHandler.pull(delta); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onOverscrollRefreshRelease(boolean allowRefresh) { |
| if (mOverscrollRefreshHandler != null) mOverscrollRefreshHandler.release(allowRefresh); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onOverscrollRefreshReset() { |
| if (mOverscrollRefreshHandler != null) mOverscrollRefreshHandler.reset(); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onSelectionChanged(String text) { |
| mSelectionPopupController.onSelectionChanged(text); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void performLongPressHapticFeedback() { |
| mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); |
| } |
| |
| @CalledByNative |
| private void showPastePopup(int x, int y) { |
| mSelectionPopupController.showPastePopup(x, y); |
| } |
| |
| private void hidePastePopup() { |
| mSelectionPopupController.hidePastePopup(); |
| } |
| |
| private void destroyPastePopup() { |
| mSelectionPopupController.destroyPastePopup(); |
| } |
| |
| private boolean canPaste() { |
| return ((ClipboardManager) mContext.getSystemService( |
| Context.CLIPBOARD_SERVICE)).hasPrimaryClip(); |
| } |
| |
| @SuppressWarnings("unused") |
| @CalledByNative |
| private void onRenderProcessChange() { |
| attachImeAdapter(); |
| // Immediately sync closed caption settings to the new render process. |
| mSystemCaptioningBridge.syncToListener(this); |
| } |
| |
| /** |
| * Attaches the native ImeAdapter object to the java ImeAdapter to allow communication via JNI. |
| */ |
| public void attachImeAdapter() { |
| if (mImeAdapter != null && mNativeContentViewCore != 0) { |
| mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore)); |
| } |
| } |
| |
| /** |
| * @see View#hasFocus() |
| */ |
| @CalledByNative |
| private boolean hasFocus() { |
| // 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(); |
| } |
| |
| /** |
| * Checks whether the ContentViewCore can be zoomed in. |
| * |
| * @return True if the ContentViewCore can be zoomed in. |
| */ |
| // This method uses the term 'zoom' for legacy reasons, but relates |
| // to what chrome calls the 'page scale factor'. |
| public boolean canZoomIn() { |
| final float zoomInExtent = mRenderCoordinates.getMaxPageScaleFactor() |
| - mRenderCoordinates.getPageScaleFactor(); |
| return zoomInExtent > ZOOM_CONTROLS_EPSILON; |
| } |
| |
| /** |
| * Checks whether the ContentViewCore can be zoomed out. |
| * |
| * @return True if the ContentViewCore can be zoomed out. |
| */ |
| // This method uses the term 'zoom' for legacy reasons, but relates |
| // to what chrome calls the 'page scale factor'. |
| public boolean canZoomOut() { |
| final float zoomOutExtent = mRenderCoordinates.getPageScaleFactor() |
| - mRenderCoordinates.getMinPageScaleFactor(); |
| return zoomOutExtent > ZOOM_CONTROLS_EPSILON; |
| } |
| |
| /** |
| * Zooms in the ContentViewCore by 25% (or less if that would result in |
| * zooming in more than possible). |
| * |
| * @return True if there was a zoom change, false otherwise. |
| */ |
| // This method uses the term 'zoom' for legacy reasons, but relates |
| // to what chrome calls the 'page scale factor'. |
| public boolean zoomIn() { |
| if (!canZoomIn()) { |
| return false; |
| } |
| return pinchByDelta(1.25f); |
| } |
| |
| /** |
| * Zooms out the ContentViewCore by 20% (or less if that would result in |
| * zooming out more than possible). |
| * |
| * @return True if there was a zoom change, false otherwise. |
| */ |
| // This method uses the term 'zoom' for legacy reasons, but relates |
| // to what chrome calls the 'page scale factor'. |
| public boolean zoomOut() { |
| if (!canZoomOut()) { |
| return false; |
| } |
| return pinchByDelta(0.8f); |
| } |
| |
| /** |
| * Resets the zoom factor of the ContentViewCore. |
| * |
| * @return True if there was a zoom change, false otherwise. |
| */ |
| // This method uses the term 'zoom' for legacy reasons, but relates |
| // to what chrome calls the 'page scale factor'. |
| public boolean zoomReset() { |
| // The page scale factor is initialized to mNativeMinimumScale when |
| // the page finishes loading. Thus sets it back to mNativeMinimumScale. |
| if (!canZoomOut()) return false; |
| return pinchByDelta( |
| mRenderCoordinates.getMinPageScaleFactor() |
| / mRenderCoordinates.getPageScaleFactor()); |
| } |
| |
| /** |
| * Simulate a pinch zoom gesture. |
| * |
| * @param delta the factor by which the current page scale should be multiplied by. |
| * @return whether the gesture was sent. |
| */ |
| public boolean pinchByDelta(float delta) { |
| if (mNativeContentViewCore == 0) return false; |
| |
| long timeMs = SystemClock.uptimeMillis(); |
| int xPix = getViewportWidthPix() / 2; |
| int yPix = getViewportHeightPix() / 2; |
| |
| nativePinchBegin(mNativeContentViewCore, timeMs, xPix, yPix); |
| nativePinchBy(mNativeContentViewCore, timeMs, xPix, yPix, delta); |
| nativePinchEnd(mNativeContentViewCore, timeMs); |
| |
| return true; |
| } |
| |
| /** |
| * Send start of pinch zoom gesture. |
| * |
| * @param xPix X-coordinate of location from which pinch zoom would start. |
| * @param yPix Y-coordinate of location from which pinch zoom would start. |
| * @return whether the pinch zoom start gesture was sent. |
| */ |
| public boolean pinchBegin(int xPix, int yPix) { |
| if (mNativeContentViewCore == 0) return false; |
| nativePinchBegin(mNativeContentViewCore, SystemClock.uptimeMillis(), xPix, yPix); |
| return true; |
| } |
| |
| /** |
| * Send pinch zoom gesture. |
| * |
| * @param xPix X-coordinate of pinch zoom location. |
| * @param yPix Y-coordinate of pinch zoom location. |
| * @param delta the factor by which the current page scale should be multiplied by. |
| * @return whether the pinchby gesture was sent. |
| */ |
| public boolean pinchBy(int xPix, int yPix, float delta) { |
| if (mNativeContentViewCore == 0) return false; |
| nativePinchBy(mNativeContentViewCore, SystemClock.uptimeMillis(), xPix, yPix, delta); |
| return true; |
| } |
| |
| /** |
| * Stop pinch zoom gesture. |
| * |
| * @return whether the pinch stop gesture was sent. |
| */ |
| public boolean pinchEnd() { |
| if (mNativeContentViewCore == 0) return false; |
| nativePinchEnd(mNativeContentViewCore, SystemClock.uptimeMillis()); |
| return true; |
| } |
| |
| /** |
| * Invokes the graphical zoom picker widget for this ContentView. |
| */ |
| public void invokeZoomPicker() { |
| mZoomControlsDelegate.invokeZoomPicker(); |
| } |
| |
| /** |
| * 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. |
| * |
| * @param object The Java object to inject into the ContentViewCore's JavaScript context. Null |
| * values are ignored. |
| * @param name The name used to expose the instance in JavaScript. |
| */ |
| public void addJavascriptInterface(Object object, String name) { |
| addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class); |
| } |
| |
| /** |
| * This method injects the supplied Java object into the ContentViewCore. |
| * The object is injected into the JavaScript context of the main frame, |
| * using the supplied name. This allows the Java object to be accessed from |
| * JavaScript. Note that that injected objects will not appear in |
| * JavaScript until the page is next (re)loaded. For example: |
| * <pre> view.addJavascriptInterface(new Object(), "injectedObject"); |
| * view.loadData("<!DOCTYPE html><title></title>", "text/html", null); |
| * view.loadUrl("javascript:alert(injectedObject.toString())");</pre> |
| * <p><strong>IMPORTANT:</strong> |
| * <ul> |
| * <li> addJavascriptInterface() can be used to allow JavaScript to control |
| * the host application. This is a powerful feature, but also presents a |
| * security risk. Use of this method in a ContentViewCore containing |
| * untrusted content could allow an attacker to manipulate the host |
| * application in unintended ways, executing Java code with the permissions |
| * of the host application. Use extreme care when using this method in a |
| * ContentViewCore which could contain untrusted content. Particular care |
| * should be taken to avoid unintentional access to inherited methods, such |
| * as {@link Object#getClass()}. To prevent access to inherited methods, |
| * pass an annotation for {@code requiredAnnotation}. This will ensure |
| * that only methods with {@code requiredAnnotation} are exposed to the |
| * Javascript layer. {@code requiredAnnotation} will be passed to all |
| * subsequently injected Java objects if any methods return an object. This |
| * means the same restrictions (or lack thereof) will apply. Alternatively, |
| * {@link #addJavascriptInterface(Object, String)} can be called, which |
| * automatically uses the {@link JavascriptInterface} annotation. |
| * <li> JavaScript interacts with Java objects on a private, background |
| * thread of the ContentViewCore. Care is therefore required to maintain |
| * thread safety.</li> |
| * </ul></p> |
| * |
| * @param object The Java object to inject into the |
| * ContentViewCore's JavaScript context. Null |
| * values are ignored. |
| * @param name The name used to expose the instance in |
| * JavaScript. |
| * @param requiredAnnotation Restrict exposed methods to ones with this |
| * annotation. If {@code null} all methods are |
| * exposed. |
| * |
| */ |
| public void addPossiblyUnsafeJavascriptInterface(Object object, String name, |
| Class<? extends Annotation> requiredAnnotation) { |
| if (mNativeContentViewCore != 0 && object != null) { |
| mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation)); |
| nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation); |
| } |
| } |
| |
| /** |
| * Removes a previously added JavaScript interface with the given name. |
| * |
| * @param name The name of the interface to remove. |
| */ |
| public void removeJavascriptInterface(String name) { |
| mJavaScriptInterfaces.remove(name); |
| if (mNativeContentViewCore != 0) { |
| nativeRemoveJavascriptInterface(mNativeContentViewCore, name); |
| } |
| } |
| |
| /** |
| * Return the current scale of the ContentView. |
| * @return The current page scale factor. |
| */ |
| @VisibleForTesting |
| public float getScale() { |
| return mRenderCoordinates.getPageScaleFactor(); |
| } |
| |
| @CalledByNative |
| private void startContentIntent(String contentUrl, boolean isMainFrame) { |
| getContentViewClient().onStartContentIntent(getContext(), contentUrl, isMainFrame); |
| } |
| |
| @Override |
| public void onAccessibilityStateChanged(boolean enabled) { |
| setAccessibilityState(enabled); |
| } |
| |
| /** |
| * Determines whether or not this ContentViewCore can handle this accessibility action. |
| * @param action The action to perform. |
| * @return Whether or not this action is supported. |
| */ |
| public boolean supportsAccessibilityAction(int action) { |
| // TODO(dmazzoni): implement this in BrowserAccessibilityManager. |
| return false; |
| } |
| |
| /** |
| * Attempts to perform an accessibility action on the web content. If the accessibility action |
| * cannot be processed, it returns {@code null}, allowing the caller to know to call the |
| * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value. |
| * Otherwise the return value from this method should be used. |
| * @param action The action to perform. |
| * @param arguments Optional action arguments. |
| * @return Whether the action was performed or {@code null} if the call should be delegated to |
| * the super {@link View} class. |
| */ |
| public boolean performAccessibilityAction(int action, Bundle arguments) { |
| // TODO(dmazzoni): implement this in BrowserAccessibilityManager. |
| return false; |
| } |
| |
| /** |
| * Set the BrowserAccessibilityManager, used for native accessibility |
| * (not script injection). This is only set when system accessibility |
| * has been enabled. |
| * @param manager The new BrowserAccessibilityManager. |
| */ |
| public void setBrowserAccessibilityManager(BrowserAccessibilityManager manager) { |
| mBrowserAccessibilityManager = manager; |
| |
| if (mBrowserAccessibilityManager != null && mRenderCoordinates.hasFrameInfo()) { |
| mBrowserAccessibilityManager.notifyFrameInfoInitialized(); |
| } |
| |
| if (mBrowserAccessibilityManager == null) mNativeAccessibilityEnabled = false; |
| } |
| |
| /** |
| * Get the BrowserAccessibilityManager, used for native accessibility |
| * (not script injection). This will return null when system accessibility |
| * is not enabled. |
| * @return This view's BrowserAccessibilityManager. |
| */ |
| public BrowserAccessibilityManager getBrowserAccessibilityManager() { |
| return mBrowserAccessibilityManager; |
| } |
| |
| /** |
| * If native accessibility is enabled and no other views are temporarily |
| * obscuring this one, returns an AccessibilityNodeProvider that |
| * implements native accessibility for this view. Returns null otherwise. |
| * Lazily initializes native accessibility here if it's allowed. |
| * @return The AccessibilityNodeProvider, if available, or null otherwise. |
| */ |
| public AccessibilityNodeProvider getAccessibilityNodeProvider() { |
| if (mIsObscuredByAnotherView) return null; |
| |
| if (mBrowserAccessibilityManager != null) { |
| return mBrowserAccessibilityManager.getAccessibilityNodeProvider(); |
| } |
| |
| if (mNativeAccessibilityAllowed && !mNativeAccessibilityEnabled |
| && mNativeContentViewCore != 0) { |
| mNativeAccessibilityEnabled = true; |
| nativeSetAccessibilityEnabled(mNativeContentViewCore, true); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Set whether or not the web contents are obscured by another view. |
| * If true, we won't return an accessibility node provider or respond |
| * to touch exploration events. |
| */ |
| public void setObscuredByAnotherView(boolean isObscured) { |
| if (isObscured != mIsObscuredByAnotherView) { |
| mIsObscuredByAnotherView = isObscured; |
| getContainerView().sendAccessibilityEvent( |
| AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); |
| } |
| } |
| |
| @TargetApi(Build.VERSION_CODES.M) |
| public void onProvideVirtualStructure( |
| final ViewStructure structure, final boolean ignoreScrollOffset) { |
| // Do not collect accessibility tree in incognito mode |
| if (getWebContents().isIncognito()) { |
| structure.setChildCount(0); |
| return; |
| } |
| structure.setChildCount(1); |
| final ViewStructure viewRoot = structure.asyncNewChild(0); |
| getWebContents().requestAccessibilitySnapshot(new AccessibilitySnapshotCallback() { |
| @Override |
| public void onAccessibilitySnapshot(AccessibilitySnapshotNode root) { |
| viewRoot.setClassName(""); |
| viewRoot.setHint(mProductVersion); |
| if (root == null) { |
| viewRoot.asyncCommit(); |
| return; |
| } |
| createVirtualStructure(viewRoot, root, ignoreScrollOffset); |
| } |
| }); |
| } |
| |
| // When creating the View structure, the left and top are relative to the parent node. |
| @TargetApi(Build.VERSION_CODES.M) |
| private void createVirtualStructure(ViewStructure viewNode, AccessibilitySnapshotNode node, |
| final boolean ignoreScrollOffset) { |
| viewNode.setClassName(node.className); |
| if (node.hasSelection) { |
| viewNode.setText(node.text, node.startSelection, node.endSelection); |
| } else { |
| viewNode.setText(node.text); |
| } |
| int left = (int) mRenderCoordinates.fromLocalCssToPix(node.x); |
| int top = (int) mRenderCoordinates.fromLocalCssToPix(node.y); |
| int width = (int) mRenderCoordinates.fromLocalCssToPix(node.width); |
| int height = (int) mRenderCoordinates.fromLocalCssToPix(node.height); |
| |
| Rect boundsInParent = new Rect(left, top, left + width, top + height); |
| if (node.isRootNode) { |
| // Offset of the web content relative to the View. |
| boundsInParent.offset(0, (int) mRenderCoordinates.getContentOffsetYPix()); |
| if (!ignoreScrollOffset) { |
| boundsInParent.offset(-(int) mRenderCoordinates.getScrollXPix(), |
| -(int) mRenderCoordinates.getScrollYPix()); |
| } |
| } |
| |
| viewNode.setDimens(boundsInParent.left, boundsInParent.top, 0, 0, width, height); |
| viewNode.setChildCount(node.children.size()); |
| if (node.hasStyle) { |
| // The text size should be in physical pixels, not CSS pixels. |
| float textSize = mRenderCoordinates.fromLocalCssToPix(node.textSize); |
| |
| int style = (node.bold ? ViewNode.TEXT_STYLE_BOLD : 0) |
| | (node.italic ? ViewNode.TEXT_STYLE_ITALIC : 0) |
| | (node.underline ? ViewNode.TEXT_STYLE_UNDERLINE : 0) |
| | (node.lineThrough ? ViewNode.TEXT_STYLE_STRIKE_THRU : 0); |
| viewNode.setTextStyle(textSize, node.color, node.bgcolor, style); |
| } |
| for (int i = 0; i < node.children.size(); i++) { |
| createVirtualStructure(viewNode.asyncNewChild(i), node.children.get(i), true); |
| } |
| viewNode.asyncCommit(); |
| } |
| |
| @TargetApi(Build.VERSION_CODES.KITKAT) |
| @Override |
| public void onSystemCaptioningChanged(TextTrackSettings settings) { |
| if (mNativeContentViewCore == 0) return; |
| nativeSetTextTrackSettings(mNativeContentViewCore, |
| settings.getTextTracksEnabled(), settings.getTextTrackBackgroundColor(), |
| settings.getTextTrackFontFamily(), settings.getTextTrackFontStyle(), |
| settings.getTextTrackFontVariant(), settings.getTextTrackTextColor(), |
| settings.getTextTrackTextShadow(), settings.getTextTrackTextSize()); |
| } |
| |
| /** |
| * Called when the processed text is replied from an activity that supports |
| * Intent.ACTION_PROCESS_TEXT. |
| * @param resultCode the code that indicates if the activity successfully processed the text |
| * @param data the reply that contains the processed text. |
| */ |
| public void onReceivedProcessTextResult(int resultCode, Intent data) { |
| mSelectionPopupController.onReceivedProcessTextResult(resultCode, data); |
| } |
| |
| /** |
| * 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 |
| * accessibility scripts. Native accessibility is enabled on demand. |
| */ |
| public void setAccessibilityState(boolean state) { |
| if (!state) { |
| mNativeAccessibilityAllowed = false; |
| mTouchExplorationEnabled = false; |
| } else { |
| mNativeAccessibilityAllowed = true; |
| mTouchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled(); |
| } |
| } |
| |
| /** |
| * Return whether or not we should set accessibility focus on page load. |
| */ |
| public boolean shouldSetAccessibilityFocusOnPageLoad() { |
| return mShouldSetAccessibilityFocusOnPageLoad; |
| } |
| |
| /** |
| * 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 void setShouldSetAccessibilityFocusOnPageLoad(boolean on) { |
| mShouldSetAccessibilityFocusOnPageLoad = on; |
| } |
| |
| /** |
| * |
| * @return The cached copy of render positions and scales. |
| */ |
| public RenderCoordinates getRenderCoordinates() { |
| return mRenderCoordinates; |
| } |
| |
| /** |
| * @return Whether the current page seems to be mobile-optimized. This hint is based upon |
| * rendered frames and may return different values when called multiple times for the |
| * same page (particularly during page load). |
| */ |
| public boolean getIsMobileOptimizedHint() { |
| return mIsMobileOptimizedHint; |
| } |
| |
| @CalledByNative |
| private static Rect createRect(int x, int y, int right, int bottom) { |
| return new Rect(x, y, right, bottom); |
| } |
| |
| public void extractSmartClipData(int x, int y, int width, int height) { |
| if (mNativeContentViewCore != 0) { |
| x += mSmartClipOffsetX; |
| y += mSmartClipOffsetY; |
| nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| |
| @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); |
| |
| if (mSmartClipDataListener != null) { |
| mSmartClipDataListener.onSmartClipDataExtracted(text, html, clipRect); |
| } |
| } |
| |
| public void setSmartClipDataListener(SmartClipDataListener listener) { |
| mSmartClipDataListener = listener; |
| } |
| |
| public void setBackgroundOpaque(boolean opaque) { |
| if (mNativeContentViewCore != 0) { |
| nativeSetBackgroundOpaque(mNativeContentViewCore, opaque); |
| } |
| } |
| |
| /** |
| * @see View#onDragEvent(DragEvent) |
| */ |
| // TODO(hush): uncomment below when we build with API 24. |
| // @TargetApi(Build.VERSION_CODES.N) |
| public boolean onDragEvent(DragEvent event) { |
| if (mNativeContentViewCore == 0 || Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) { |
| return false; |
| } |
| |
| ClipDescription clipDescription = event.getClipDescription(); |
| |
| // text/* will match text/uri-list, text/html, text/plain. |
| String[] mimeTypes = |
| clipDescription == null ? new String[0] : clipDescription.filterMimeTypes("text/*"); |
| |
| if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) { |
| // TODO(hush): support dragging more than just text. |
| return mimeTypes != null && mimeTypes.length > 0 |
| && nativeIsTouchDragDropEnabled(mNativeContentViewCore); |
| } |
| |
| StringBuilder content = new StringBuilder(""); |
| if (event.getAction() == DragEvent.ACTION_DROP) { |
| // TODO(hush): obtain dragdrop permissions (via reflection?), when dragging files into |
| // Chrome/WebView is supported. Not necessary to do so for now, because only text |
| // dragging is supported. |
| ClipData clipData = event.getClipData(); |
| final int itemCount = clipData.getItemCount(); |
| for (int i = 0; i < itemCount; i++) { |
| ClipData.Item item = clipData.getItemAt(i); |
| content.append(item.coerceToStyledText(mContainerView.getContext())); |
| } |
| } |
| |
| int[] locationOnScreen = new int[2]; |
| mContainerView.getLocationOnScreen(locationOnScreen); |
| |
| float xPix = event.getX() + mCurrentTouchOffsetX; |
| float yPix = event.getY() + mCurrentTouchOffsetY; |
| |
| int xCss = (int) mRenderCoordinates.fromPixToDip(xPix); |
| int yCss = (int) mRenderCoordinates.fromPixToDip(yPix); |
| int screenXCss = (int) mRenderCoordinates.fromPixToDip(xPix + locationOnScreen[0]); |
| int screenYCss = (int) mRenderCoordinates.fromPixToDip(yPix + locationOnScreen[1]); |
| |
| nativeOnDragEvent(mNativeContentViewCore, event.getAction(), xCss, yCss, screenXCss, |
| screenYCss, mimeTypes, content.toString()); |
| return true; |
| } |
| |
| /** |
| * Offer a long press gesture to the embedding View, primarily for WebView compatibility. |
| * |
| * @return true if the embedder handled the event. |
| */ |
| private boolean offerLongPressToEmbedder() { |
| 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; |
| |
| setTouchScrollInProgress(false); |
| mPotentiallyActiveFlingCount = 0; |
| if (touchScrollInProgress) updateGestureStateListener(GestureEventType.SCROLL_END); |
| if (potentiallyActiveFlingCount > 0) updateGestureStateListener(GestureEventType.FLING_END); |
| } |
| |
| @CalledByNative |
| private void onNativeFlingStopped() { |
| // Note that mTouchScrollInProgress should normally be false at this |
| // point, but we reset it anyway as another failsafe. |
| setTouchScrollInProgress(false); |
| if (mPotentiallyActiveFlingCount <= 0) return; |
| mPotentiallyActiveFlingCount--; |
| updateGestureStateListener(GestureEventType.FLING_END); |
| } |
| |
| // DisplayAndroidObserver method. |
| @Override |
| public void onRotationChanged(int rotation) { |
| // ActionMode#invalidate() won't be able to re-layout the floating |
| // action mode menu items according to the new rotation. So Chrome |
| // has to re-create the action mode. |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M |
| && mSelectionPopupController.isActionModeValid()) { |
| hidePopupsAndPreserveSelection(); |
| showSelectActionMode(true); |
| } |
| |
| int rotationDegrees = 0; |
| switch (rotation) { |
| case Surface.ROTATION_0: |
| rotationDegrees = 0; |
| break; |
| case Surface.ROTATION_90: |
| rotationDegrees = 90; |
| break; |
| case Surface.ROTATION_180: |
| rotationDegrees = 180; |
| break; |
| case Surface.ROTATION_270: |
| rotationDegrees = -90; |
| break; |
| default: |
| throw new IllegalStateException( |
| "Display.getRotation() shouldn't return that value"); |
| } |
| |
| sendOrientationChangeEvent(rotationDegrees); |
| } |
| |
| // DisplayAndroidObserver method. |
| @Override |
| public void onDIPScaleChanged(float dipScale) { |
| WindowAndroid windowAndroid = getWindowAndroid(); |
| if (windowAndroid == null || mNativeContentViewCore == 0) return; |
| |
| mRenderCoordinates.setDeviceScaleFactor(dipScale, getWindowAndroid().getContext()); |
| nativeSetDIPScale(mNativeContentViewCore, dipScale); |
| } |
| |
| /** |
| * Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock |
| * the screen orientation. |
| */ |
| public void setFullscreenRequiredForOrientationLock(boolean value) { |
| mFullscreenRequiredForOrientationLock = value; |
| } |
| |
| @CalledByNative |
| private boolean isFullscreenRequiredForOrientationLock() { |
| return mFullscreenRequiredForOrientationLock; |
| } |
| |
| /** |
| * Sets the client to use for Contextual Search functionality. |
| * @param contextualSearchClient The client to notify for Contextual Search operations. |
| */ |
| public void setContextualSearchClient(ContextualSearchClient contextualSearchClient) { |
| mSelectionPopupController.setContextualSearchClient(contextualSearchClient); |
| } |
| |
| /** |
| * Call this when we get result from ResultReceiver passed in calling showSoftInput(). |
| * @param resultCode The result of showSoftInput() as defined in InputMethodManager. |
| */ |
| public void onShowKeyboardReceiveResult(int resultCode) { |
| if (resultCode == InputMethodManager.RESULT_SHOWN) { |
| // If OSK is newly shown, delay the form focus until |
| // the onSizeChanged (in order to adjust relative to the |
| // new size). |
| // TODO(jdduke): We should not assume that onSizeChanged will |
| // always be called, crbug.com/294908. |
| getContainerView().getWindowVisibleDisplayFrame( |
| mFocusPreOSKViewportRect); |
| } else if (hasFocus() && resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN) { |
| // If the OSK was already there, focus the form immediately. |
| if (mWebContents != null) { |
| mWebContents.scrollFocusedEditableNodeIntoView(); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public ResultReceiver getNewShowKeyboardReceiver() { |
| if (mShowKeyboardResultReceiver == null) { |
| // Note: the returned object will get leaked by Android framework. |
| mShowKeyboardResultReceiver = new ShowKeyboardResultReceiver(this, new Handler()); |
| } |
| return mShowKeyboardResultReceiver; |
| } |
| |
| private native long nativeInit(WebContents webContents, ViewAndroidDelegate viewAndroidDelegate, |
| long windowAndroidPtr, float dipScale, HashSet<Object> retainedObjectSet); |
| private static native ContentViewCore nativeFromWebContentsAndroid(WebContents webContents); |
| |
| private native void nativeUpdateWindowAndroid( |
| long nativeContentViewCoreImpl, long windowAndroidPtr); |
| private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl); |
| private native WindowAndroid nativeGetJavaWindowAndroid(long nativeContentViewCoreImpl); |
| |
| private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl); |
| |
| private native void nativeSetFocus(long nativeContentViewCoreImpl, boolean focused); |
| |
| private native void nativeSetDIPScale(long nativeContentViewCoreImpl, float dipScale); |
| |
| private native void nativeSendOrientationChangeEvent( |
| long nativeContentViewCoreImpl, int orientation); |
| |
| // All touch events (including flings, scrolls etc) accept coordinates in physical pixels. |
| 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 tilt0, float tilt1, |
| 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, int toolType); |
| |
| private native int nativeSendMouseWheelEvent(long nativeContentViewCoreImpl, long timeMs, |
| float x, float y, float ticksX, float ticksY, float pixelsPerTick); |
| |
| private native void nativeScrollBegin(long nativeContentViewCoreImpl, long timeMs, float x, |
| float y, float hintX, float hintY, boolean targetViewport); |
| |
| private native void nativeScrollEnd(long nativeContentViewCoreImpl, long timeMs); |
| |
| private native void nativeScrollBy( |
| long nativeContentViewCoreImpl, long timeMs, float x, float y, |
| float deltaX, float deltaY); |
| |
| private native void nativeFlingStart(long nativeContentViewCoreImpl, long timeMs, float x, |
| float y, float vx, float vy, boolean targetViewport); |
| |
| private native void nativeFlingCancel(long nativeContentViewCoreImpl, long timeMs); |
| |
| private native void nativeSingleTap( |
| 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); |
| |
| private native void nativePinchBegin( |
| long nativeContentViewCoreImpl, long timeMs, float x, float y); |
| |
| private native void nativePinchEnd(long nativeContentViewCoreImpl, long timeMs); |
| |
| private native void nativePinchBy(long nativeContentViewCoreImpl, long timeMs, |
| float anchorX, float anchorY, float deltaScale); |
| |
| private native void nativeSetTextHandlesTemporarilyHidden( |
| long nativeContentViewCoreImpl, boolean hidden); |
| |
| private native void nativeResetGestureDetection(long nativeContentViewCoreImpl); |
| |
| private native void nativeSetDoubleTapSupportEnabled( |
| long nativeContentViewCoreImpl, boolean enabled); |
| |
| private native void nativeSetMultiTouchZoomSupportEnabled( |
| long nativeContentViewCoreImpl, boolean enabled); |
| |
| private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl, |
| long nativeSelectPopupSourceFrame, int[] indices); |
| |
| |
| private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl); |
| |
| private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl); |
| |
| private native void nativeSetAllowJavascriptInterfacesInspection( |
| long nativeContentViewCoreImpl, boolean allow); |
| |
| private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object, |
| String name, Class requiredAnnotation); |
| |
| private native void nativeRemoveJavascriptInterface(long nativeContentViewCoreImpl, |
| String name); |
| |
| private native void nativeWasResized(long nativeContentViewCoreImpl); |
| |
| private native void nativeSetAccessibilityEnabled( |
| long nativeContentViewCoreImpl, boolean enabled); |
| |
| private native void nativeSetTextTrackSettings(long nativeContentViewCoreImpl, |
| boolean textTracksEnabled, String textTrackBackgroundColor, String textTrackFontFamily, |
| String textTrackFontStyle, String textTrackFontVariant, String textTrackTextColor, |
| String textTrackTextShadow, String textTrackTextSize); |
| |
| private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl, |
| int x, int y, int w, int h); |
| |
| private native void nativeSetBackgroundOpaque(long nativeContentViewCoreImpl, boolean opaque); |
| private native boolean nativeIsTouchDragDropEnabled(long nativeContentViewCoreImpl); |
| private native void nativeOnDragEvent(long nativeContentViewCoreImpl, int action, int x, int y, |
| int screenX, int screenY, String[] mimeTypes, String content); |
| } |