blob: e90e7bbb03018f7193fa78665bcab6ef4e18095e [file] [log] [blame]
// 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.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.Editable;
import android.util.Log;
import android.util.Pair;
import android.view.ActionMode;
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.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsoluteLayout;
import android.widget.FrameLayout;
import com.google.common.annotations.VisibleForTesting;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.WeakContext;
import org.chromium.content.R;
import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate;
import org.chromium.content.browser.accessibility.AccessibilityInjector;
import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
import org.chromium.content.browser.input.AdapterInputConnection;
import org.chromium.content.browser.input.HandleView;
import org.chromium.content.browser.input.ImeAdapter;
import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory;
import org.chromium.content.browser.input.InputMethodManagerWrapper;
import org.chromium.content.browser.input.InsertionHandleController;
import org.chromium.content.browser.input.SelectPopupDialog;
import org.chromium.content.browser.input.SelectionHandleController;
import org.chromium.content.common.TraceEvent;
import org.chromium.ui.ViewAndroid;
import org.chromium.ui.ViewAndroidDelegate;
import org.chromium.ui.WindowAndroid;
import org.chromium.ui.gfx.DeviceDisplayInfo;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
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 MotionEventDelegate,
NavigationClient,
AccessibilityStateChangeListener {
/**
* Indicates that input events are batched together and delivered just before vsync.
*/
public static final int INPUT_EVENTS_DELIVERED_AT_VSYNC = 1;
/**
* Opposite of INPUT_EVENTS_DELIVERED_AT_VSYNC.
*/
public static final int INPUT_EVENTS_DELIVERED_IMMEDIATELY = 0;
private static final String TAG = "ContentViewCore";
// Used to represent gestures for long press and long tap.
private static final int IS_LONG_PRESS = 1;
private static final int IS_LONG_TAP = 2;
// Length of the delay (in ms) before fading in handles after the last page movement.
private static final int TEXT_HANDLE_FADE_IN_DELAY = 300;
// 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 refernces. We put into the map on addJavaScriptInterface()
// and remove from it in removeJavaScriptInterface().
private final Map<String, Object> mJavaScriptInterfaces = new HashMap<String, Object>();
// 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>();
/**
* 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#drawChild(Canvas, View, long)
*/
boolean drawChild(Canvas canvas, View child, long drawingTime);
/**
* @see View#onKeyUp(keyCode, KeyEvent)
*/
boolean super_onKeyUp(int keyCode, KeyEvent event);
/**
* @see View#dispatchKeyEventPreIme(KeyEvent)
*/
boolean super_dispatchKeyEventPreIme(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#awakenScrollBars()
*/
boolean awakenScrollBars();
/**
* @see View#awakenScrollBars(int, boolean)
*/
boolean super_awakenScrollBars(int startDelay, boolean invalidate);
}
/**
* An interface that allows the embedder to be notified when the pinch gesture starts and
* stops.
*/
public interface GestureStateListener {
/**
* Called when the pinch gesture starts.
*/
void onPinchGestureStart();
/**
* Called when the pinch gesture ends.
*/
void onPinchGestureEnd();
/**
* Called when the fling gesture is sent.
*/
void onFlingStartGesture(int vx, int vy);
/**
* Called when the fling cancel gesture is sent.
*/
void onFlingCancelGesture();
/**
* Called when a fling event was not handled by the renderer.
*/
void onUnhandledFlingStartEvent();
}
/**
* 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 of changes to the parameters of the
* currently displayed contents.
* These notifications are consistent with respect to the UI thread (the size is the size of
* the contents currently displayed on screen).
*/
public interface UpdateFrameInfoListener {
/**
* Called each time any of the parameters are changed.
*
* @param pageScaleFactor The page scale.
*/
void onFrameInfoUpdated(float pageScaleFactor);
}
private VSyncManager.Provider mVSyncProvider;
private VSyncManager.Listener mVSyncListener;
private int mVSyncSubscriberCount;
private boolean mVSyncListenerRegistered;
// To avoid IPC delay we use input events to directly trigger a vsync signal in the renderer.
// When we do this, we also need to avoid sending the real vsync signal for the current
// frame to avoid double-ticking. This flag is used to inhibit the next vsync notification.
private boolean mDidSignalVSyncUsingInputEvent;
public VSyncManager.Listener getVSyncListener(VSyncManager.Provider vsyncProvider) {
if (mVSyncProvider != null && mVSyncListenerRegistered) {
mVSyncProvider.unregisterVSyncListener(mVSyncListener);
mVSyncListenerRegistered = false;
}
mVSyncProvider = vsyncProvider;
mVSyncListener = new VSyncManager.Listener() {
@Override
public void updateVSync(long tickTimeMicros, long intervalMicros) {
if (mNativeContentViewCore != 0) {
nativeUpdateVSyncParameters(mNativeContentViewCore, tickTimeMicros,
intervalMicros);
}
}
@Override
public void onVSync(long frameTimeMicros) {
animateIfNecessary(frameTimeMicros);
if (mDidSignalVSyncUsingInputEvent) {
TraceEvent.instant("ContentViewCore::onVSync ignored");
mDidSignalVSyncUsingInputEvent = false;
return;
}
if (mNativeContentViewCore != 0) {
nativeOnVSync(mNativeContentViewCore, frameTimeMicros);
}
}
};
if (mVSyncSubscriberCount > 0) {
// setVSyncNotificationEnabled(true) is called before getVSyncListener.
vsyncProvider.registerVSyncListener(mVSyncListener);
mVSyncListenerRegistered = true;
}
return mVSyncListener;
}
@CalledByNative
void setVSyncNotificationEnabled(boolean enabled) {
if (!isVSyncNotificationEnabled() && enabled) {
mDidSignalVSyncUsingInputEvent = false;
}
if (mVSyncProvider != null) {
if (!mVSyncListenerRegistered && enabled) {
mVSyncProvider.registerVSyncListener(mVSyncListener);
mVSyncListenerRegistered = true;
} else if (mVSyncSubscriberCount == 1 && !enabled) {
assert mVSyncListenerRegistered;
mVSyncProvider.unregisterVSyncListener(mVSyncListener);
mVSyncListenerRegistered = false;
}
}
mVSyncSubscriberCount += enabled ? 1 : -1;
assert mVSyncSubscriberCount >= 0;
}
@CalledByNative
private void resetVSyncNotification() {
while (isVSyncNotificationEnabled()) setVSyncNotificationEnabled(false);
mVSyncSubscriberCount = 0;
mVSyncListenerRegistered = false;
mNeedAnimate = false;
}
private boolean isVSyncNotificationEnabled() {
return mVSyncProvider != null && mVSyncListenerRegistered;
}
@CalledByNative
private void setNeedsAnimate() {
if (!mNeedAnimate) {
mNeedAnimate = true;
setVSyncNotificationEnabled(true);
}
}
private final Context mContext;
private ViewGroup mContainerView;
private InternalAccessDelegate mContainerViewInternals;
private WebContentsObserverAndroid mWebContentsObserver;
private ContentViewClient mContentViewClient;
private ContentSettings mContentSettings;
// Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit().
private int mNativeContentViewCore = 0;
private boolean mAttachedToWindow = false;
// Pid of the renderer process backing this ContentViewCore.
private int mPid = 0;
private ContentViewGestureHandler mContentViewGestureHandler;
private GestureStateListener mGestureStateListener;
private ZoomManager mZoomManager;
private ZoomControlsDelegate mZoomControlsDelegate;
private PopupZoomer mPopupZoomer;
private Runnable mFakeMouseMoveRunnable = null;
// Only valid when focused on a text / password field.
private ImeAdapter mImeAdapter;
private ImeAdapter.AdapterInputConnectionFactory mAdapterInputConnectionFactory;
private AdapterInputConnection mInputConnection;
private SelectionHandleController mSelectionHandleController;
private InsertionHandleController mInsertionHandleController;
private Runnable mDeferredHandleFadeInRunnable;
// Size of the viewport in physical pixels as set from onSizeChanged.
private int mViewportWidthPix;
private int mViewportHeightPix;
private int mPhysicalBackingWidthPix;
private int mPhysicalBackingHeightPix;
private int mOverdrawBottomHeightPix;
private int mViewportSizeOffsetWidthPix;
private int mViewportSizeOffsetHeightPix;
// Cached copy of all positions and scales as reported by the renderer.
private final RenderCoordinates mRenderCoordinates;
private final RenderCoordinates.NormalizedPoint mStartHandlePoint;
private final RenderCoordinates.NormalizedPoint mEndHandlePoint;
private final RenderCoordinates.NormalizedPoint mInsertionHandlePoint;
// Tracks whether a selection is currently active. When applied to selected text, indicates
// whether the last selected text is still highlighted.
private boolean mHasSelection;
private String mLastSelectedText;
private boolean mSelectionEditable;
private ActionMode mActionMode;
private boolean mUnselectAllOnActionModeDismiss;
// Delegate that will handle GET downloads, and be notified of completion of POST downloads.
private ContentViewDownloadDelegate mDownloadDelegate;
// The AccessibilityInjector that handles loading Accessibility scripts into the web page.
private AccessibilityInjector mAccessibilityInjector;
// 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;
// Allows us to dynamically respond when the accessibility script injection flag changes.
private ContentObserver mAccessibilityScriptInjectionObserver;
// Temporary notification to tell onSizeChanged to focus a form element,
// because the OSK was just brought up.
private boolean mUnfocusOnNextSizeChanged = false;
private final Rect mFocusPreOSKViewportRect = new Rect();
private boolean mNeedUpdateOrientationChanged;
// Used to keep track of whether we should try to undo the last zoom-to-textfield operation.
private boolean mScrolledAndZoomedFocusedEditableNode = false;
// Whether we use hardware-accelerated drawing.
private boolean mHardwareAccelerated = false;
// Whether we received a new frame since consumePendingRendererFrame() was last called.
private boolean mPendingRendererFrame = false;
// Whether we should animate at the next vsync tick.
private boolean mNeedAnimate = false;
private ViewAndroid mViewAndroid;
/**
* 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) {
mContext = context;
WeakContext.initializeWeakContext(context);
HeapStatsLogger.init(mContext.getApplicationContext());
mAdapterInputConnectionFactory = new AdapterInputConnectionFactory();
mRenderCoordinates = new RenderCoordinates();
mRenderCoordinates.setDeviceScaleFactor(
getContext().getResources().getDisplayMetrics().density);
mStartHandlePoint = mRenderCoordinates.createNormalizedPoint();
mEndHandlePoint = mRenderCoordinates.createNormalizedPoint();
mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint();
mAccessibilityManager = (AccessibilityManager)
getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
}
/**
* @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;
}
/**
* Specifies how much smaller the WebKit layout size should be relative to the size of this
* view.
* @param offsetXPix The X amount in pixels to shrink the viewport by.
* @param offsetYPix The Y amount in pixels to shrink the viewport by.
*/
public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
if (offsetXPix != mViewportSizeOffsetWidthPix ||
offsetYPix != mViewportSizeOffsetHeightPix) {
mViewportSizeOffsetWidthPix = offsetXPix;
mViewportSizeOffsetHeightPix = offsetYPix;
if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore);
}
}
/**
* Returns a delegate that can be used to add and remove views from the ContainerView.
*
* NOTE: Use with care, as not all ContentViewCore users setup their ContainerView in the same
* way. In particular, the Android WebView has limitations on what implementation details can
* be provided via a child view, as they are visible in the API and could introduce
* compatibility breaks with existing applications. If in doubt, contact the
* android_webview/OWNERS
*
* @return A ViewAndroidDelegate that can be used to add and remove views.
*/
@VisibleForTesting
public ViewAndroidDelegate getViewAndroidDelegate() {
return new ViewAndroidDelegate() {
@Override
public View acquireAnchorView() {
View anchorView = new View(getContext());
mContainerView.addView(anchorView);
return anchorView;
}
@Override
@SuppressWarnings("deprecation") // AbsoluteLayout.LayoutParams
public void setAnchorViewPosition(
View view, float x, float y, float width, float height) {
assert(view.getParent() == mContainerView);
float scale = (float) DeviceDisplayInfo.create(getContext()).getDIPScale();
// The anchor view should not go outside the bounds of the ContainerView.
int leftMargin = Math.round(x * scale);
int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
int scaledWidth = Math.round(width * scale);
// ContentViewCore currently only supports these two container view types.
if (mContainerView instanceof FrameLayout) {
if (scaledWidth + leftMargin > mContainerView.getWidth()) {
scaledWidth = mContainerView.getWidth() - leftMargin;
}
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
scaledWidth, Math.round(height * scale));
lp.leftMargin = leftMargin;
lp.topMargin = topMargin;
view.setLayoutParams(lp);
} else if (mContainerView instanceof AbsoluteLayout) {
// This fixes the offset due to a difference in
// scrolling model of WebView vs. Chrome.
// TODO(sgurun) fix this to use mContainerView.getScroll[X/Y]()
// as it naturally accounts for scroll differences between
// these models.
leftMargin += mRenderCoordinates.getScrollXPixInt();
topMargin += mRenderCoordinates.getScrollYPixInt();
android.widget.AbsoluteLayout.LayoutParams lp =
new android.widget.AbsoluteLayout.LayoutParams(
scaledWidth, (int)(height * scale), leftMargin, topMargin);
view.setLayoutParams(lp);
} else {
Log.e(TAG, "Unknown layout " + mContainerView.getClass().getName());
}
}
@Override
public void releaseAnchorView(View anchorView) {
mContainerView.removeView(anchorView);
}
};
}
@VisibleForTesting
public ImeAdapter getImeAdapterForTest() {
return mImeAdapter;
}
@VisibleForTesting
public void setAdapterInputConnectionFactory(AdapterInputConnectionFactory factory) {
mAdapterInputConnectionFactory = factory;
}
@VisibleForTesting
public AdapterInputConnection getInputConnectionForTest() {
return mInputConnection;
}
private ImeAdapter createImeAdapter(Context context) {
return new ImeAdapter(new InputMethodManagerWrapper(context),
new ImeAdapter.ImeAdapterDelegate() {
@Override
public void onImeEvent(boolean isFinish) {
getContentViewClient().onImeEvent();
if (!isFinish) {
hideHandles();
undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
}
}
@Override
public void onSetFieldValue() {
scrollFocusedEditableNodeIntoView();
}
@Override
public void onDismissInput() {
getContentViewClient().onImeStateChangeRequested(false);
}
@Override
public View getAttachedView() {
return mContainerView;
}
@Override
public ResultReceiver getNewShowKeyboardReceiver() {
return new ResultReceiver(new Handler()) {
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
getContentViewClient().onImeStateChangeRequested(
resultCode == InputMethodManager.RESULT_SHOWN ||
resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
if (resultCode == InputMethodManager.RESULT_SHOWN) {
// If OSK is newly shown, delay the form focus until
// the onSizeChanged (in order to adjust relative to the
// new size).
// TODO(jdduke): We should not assume that onSizeChanged will
// always be called, crbug.com/294908.
getContainerView().getWindowVisibleDisplayFrame(
mFocusPreOSKViewportRect);
} else if (resultCode ==
InputMethodManager.RESULT_UNCHANGED_SHOWN) {
// If the OSK was already there, focus the form immediately.
scrollFocusedEditableNodeIntoView();
} else {
undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
}
}
};
}
}
);
}
/**
* Returns true if the given Activity has hardware acceleration enabled
* in its manifest, or in its foreground window.
*
* TODO(husky): Remove when initialize() is refactored (see TODO there)
* TODO(dtrainor) This is still used by other classes. Make sure to pull some version of this
* out before removing it.
*/
public static boolean hasHardwareAcceleration(Activity activity) {
// Has HW acceleration been enabled manually in the current window?
Window window = activity.getWindow();
if (window != null) {
if ((window.getAttributes().flags
& WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
}
// Has HW acceleration been enabled in the manifest?
try {
ActivityInfo info = activity.getPackageManager().getActivityInfo(
activity.getComponentName(), 0);
if ((info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
return true;
}
} catch (PackageManager.NameNotFoundException e) {
Log.e("Chrome", "getActivityInfo(self) should not fail");
}
return false;
}
/**
* Returns true if the given Context is a HW-accelerated Activity.
*
* TODO(husky): Remove when initialize() is refactored (see TODO there)
*/
private static boolean hasHardwareAcceleration(Context context) {
if (context instanceof Activity) {
return hasHardwareAcceleration((Activity) context);
}
return false;
}
/**
*
* @param containerView The view that will act as a container for all views created by this.
* @param internalDispatcher Handles dispatching all hidden or super methods to the
* containerView.
* @param nativeWebContents A pointer to the native web contents.
* @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(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
int nativeWebContents, WindowAndroid windowAndroid,
int inputEventDeliveryMode) {
// Check whether to use hardware acceleration. This is a bit hacky, and
// only works if the Context is actually an Activity (as it is in the
// Chrome application).
//
// What we're doing here is checking whether the app has *requested*
// hardware acceleration by setting the appropriate flags. This does not
// necessarily mean we're going to *get* hardware acceleration -- that's
// up to the Android framework.
//
// TODO(husky): Once the native code has been updated so that the
// HW acceleration flag can be set dynamically (Grace is doing this),
// move this check into onAttachedToWindow(), where we can test for
// HW support directly.
mHardwareAccelerated = hasHardwareAcceleration(mContext);
mContainerView = containerView;
int windowNativePointer = windowAndroid != null ? windowAndroid.getNativePointer() : 0;
int viewAndroidNativePointer = 0;
if (windowNativePointer != 0) {
mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate());
viewAndroidNativePointer = mViewAndroid.getNativePointer();
}
mNativeContentViewCore = nativeInit(mHardwareAccelerated,
nativeWebContents, viewAndroidNativePointer, windowNativePointer);
mContentSettings = new ContentSettings(this, mNativeContentViewCore);
initializeContainerView(internalDispatcher, inputEventDeliveryMode);
mAccessibilityInjector = AccessibilityInjector.newInstance(this);
String contentDescription = "Web View";
if (R.string.accessibility_content_view == 0) {
Log.w(TAG, "Setting contentDescription to 'Web View' as no value was specified.");
} else {
contentDescription = mContext.getResources().getString(
R.string.accessibility_content_view);
}
mContainerView.setContentDescription(contentDescription);
mWebContentsObserver = new WebContentsObserverAndroid(this) {
@Override
public void didStartLoading(String url) {
hidePopupDialog();
resetGestureDetectors();
}
};
mPid = nativeGetCurrentRenderProcessId(mNativeContentViewCore);
}
@CalledByNative
void onNativeContentViewCoreDestroyed(int nativeContentViewCore) {
assert nativeContentViewCore == mNativeContentViewCore;
mNativeContentViewCore = 0;
}
/**
* Initializes the View that will contain all Views created by the ContentViewCore.
*
* @param internalDispatcher Handles dispatching all hidden or super methods to the
* containerView.
*/
private void initializeContainerView(InternalAccessDelegate internalDispatcher,
int inputEventDeliveryMode) {
TraceEvent.begin();
mContainerViewInternals = internalDispatcher;
mContainerView.setWillNotDraw(false);
mContainerView.setClickable(true);
mZoomManager = new ZoomManager(mContext, this);
mContentViewGestureHandler = new ContentViewGestureHandler(mContext, this, mZoomManager,
inputEventDeliveryMode);
mZoomControlsDelegate = new ZoomControlsDelegate() {
@Override
public void invokeZoomPicker() {}
@Override
public void dismissZoomPicker() {}
@Override
public void updateZoomControls() {}
};
mRenderCoordinates.reset();
initPopupZoomer(mContext);
mImeAdapter = createImeAdapter(mContext);
TraceEvent.end();
}
private void initPopupZoomer(Context context){
mPopupZoomer = new PopupZoomer(context);
mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() {
@Override
public void onPopupZoomerShown(final PopupZoomer zoomer) {
mContainerView.post(new Runnable() {
@Override
public void run() {
if (mContainerView.indexOfChild(zoomer) == -1) {
mContainerView.addView(zoomer);
} else {
assert false : "PopupZoomer should never be shown without being hidden";
}
}
});
}
@Override
public void onPopupZoomerHidden(final PopupZoomer zoomer) {
mContainerView.post(new Runnable() {
@Override
public void run() {
if (mContainerView.indexOfChild(zoomer) != -1) {
mContainerView.removeView(zoomer);
mContainerView.invalidate();
} else {
assert false : "PopupZoomer should never be hidden without being shown";
}
}
});
}
});
// 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() {
@Override
public boolean onSingleTap(View v, MotionEvent e) {
mContainerView.requestFocus();
if (mNativeContentViewCore != 0) {
nativeSingleTap(mNativeContentViewCore, e.getEventTime(),
e.getX(), e.getY(), true);
}
return true;
}
@Override
public boolean onLongPress(View v, MotionEvent e) {
if (mNativeContentViewCore != 0) {
nativeLongPress(mNativeContentViewCore, e.getEventTime(),
e.getX(), e.getY(), true);
}
return true;
}
};
mPopupZoomer.setOnTapListener(listener);
}
/**
* 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.
*/
public void destroy() {
if (mNativeContentViewCore != 0) {
nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore);
}
resetVSyncNotification();
mVSyncProvider = null;
if (mViewAndroid != null) mViewAndroid.destroy();
mNativeContentViewCore = 0;
mContentSettings = null;
mJavaScriptInterfaces.clear();
mRetainedJavaScriptObjects.clear();
unregisterAccessibilityContentObserver();
}
private void unregisterAccessibilityContentObserver() {
if (mAccessibilityScriptInjectionObserver == null) {
return;
}
getContext().getContentResolver().unregisterContentObserver(
mAccessibilityScriptInjectionObserver);
mAccessibilityScriptInjectionObserver = null;
}
/**
* 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;
}
/**
* This is only useful for passing over JNI to native code that requires ContentViewCore*.
* @return native ContentViewCore pointer.
*/
@CalledByNative
public int getNativeContentViewCore() {
return mNativeContentViewCore;
}
/**
* For internal use. Throws IllegalStateException if mNativeContentView is 0.
* Use this to ensure we get a useful Java stack trace, rather than a native
* crash dump, from use-after-destroy bugs in Java code.
*/
void checkIsAlive() throws IllegalStateException {
if (!isAlive()) {
throw new IllegalStateException("ContentView used after destroy() was called");
}
}
public void setContentViewClient(ContentViewClient client) {
if (client == null) {
throw new IllegalArgumentException("The client can't be null.");
}
mContentViewClient = client;
}
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;
}
public int getBackgroundColor() {
if (mNativeContentViewCore != 0) {
return nativeGetBackgroundColor(mNativeContentViewCore);
}
return Color.WHITE;
}
@CalledByNative
private void onBackgroundColorChanged(int color) {
getContentViewClient().onBackgroundColorChanged(color);
}
/**
* Load url without fixing up the url string. Consumers of ContentView are responsible for
* ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left
* off during user input).
*
* @param params Parameters for this load.
*/
public void loadUrl(LoadUrlParams params) {
if (mNativeContentViewCore == 0) return;
nativeLoadUrl(mNativeContentViewCore,
params.mUrl,
params.mLoadUrlType,
params.mTransitionType,
params.mUaOverrideOption,
params.getExtraHeadersString(),
params.mPostData,
params.mBaseUrlForDataUrl,
params.mVirtualUrlForDataUrl,
params.mCanLoadLocalResources);
}
/**
* Stops loading the current web contents.
*/
public void stopLoading() {
if (mNativeContentViewCore != 0) nativeStopLoading(mNativeContentViewCore);
}
/**
* Get the URL of the current page.
*
* @return The URL of the current page.
*/
public String getUrl() {
if (mNativeContentViewCore != 0) return nativeGetURL(mNativeContentViewCore);
return null;
}
/**
* Get the title of the current page.
*
* @return The title of the current page.
*/
public String getTitle() {
if (mNativeContentViewCore != 0) return nativeGetTitle(mNativeContentViewCore);
return null;
}
/**
* Shows an interstitial page driven by the passed in delegate.
*
* @param url The URL being blocked by the interstitial.
* @param delegate The delegate handling the interstitial.
*/
@VisibleForTesting
public void showInterstitialPage(
String url, InterstitialPageDelegateAndroid delegate) {
if (mNativeContentViewCore == 0) return;
nativeShowInterstitialPage(mNativeContentViewCore, url, delegate.getNative());
}
/**
* @return Whether the page is currently showing an interstitial, such as a bad HTTPS page.
*/
public boolean isShowingInterstitialPage() {
return mNativeContentViewCore == 0 ?
false : nativeIsShowingInterstitialPage(mNativeContentViewCore);
}
/**
* Mark any new frames that have arrived since this function was last called as non-pending.
*
* @return Whether there was a pending frame from the renderer.
*/
public boolean consumePendingRendererFrame() {
boolean hadPendingFrame = mPendingRendererFrame;
mPendingRendererFrame = false;
return hadPendingFrame;
}
/**
* @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 Width of underlying physical surface.
*/
@CalledByNative
public int getPhysicalBackingWidthPix() { return mPhysicalBackingWidthPix; }
/**
* @return Height of underlying physical surface.
*/
@CalledByNative
public int getPhysicalBackingHeightPix() { return mPhysicalBackingHeightPix; }
/**
* @return Amount the output surface extends past the bottom of the window viewport.
*/
@CalledByNative
public int getOverdrawBottomHeightPix() { return mOverdrawBottomHeightPix; }
/**
* @return The amount to shrink the viewport relative to {@link #getViewportWidthPix()}.
*/
@CalledByNative
public int getViewportSizeOffsetWidthPix() { return mViewportSizeOffsetWidthPix; }
/**
* @return The amount to shrink the viewport relative to {@link #getViewportHeightPix()}.
*/
@CalledByNative
public int getViewportSizeOffsetHeightPix() { return mViewportSizeOffsetHeightPix; }
/**
* @see android.webkit.WebView#getContentHeight()
*/
public float getContentHeightCss() {
return mRenderCoordinates.getContentHeightCss();
}
/**
* @see android.webkit.WebView#getContentWidth()
*/
public float getContentWidthCss() {
return mRenderCoordinates.getContentWidthCss();
}
public Bitmap getBitmap() {
return getBitmap(getViewportWidthPix(), getViewportHeightPix());
}
public Bitmap getBitmap(int width, int height) {
if (width == 0 || height == 0
|| getViewportWidthPix() == 0 || getViewportHeightPix() == 0) {
return null;
}
Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
if (mNativeContentViewCore != 0 &&
nativePopulateBitmapFromCompositor(mNativeContentViewCore, b)) {
// If we successfully grabbed a bitmap, check if we have to draw the Android overlay
// components as well.
if (mContainerView.getChildCount() > 0) {
Canvas c = new Canvas(b);
c.scale(width / (float) getViewportWidthPix(),
height / (float) getViewportHeightPix());
mContainerView.draw(c);
}
return b;
}
return null;
}
/**
* Generates a bitmap of the content that is performance optimized based on capture time.
*
* <p>
* To have a consistent capture time across devices, we will scale down the captured bitmap
* where necessary to reduce the time to generate the bitmap.
*
* @param width The width of the content to be captured.
* @param height The height of the content to be captured.
* @return A pair of the generated bitmap, and the scale that needs to be applied to return the
* bitmap to it's original size (i.e. if the bitmap is scaled down 50%, this
* will be 2).
*/
public Pair<Bitmap, Float> getScaledPerformanceOptimizedBitmap(int width, int height) {
float scale = 1f;
// On tablets, always scale down to MDPI for performance reasons.
if (DeviceUtils.isTablet(getContext())) {
scale = getContext().getResources().getDisplayMetrics().density;
}
return Pair.create(
getBitmap((int) (width / scale), (int) (height / scale)),
scale);
}
/**
* @return Whether the current WebContents has a previous navigation entry.
*/
public boolean canGoBack() {
return mNativeContentViewCore != 0 && nativeCanGoBack(mNativeContentViewCore);
}
/**
* @return Whether the current WebContents has a navigation entry after the current one.
*/
public boolean canGoForward() {
return mNativeContentViewCore != 0 && nativeCanGoForward(mNativeContentViewCore);
}
/**
* @param offset The offset into the navigation history.
* @return Whether we can move in history by given offset
*/
public boolean canGoToOffset(int offset) {
return mNativeContentViewCore != 0 && nativeCanGoToOffset(mNativeContentViewCore, offset);
}
/**
* Navigates to the specified offset from the "current entry". Does nothing if the offset is out
* of bounds.
* @param offset The offset into the navigation history.
*/
public void goToOffset(int offset) {
if (mNativeContentViewCore != 0) nativeGoToOffset(mNativeContentViewCore, offset);
}
@Override
public void goToNavigationIndex(int index) {
if (mNativeContentViewCore != 0) nativeGoToNavigationIndex(mNativeContentViewCore, index);
}
/**
* Goes to the navigation entry before the current one.
*/
public void goBack() {
if (mNativeContentViewCore != 0) nativeGoBack(mNativeContentViewCore);
}
/**
* Goes to the navigation entry following the current one.
*/
public void goForward() {
if (mNativeContentViewCore != 0) nativeGoForward(mNativeContentViewCore);
}
/**
* Reload the current page.
*/
public void reload() {
mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
if (mNativeContentViewCore != 0) nativeReload(mNativeContentViewCore);
}
/**
* Cancel the pending reload.
*/
public void cancelPendingReload() {
if (mNativeContentViewCore != 0) nativeCancelPendingReload(mNativeContentViewCore);
}
/**
* Continue the pending reload.
*/
public void continuePendingReload() {
if (mNativeContentViewCore != 0) nativeContinuePendingReload(mNativeContentViewCore);
}
/**
* Clears the ContentViewCore's page history in both the backwards and
* forwards directions.
*/
public void clearHistory() {
if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore);
}
/**
* @return The selected text (empty if no text selected).
*/
public String getSelectedText() {
return mHasSelection ? mLastSelectedText : "";
}
/**
* @return Whether the current selection is editable (false if no text selected).
*/
public boolean isSelectionEditable() {
return mHasSelection ? mSelectionEditable : false;
}
// End FrameLayout overrides.
/**
* @see View#onTouchEvent(MotionEvent)
*/
public boolean onTouchEvent(MotionEvent event) {
undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
return mContentViewGestureHandler.onTouchEvent(event);
}
/**
* @return ContentViewGestureHandler for all MotionEvent and gesture related calls.
*/
ContentViewGestureHandler getContentViewGestureHandler() {
return mContentViewGestureHandler;
}
@Override
public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) {
if (mNativeContentViewCore != 0) {
return nativeSendTouchEvent(mNativeContentViewCore, timeMs, action, pts);
}
return false;
}
@SuppressWarnings("unused")
@CalledByNative
private void hasTouchEventHandlers(boolean hasTouchHandlers) {
mContentViewGestureHandler.hasTouchEventHandlers(hasTouchHandlers);
}
@SuppressWarnings("unused")
@CalledByNative
private void confirmTouchEvent(int ackResult) {
mContentViewGestureHandler.confirmTouchEvent(ackResult);
}
@SuppressWarnings("unused")
@CalledByNative
private void unhandledFlingStartEvent() {
if (mGestureStateListener != null) {
mGestureStateListener.onUnhandledFlingStartEvent();
}
}
@Override
public boolean sendGesture(int type, long timeMs, int x, int y, boolean lastInputEventForVSync,
Bundle b) {
if (offerGestureToEmbedder(type)) return false;
if (mNativeContentViewCore == 0) return false;
updateTextHandlesForGesture(type);
updateGestureStateListener(type, b);
if (lastInputEventForVSync && isVSyncNotificationEnabled()) {
assert type == ContentViewGestureHandler.GESTURE_SCROLL_BY ||
type == ContentViewGestureHandler.GESTURE_PINCH_BY;
mDidSignalVSyncUsingInputEvent = true;
}
switch (type) {
case ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE:
nativeShowPressState(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_SHOW_PRESS_CANCEL:
nativeShowPressCancel(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_DOUBLE_TAP:
nativeDoubleTap(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UP:
nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false);
return true;
case ContentViewGestureHandler.GESTURE_SINGLE_TAP_CONFIRMED:
handleTapOrPress(timeMs, x, y, 0,
b.getBoolean(ContentViewGestureHandler.SHOW_PRESS, false));
return true;
case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UNCONFIRMED:
nativeSingleTapUnconfirmed(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_LONG_PRESS:
handleTapOrPress(timeMs, x, y, IS_LONG_PRESS, false);
return true;
case ContentViewGestureHandler.GESTURE_LONG_TAP:
handleTapOrPress(timeMs, x, y, IS_LONG_TAP, false);
return true;
case ContentViewGestureHandler.GESTURE_SCROLL_START:
nativeScrollBegin(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_SCROLL_BY: {
int dx = b.getInt(ContentViewGestureHandler.DISTANCE_X);
int dy = b.getInt(ContentViewGestureHandler.DISTANCE_Y);
nativeScrollBy(mNativeContentViewCore, timeMs, x, y, dx, dy,
lastInputEventForVSync);
return true;
}
case ContentViewGestureHandler.GESTURE_SCROLL_END:
nativeScrollEnd(mNativeContentViewCore, timeMs);
return true;
case ContentViewGestureHandler.GESTURE_FLING_START:
nativeFlingStart(mNativeContentViewCore, timeMs, x, y,
b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
return true;
case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
nativeFlingCancel(mNativeContentViewCore, timeMs);
return true;
case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
nativePinchBegin(mNativeContentViewCore, timeMs, x, y);
return true;
case ContentViewGestureHandler.GESTURE_PINCH_BY:
nativePinchBy(mNativeContentViewCore, timeMs, x, y,
b.getFloat(ContentViewGestureHandler.DELTA, 0),
lastInputEventForVSync);
return true;
case ContentViewGestureHandler.GESTURE_PINCH_END:
nativePinchEnd(mNativeContentViewCore, timeMs);
return true;
default:
return false;
}
}
public void setGestureStateListener(GestureStateListener pinchGestureStateListener) {
mGestureStateListener = pinchGestureStateListener;
}
void updateGestureStateListener(int gestureType, Bundle b) {
if (mGestureStateListener == null) return;
switch (gestureType) {
case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
mGestureStateListener.onPinchGestureStart();
break;
case ContentViewGestureHandler.GESTURE_PINCH_END:
mGestureStateListener.onPinchGestureEnd();
break;
case ContentViewGestureHandler.GESTURE_FLING_START:
mGestureStateListener.onFlingStartGesture(
b.getInt(ContentViewGestureHandler.VELOCITY_X, 0),
b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0));
break;
case ContentViewGestureHandler.GESTURE_FLING_CANCEL:
mGestureStateListener.onFlingCancelGesture();
break;
default:
break;
}
}
public interface JavaScriptCallback {
void handleJavaScriptResult(String jsonResult);
}
/**
* Injects the passed Javascript code in the current page and evaluates it.
* If a result is required, pass in a callback.
* Used in automation tests.
*
* @param script The Javascript to execute.
* @param callback The callback to be fired off when a result is ready. The script's
* result will be json encoded and passed as the parameter, and the call
* will be made on the main thread.
* If no result is required, pass null.
*/
public void evaluateJavaScript(String script, JavaScriptCallback callback) {
if (mNativeContentViewCore == 0) return;
nativeEvaluateJavaScript(mNativeContentViewCore, script, callback, false);
}
/**
* Injects the passed Javascript code in the current page and evaluates it.
* If there is no page existing, a new one will be created.
*
* @param script The Javascript to execute.
*/
public void evaluateJavaScriptEvenIfNotYetNavigated(String script) {
if (mNativeContentViewCore == 0) return;
nativeEvaluateJavaScript(mNativeContentViewCore, script, null, true);
}
/**
* This method should be called when the containing activity is paused.
*/
public void onActivityPause() {
TraceEvent.begin();
hidePopupDialog();
nativeOnHide(mNativeContentViewCore);
TraceEvent.end();
}
/**
* This method should be called when the containing activity is resumed.
*/
public void onActivityResume() {
nativeOnShow(mNativeContentViewCore);
setAccessibilityState(mAccessibilityManager.isEnabled());
}
/**
* To be called when the ContentView is shown.
*/
public void onShow() {
nativeOnShow(mNativeContentViewCore);
setAccessibilityState(mAccessibilityManager.isEnabled());
}
/**
* To be called when the ContentView is hidden.
*/
public void onHide() {
hidePopupDialog();
setInjectedAccessibility(false);
nativeOnHide(mNativeContentViewCore);
}
/**
* Return the ContentSettings object used to retrieve the settings for this
* ContentViewCore. For modifications, ChromeNativePreferences is to be used.
* @return A ContentSettings object that can be used to retrieve this
* ContentViewCore's settings.
*/
public ContentSettings getContentSettings() {
return mContentSettings;
}
@Override
public boolean didUIStealScroll(float x, float y) {
return getContentViewClient().shouldOverrideScroll(
x, y, computeHorizontalScrollOffset(), computeVerticalScrollOffset());
}
@Override
public boolean hasFixedPageScale() {
return mRenderCoordinates.hasFixedPageScale();
}
private void hidePopupDialog() {
SelectPopupDialog.hide(this);
hideHandles();
hideSelectActionBar();
}
void hideSelectActionBar() {
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
}
}
public boolean isSelectActionBarShowing() {
return mActionMode != null;
}
private void resetGestureDetectors() {
mContentViewGestureHandler.resetGestureHandlers();
}
/**
* @see View#onAttachedToWindow()
*/
@SuppressWarnings("javadoc")
public void onAttachedToWindow() {
mAttachedToWindow = true;
if (mNativeContentViewCore != 0) {
assert mPid == nativeGetCurrentRenderProcessId(mNativeContentViewCore);
ChildProcessLauncher.bindAsHighPriority(mPid);
// Normally the initial binding is removed in onRenderProcessSwap(), but it is possible
// to construct WebContents and spawn the renderer before passing it to ContentViewCore.
// In this case there will be no onRenderProcessSwap() call and the initial binding will
// be removed here.
ChildProcessLauncher.removeInitialBinding(mPid);
}
setAccessibilityState(mAccessibilityManager.isEnabled());
}
/**
* @see View#onDetachedFromWindow()
*/
@SuppressWarnings("javadoc")
public void onDetachedFromWindow() {
mAttachedToWindow = false;
if (mNativeContentViewCore != 0) {
assert mPid == nativeGetCurrentRenderProcessId(mNativeContentViewCore);
ChildProcessLauncher.unbindAsHighPriority(mPid);
}
setInjectedAccessibility(false);
hidePopupDialog();
mZoomControlsDelegate.dismissZoomPicker();
unregisterAccessibilityContentObserver();
}
/**
* @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) {
if (!mImeAdapter.hasTextInputType()) {
// Although onCheckIsTextEditor will return false in this case, the EditorInfo
// is still used by the InputMethodService. Need to make sure the IME doesn't
// enter fullscreen mode.
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
}
mInputConnection =
mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter, outAttrs);
return mInputConnection;
}
public Editable getEditableForTest() {
return mInputConnection.getEditable();
}
/**
* @see View#onCheckIsTextEditor()
*/
public boolean onCheckIsTextEditor() {
return mImeAdapter.hasTextInputType();
}
/**
* @see View#onConfigurationChanged(Configuration)
*/
@SuppressWarnings("javadoc")
public void onConfigurationChanged(Configuration newConfig) {
TraceEvent.begin();
if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
ImeAdapter.getTextInputTypeNone(),
AdapterInputConnection.INVALID_SELECTION,
AdapterInputConnection.INVALID_SELECTION);
InputMethodManager manager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
manager.restartInput(mContainerView);
}
mContainerViewInternals.super_onConfigurationChanged(newConfig);
mNeedUpdateOrientationChanged = true;
TraceEvent.end();
}
/**
* @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);
}
}
/**
* Called when the amount the surface is overdrawing off the bottom has changed.
* @param overdrawHeightPix The overdraw height.
*/
public void onOverdrawBottomHeightChanged(int overdrawHeightPix) {
if (mOverdrawBottomHeightPix == overdrawHeightPix) return;
mOverdrawBottomHeightPix = overdrawHeightPix;
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()) {
scrollFocusedEditableNodeIntoView();
}
mFocusPreOSKViewportRect.setEmpty();
}
} else if (mUnfocusOnNextSizeChanged) {
undoScrollFocusedEditableNodeIntoViewIfNeeded(true);
mUnfocusOnNextSizeChanged = false;
}
if (mNeedUpdateOrientationChanged) {
sendOrientationChangeEvent();
mNeedUpdateOrientationChanged = false;
}
}
private void scrollFocusedEditableNodeIntoView() {
if (mNativeContentViewCore != 0) {
Runnable scrollTask = new Runnable() {
@Override
public void run() {
if (mNativeContentViewCore != 0) {
nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
}
}
};
scrollTask.run();
// The native side keeps track of whether the zoom and scroll actually occurred. It is
// more efficient to do it this way and sometimes fire an unnecessary message rather
// than synchronize with the renderer and always have an additional message.
mScrolledAndZoomedFocusedEditableNode = true;
}
}
private void undoScrollFocusedEditableNodeIntoViewIfNeeded(boolean backButtonPressed) {
// The only call to this function that matters is the first call after the
// scrollFocusedEditableNodeIntoView function call.
// If the first call to this function is a result of a back button press we want to undo the
// preceding scroll. If the call is a result of some other action we don't want to perform
// an undo.
// All subsequent calls are ignored since only the scroll function sets
// mScrolledAndZoomedFocusedEditableNode to true.
if (mScrolledAndZoomedFocusedEditableNode && backButtonPressed &&
mNativeContentViewCore != 0) {
Runnable scrollTask = new Runnable() {
@Override
public void run() {
if (mNativeContentViewCore != 0) {
nativeUndoScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
}
}
};
scrollTask.run();
}
mScrolledAndZoomedFocusedEditableNode = false;
}
/**
* @see View#onWindowFocusChanged(boolean)
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
if (!hasWindowFocus) {
mContentViewGestureHandler.onWindowFocusLost();
}
}
public void onFocusChanged(boolean gainFocus) {
if (!gainFocus) getContentViewClient().onImeStateChangeRequested(false);
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#dispatchKeyEventPreIme(KeyEvent)
*/
public boolean dispatchKeyEventPreIme(KeyEvent event) {
try {
TraceEvent.begin();
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && mImeAdapter.isActive()) {
mUnfocusOnNextSizeChanged = true;
} else {
undoScrollFocusedEditableNodeIntoViewIfNeeded(false);
}
return mContainerViewInternals.super_dispatchKeyEventPreIme(event);
} finally {
TraceEvent.end();
}
}
/**
* @see View#dispatchKeyEvent(KeyEvent)
*/
public boolean dispatchKeyEvent(KeyEvent event) {
if (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");
mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
if (mBrowserAccessibilityManager != null) {
return mBrowserAccessibilityManager.onHoverEvent(event);
}
if (mNativeContentViewCore != 0) {
nativeSendMouseMoveEvent(mNativeContentViewCore, event.getEventTime(),
event.getX(), event.getY());
}
TraceEvent.end("onHoverEvent");
return true;
}
/**
* @see View#onGenericMotionEvent(MotionEvent)
*/
public boolean onGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL:
nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(),
event.getX(), event.getY(),
event.getAxisValue(MotionEvent.AXIS_VSCROLL));
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);
}
};
mContainerView.postDelayed(mFakeMouseMoveRunnable, 250);
return true;
}
}
return mContainerViewInternals.super_onGenericMotionEvent(event);
}
/**
* @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(int xPix, int yPix) {
if (mNativeContentViewCore != 0) {
nativeScrollBy(mNativeContentViewCore,
System.currentTimeMillis(), 0, 0, xPix, yPix, false);
}
}
/**
* @see View#scrollTo(int, int)
*/
public void scrollTo(int xPix, int 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;
if (dxPix != 0 || dyPix != 0) {
long time = System.currentTimeMillis();
nativeScrollBegin(mNativeContentViewCore, time, xCurrentPix, yCurrentPix);
nativeScrollBy(mNativeContentViewCore,
time, xCurrentPix, yCurrentPix, dxPix, dyPix, false);
nativeScrollEnd(mNativeContentViewCore, time);
}
}
// 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);
}
}
/**
* Called by native side when the corresponding renderer crashes. Note that if a renderer is
* shared between tabs, this might be called multiple times while the tab is crashed. This is
* because the tabs sharing a renderer also share RenderProcessHost. When one of those tabs
* reloads and a new renderer is created for the shared RenderProcessHost, all tabs are notified
* in onRenderProcessSwap(), not only the one that reloads. If this renderer dies, all the other
* dead tabs are notified again.
* @param alreadyCrashed true iff this tab is already in crashed state but the shared renderer
* resurrected and died again since the last time this was called.
*/
@SuppressWarnings("unused")
@CalledByNative
private void onTabCrash(boolean alreadyCrashed) {
assert mPid != 0;
if (!alreadyCrashed) {
getContentViewClient().onRendererCrash(ChildProcessLauncher.isOomProtected(mPid));
}
mPid = 0;
}
private void handleTapOrPress(
long timeMs, float xPix, float yPix, int isLongPressOrTap, boolean showPress) {
if (mContainerView.isFocusable() && mContainerView.isFocusableInTouchMode()
&& !mContainerView.isFocused()) {
mContainerView.requestFocus();
}
if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix);
if (isLongPressOrTap == IS_LONG_PRESS) {
getInsertionHandleController().allowAutomaticShowing();
getSelectionHandleController().allowAutomaticShowing();
if (mNativeContentViewCore != 0) {
nativeLongPress(mNativeContentViewCore, timeMs, xPix, yPix, false);
}
} else if (isLongPressOrTap == IS_LONG_TAP) {
getInsertionHandleController().allowAutomaticShowing();
getSelectionHandleController().allowAutomaticShowing();
if (mNativeContentViewCore != 0) {
nativeLongTap(mNativeContentViewCore, timeMs, xPix, yPix, false);
}
} else {
if (!showPress && mNativeContentViewCore != 0) {
nativeShowPressState(mNativeContentViewCore, timeMs, xPix, yPix);
}
if (mSelectionEditable) getInsertionHandleController().allowAutomaticShowing();
if (mNativeContentViewCore != 0) {
nativeSingleTap(mNativeContentViewCore, timeMs, xPix, yPix, false);
}
}
}
public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) {
mZoomControlsDelegate = zoomControlsDelegate;
}
public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) {
mZoomManager.updateMultiTouchSupport(supportsMultiTouchZoom);
}
public void updateDoubleTapDragSupport(boolean supportsDoubleTapDrag) {
mContentViewGestureHandler.updateDoubleTapDragSupport(supportsDoubleTapDrag);
}
public void selectPopupMenuItems(int[] indices) {
if (mNativeContentViewCore != 0) {
nativeSelectPopupMenuItems(mNativeContentViewCore, indices);
}
}
/**
* Get the screen orientation from the OS and push it to WebKit.
*
* TODO(husky): Add a hook for mock orientations.
*
* TODO(husky): Currently each new tab starts with an orientation of 0 until you actually
* rotate the device. This is wrong if you actually started in landscape mode. To fix this, we
* need to push the correct orientation, but only after WebKit's Frame object has been fully
* initialized. Need to find a good time to do that. onPageFinished() would probably work but
* it isn't implemented yet.
*/
private void sendOrientationChangeEvent() {
if (mNativeContentViewCore == 0) return;
WindowManager windowManager =
(WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
switch (windowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_90:
nativeSendOrientationChangeEvent(mNativeContentViewCore, 90);
break;
case Surface.ROTATION_180:
nativeSendOrientationChangeEvent(mNativeContentViewCore, 180);
break;
case Surface.ROTATION_270:
nativeSendOrientationChangeEvent(mNativeContentViewCore, -90);
break;
case Surface.ROTATION_0:
nativeSendOrientationChangeEvent(mNativeContentViewCore, 0);
break;
default:
Log.w(TAG, "Unknown rotation!");
break;
}
}
/**
* Register the delegate to be used when content can not be handled by
* the rendering engine, and should be downloaded instead. This will replace
* the current delegate, if any.
* @param delegate An implementation of ContentViewDownloadDelegate.
*/
public void setDownloadDelegate(ContentViewDownloadDelegate delegate) {
mDownloadDelegate = delegate;
}
// Called by DownloadController.
ContentViewDownloadDelegate getDownloadDelegate() {
return mDownloadDelegate;
}
private SelectionHandleController getSelectionHandleController() {
if (mSelectionHandleController == null) {
mSelectionHandleController = new SelectionHandleController(getContainerView()) {
@Override
public void selectBetweenCoordinates(int x1, int y1, int x2, int y2) {
if (mNativeContentViewCore != 0 && !(x1 == x2 && y1 == y2)) {
nativeSelectBetweenCoordinates(mNativeContentViewCore,
x1, y1 - mRenderCoordinates.getContentOffsetYPix(),
x2, y2 - mRenderCoordinates.getContentOffsetYPix());
}
}
@Override
public void showHandles(int startDir, int endDir) {
super.showHandles(startDir, endDir);
showSelectActionBar();
}
};
mSelectionHandleController.hideAndDisallowAutomaticShowing();
}
return mSelectionHandleController;
}
private InsertionHandleController getInsertionHandleController() {
if (mInsertionHandleController == null) {
mInsertionHandleController = new InsertionHandleController(getContainerView()) {
private static final int AVERAGE_LINE_HEIGHT = 14;
@Override
public void setCursorPosition(int x, int y) {
if (mNativeContentViewCore != 0) {
nativeMoveCaret(mNativeContentViewCore,
x, y - mRenderCoordinates.getContentOffsetYPix());
}
}
@Override
public void paste() {
mImeAdapter.paste();
hideHandles();
}
@Override
public int getLineHeight() {
return (int) Math.ceil(
mRenderCoordinates.fromLocalCssToPix(AVERAGE_LINE_HEIGHT));
}
@Override
public void showHandle() {
super.showHandle();
}
};
mInsertionHandleController.hideAndDisallowAutomaticShowing();
}
return mInsertionHandleController;
}
@VisibleForTesting
public InsertionHandleController getInsertionHandleControllerForTest() {
return mInsertionHandleController;
}
@VisibleForTesting
public SelectionHandleController getSelectionHandleControllerForTest() {
return mSelectionHandleController;
}
private void updateHandleScreenPositions() {
if (isSelectionHandleShowing()) {
mSelectionHandleController.setStartHandlePosition(
mStartHandlePoint.getXPix(), mStartHandlePoint.getYPix());
mSelectionHandleController.setEndHandlePosition(
mEndHandlePoint.getXPix(), mEndHandlePoint.getYPix());
}
if (isInsertionHandleShowing()) {
mInsertionHandleController.setHandlePosition(
mInsertionHandlePoint.getXPix(), mInsertionHandlePoint.getYPix());
}
}
private void hideHandles() {
if (mSelectionHandleController != null) {
mSelectionHandleController.hideAndDisallowAutomaticShowing();
}
if (mInsertionHandleController != null) {
mInsertionHandleController.hideAndDisallowAutomaticShowing();
}
}
private void showSelectActionBar() {
if (mActionMode != null) {
mActionMode.invalidate();
return;
}
// Start a new action mode with a SelectActionModeCallback.
SelectActionModeCallback.ActionHandler actionHandler =
new SelectActionModeCallback.ActionHandler() {
@Override
public boolean selectAll() {
return mImeAdapter.selectAll();
}
@Override
public boolean cut() {
return mImeAdapter.cut();
}
@Override
public boolean copy() {
return mImeAdapter.copy();
}
@Override
public boolean paste() {
return mImeAdapter.paste();
}
@Override
public boolean isSelectionEditable() {
return mSelectionEditable;
}
@Override
public String getSelectedText() {
return ContentViewCore.this.getSelectedText();
}
@Override
public void onDestroyActionMode() {
mActionMode = null;
if (mUnselectAllOnActionModeDismiss) mImeAdapter.unselect();
getContentViewClient().onContextualActionBarHidden();
}
};
mActionMode = null;
// On ICS, startActionMode throws an NPE when getParent() is null.
if (mContainerView.getParent() != null) {
mActionMode = mContainerView.startActionMode(
getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
nativeIsIncognito(mNativeContentViewCore)));
}
mUnselectAllOnActionModeDismiss = true;
if (mActionMode == null) {
// There is no ActionMode, so remove the selection.
mImeAdapter.unselect();
} else {
getContentViewClient().onContextualActionBarShown();
}
}
public boolean getUseDesktopUserAgent() {
if (mNativeContentViewCore != 0) {
return nativeGetUseDesktopUserAgent(mNativeContentViewCore);
}
return false;
}
/**
* Set whether or not we're using a desktop user agent for the currently loaded page.
* @param override If true, use a desktop user agent. Use a mobile one otherwise.
* @param reloadOnChange Reload the page if the UA has changed.
*/
public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {
if (mNativeContentViewCore != 0) {
nativeSetUseDesktopUserAgent(mNativeContentViewCore, override, reloadOnChange);
}
}
public void clearSslPreferences() {
nativeClearSslPreferences(mNativeContentViewCore);
}
/**
* @return Whether the native ContentView has crashed.
*/
public boolean isCrashed() {
if (mNativeContentViewCore == 0) return false;
return nativeCrashed(mNativeContentViewCore);
}
private boolean isSelectionHandleShowing() {
return mSelectionHandleController != null && mSelectionHandleController.isShowing();
}
private boolean isInsertionHandleShowing() {
return mInsertionHandleController != null && mInsertionHandleController.isShowing();
}
private void updateTextHandlesForGesture(int type) {
switch(type) {
case ContentViewGestureHandler.GESTURE_DOUBLE_TAP:
case ContentViewGestureHandler.GESTURE_SCROLL_START:
case ContentViewGestureHandler.GESTURE_FLING_START:
case ContentViewGestureHandler.GESTURE_PINCH_BEGIN:
temporarilyHideTextHandles();
break;
default:
break;
}
}
// Makes the insertion/selection handles invisible. They will fade back in shortly after the
// last call to scheduleTextHandleFadeIn (or temporarilyHideTextHandles).
private void temporarilyHideTextHandles() {
if (isSelectionHandleShowing()) {
mSelectionHandleController.setHandleVisibility(HandleView.INVISIBLE);
}
if (isInsertionHandleShowing()) {
mInsertionHandleController.setHandleVisibility(HandleView.INVISIBLE);
}
scheduleTextHandleFadeIn();
}
private boolean allowTextHandleFadeIn() {
if (mContentViewGestureHandler.isNativeScrolling() ||
mContentViewGestureHandler.isNativePinching()) {
return false;
}
if (mPopupZoomer.isShowing()) return false;
return true;
}
// Cancels any pending fade in and schedules a new one.
private void scheduleTextHandleFadeIn() {
if (!isInsertionHandleShowing() && !isSelectionHandleShowing()) return;
if (mDeferredHandleFadeInRunnable == null) {
mDeferredHandleFadeInRunnable = new Runnable() {
@Override
public void run() {
if (!allowTextHandleFadeIn()) {
// Delay fade in until it is allowed.
scheduleTextHandleFadeIn();
} else {
if (isSelectionHandleShowing()) {
mSelectionHandleController.beginHandleFadeIn();
}
if (isInsertionHandleShowing()) {
mInsertionHandleController.beginHandleFadeIn();
}
}
}
};
}
mContainerView.removeCallbacks(mDeferredHandleFadeInRunnable);
mContainerView.postDelayed(mDeferredHandleFadeInRunnable, TEXT_HANDLE_FADE_IN_DELAY);
}
/**
* Shows the IME if the focused widget could accept text input.
*/
public void showImeIfNeeded() {
if (mNativeContentViewCore != 0) nativeShowImeIfNeeded(mNativeContentViewCore);
}
@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 controlsOffsetYCss, float contentOffsetYCss,
float overdrawBottomHeightCss) {
TraceEvent.instant("ContentViewCore:updateFrameInfo");
// Adjust contentWidth/Height to be always at least as big as
// the actual viewport (as set by onSizeChanged).
contentWidth = Math.max(contentWidth,
mRenderCoordinates.fromPixToLocalCss(mViewportWidthPix));
contentHeight = Math.max(contentHeight,
mRenderCoordinates.fromPixToLocalCss(mViewportHeightPix));
final float contentOffsetYPix = mRenderCoordinates.fromDipToPix(contentOffsetYCss);
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 contentOffsetChanged =
contentOffsetYPix != mRenderCoordinates.getContentOffsetYPix();
final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged;
final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged;
final boolean needTemporarilyHideHandles = scrollChanged;
if (needHidePopupZoomer) mPopupZoomer.hide(true);
mRenderCoordinates.updateFrameInfo(
scrollOffsetX, scrollOffsetY,
contentWidth, contentHeight,
viewportWidth, viewportHeight,
pageScaleFactor, minPageScaleFactor, maxPageScaleFactor,
contentOffsetYPix);
if (needTemporarilyHideHandles) temporarilyHideTextHandles();
if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls();
if (contentOffsetChanged) updateHandleScreenPositions();
// Update offsets for fullscreen.
final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
final float controlsOffsetPix = controlsOffsetYCss * deviceScale;
final float overdrawBottomHeightPix = overdrawBottomHeightCss * deviceScale;
getContentViewClient().onOffsetsForFullscreenChanged(
controlsOffsetPix, contentOffsetYPix, overdrawBottomHeightPix);
mPendingRendererFrame = true;
if (mBrowserAccessibilityManager != null) {
mBrowserAccessibilityManager.notifyFrameInfoInitialized();
}
// Update geometry for external video surface.
getContentViewClient().onGeometryChanged(-1, null);
}
@SuppressWarnings("unused")
@CalledByNative
private void updateImeAdapter(int nativeImeAdapterAndroid, int textInputType,
String text, int selectionStart, int selectionEnd,
int compositionStart, int compositionEnd, boolean showImeIfNeeded) {
TraceEvent.begin();
mSelectionEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
if (mActionMode != null) mActionMode.invalidate();
mImeAdapter.attachAndShowIfNeeded(nativeImeAdapterAndroid, textInputType,
selectionStart, selectionEnd, showImeIfNeeded);
if (mInputConnection != null) {
mInputConnection.setEditableText(text, selectionStart, selectionEnd,
compositionStart, compositionEnd);
}
TraceEvent.end();
}
@SuppressWarnings("unused")
@CalledByNative
private void processImeBatchStateAck(boolean isBegin) {
if (mInputConnection == null) return;
mInputConnection.setIgnoreTextInputStateUpdates(isBegin);
}
@SuppressWarnings("unused")
@CalledByNative
private void setTitle(String title) {
getContentViewClient().onUpdateTitle(title);
}
/**
* Called (from native) when the <select> popup needs to be shown.
* @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(String[] items, int[] enabled, boolean multiple,
int[] selectedIndices) {
SelectPopupDialog.show(this, items, enabled, multiple, selectedIndices);
}
@SuppressWarnings("unused")
@CalledByNative
private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) {
mPopupZoomer.setBitmap(zoomedBitmap);
mPopupZoomer.show(targetRect);
temporarilyHideTextHandles();
}
@SuppressWarnings("unused")
@CalledByNative
private SmoothScroller createSmoothScroller(boolean scrollDown, int mouseEventX,
int mouseEventY) {
return new SmoothScroller(this, scrollDown, mouseEventX, mouseEventY);
}
@SuppressWarnings("unused")
@CalledByNative
private void onSelectionChanged(String text) {
mLastSelectedText = text;
}
@SuppressWarnings("unused")
@CalledByNative
private void onSelectionBoundsChanged(Rect anchorRectDip, int anchorDir, Rect focusRectDip,
int focusDir, boolean isAnchorFirst) {
// All coordinates are in DIP.
int x1 = anchorRectDip.left;
int y1 = anchorRectDip.bottom;
int x2 = focusRectDip.left;
int y2 = focusRectDip.bottom;
if (x1 != x2 || y1 != y2 ||
(mSelectionHandleController != null && mSelectionHandleController.isDragging())) {
if (mInsertionHandleController != null) {
mInsertionHandleController.hide();
}
if (isAnchorFirst) {
mStartHandlePoint.setLocalDip(x1, y1);
mEndHandlePoint.setLocalDip(x2, y2);
} else {
mStartHandlePoint.setLocalDip(x2, y2);
mEndHandlePoint.setLocalDip(x1, y1);
}
boolean wereSelectionHandlesShowing = getSelectionHandleController().isShowing();
getSelectionHandleController().onSelectionChanged(anchorDir, focusDir);
updateHandleScreenPositions();
mHasSelection = true;
if (!wereSelectionHandlesShowing && getSelectionHandleController().isShowing()) {
// TODO(cjhopman): Remove this when there is a better signal that long press caused
// a selection. See http://crbug.com/150151.
mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
} else {
mUnselectAllOnActionModeDismiss = false;
hideSelectActionBar();
if (x1 != 0 && y1 != 0 && mSelectionEditable) {
// Selection is a caret, and a text field is focused.
if (mSelectionHandleController != null) {
mSelectionHandleController.hide();
}
mInsertionHandlePoint.setLocalDip(x1, y1);
getInsertionHandleController().onCursorPositionChanged();
updateHandleScreenPositions();
InputMethodManager manager = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (manager.isWatchingCursor(mContainerView)) {
final int xPix = (int) mInsertionHandlePoint.getXPix();
final int yPix = (int) mInsertionHandlePoint.getYPix();
manager.updateCursor(mContainerView, xPix, yPix, xPix, yPix);
}
} else {
// Deselection
if (mSelectionHandleController != null) {
mSelectionHandleController.hideAndDisallowAutomaticShowing();
}
if (mInsertionHandleController != null) {
mInsertionHandleController.hideAndDisallowAutomaticShowing();
}
}
mHasSelection = false;
}
}
@SuppressWarnings("unused")
@CalledByNative
private static void onEvaluateJavaScriptResult(
String jsonResult, JavaScriptCallback callback) {
callback.handleJavaScriptResult(jsonResult);
}
@SuppressWarnings("unused")
@CalledByNative
private void showPastePopup(int xDip, int yDip) {
mInsertionHandlePoint.setLocalDip(xDip, yDip);
getInsertionHandleController().showHandle();
updateHandleScreenPositions();
getInsertionHandleController().showHandleWithPastePopup();
}
@SuppressWarnings("unused")
@CalledByNative
private void onRenderProcessSwap(int oldPid, int newPid) {
assert mPid == oldPid || mPid == newPid;
if (mAttachedToWindow && oldPid != newPid) {
ChildProcessLauncher.unbindAsHighPriority(oldPid);
ChildProcessLauncher.bindAsHighPriority(newPid);
}
// We want to remove the initial binding even if the ContentView is not attached, so that
// renderers for ContentViews loading in background do not retain the high priority.
ChildProcessLauncher.removeInitialBinding(newPid);
mPid = newPid;
}
@SuppressWarnings("unused")
@CalledByNative
private void onWebContentsConnected() {
attachImeAdapter();
}
@SuppressWarnings("unused")
@CalledByNative
private void onWebContentsSwapped() {
attachImeAdapter();
}
/**
* 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));
}
}
/**
* @return Whether a reload happens when this ContentView is activated.
*/
public boolean needsReload() {
return mNativeContentViewCore != 0 && nativeNeedsReload(mNativeContentViewCore);
}
/**
* @see View#hasFocus()
*/
@CalledByNative
public boolean hasFocus() {
return mContainerView.hasFocus();
}
/**
* 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 = System.currentTimeMillis();
int xPix = getViewportWidthPix() / 2;
int yPix = getViewportHeightPix() / 2;
getContentViewGestureHandler().pinchBegin(timeMs, xPix, yPix);
getContentViewGestureHandler().pinchBy(timeMs, xPix, yPix, delta);
getContentViewGestureHandler().pinchEnd(timeMs);
return true;
}
/**
* Invokes the graphical zoom picker widget for this ContentView.
*/
@Override
public void invokeZoomPicker() {
mZoomControlsDelegate.invokeZoomPicker();
}
/**
* 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, object);
nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation,
mRetainedJavaScriptObjects);
}
}
/**
* 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.
*/
public float getScale() {
return mRenderCoordinates.getPageScaleFactor();
}
/**
* If the view is ready to draw contents to the screen. In hardware mode,
* the initialization of the surface texture may not occur until after the
* view has been added to the layout. This method will return {@code true}
* once the texture is actually ready.
*/
public boolean isReady() {
return nativeIsRenderWidgetHostViewReady(mNativeContentViewCore);
}
@CalledByNative
private void startContentIntent(String contentUrl) {
getContentViewClient().onStartContentIntent(getContext(), contentUrl);
}
@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) {
return mAccessibilityInjector.supportsAccessibilityAction(action);
}
/**
* 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) {
if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
return mAccessibilityInjector.performAccessibilityAction(action, arguments);
}
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;
}
/**
* 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 (not script injection) is enabled, and if this is
* running on JellyBean or later, 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 (mBrowserAccessibilityManager != null) {
return mBrowserAccessibilityManager.getAccessibilityNodeProvider();
}
if (mNativeAccessibilityAllowed &&
!mNativeAccessibilityEnabled &&
mNativeContentViewCore != 0 &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mNativeAccessibilityEnabled = true;
nativeSetAccessibilityEnabled(mNativeContentViewCore, true);
}
return null;
}
/**
* @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*/
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
// Note: this is only used by the script-injecting accessibility code.
mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
}
/**
* @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
*/
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
// Note: this is only used by the script-injecting accessibility code.
event.setClassName(this.getClass().getName());
// Identify where the top-left of the screen currently points to.
event.setScrollX(mRenderCoordinates.getScrollXPixInt());
event.setScrollY(mRenderCoordinates.getScrollYPixInt());
// The maximum scroll values are determined by taking the content dimensions and
// subtracting off the actual dimensions of the ChromeView.
int maxScrollXPix = Math.max(0, mRenderCoordinates.getMaxHorizontalScrollPixInt());
int maxScrollYPix = Math.max(0, mRenderCoordinates.getMaxVerticalScrollPixInt());
event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0);
// Setting the maximum scroll values requires API level 15 or higher.
final int SDK_VERSION_REQUIRED_TO_SET_SCROLL = 15;
if (Build.VERSION.SDK_INT >= SDK_VERSION_REQUIRED_TO_SET_SCROLL) {
event.setMaxScrollX(maxScrollXPix);
event.setMaxScrollY(maxScrollYPix);
}
}
/**
* Returns whether accessibility script injection is enabled on the device
*/
public boolean isDeviceAccessibilityScriptInjectionEnabled() {
try {
if (!mContentSettings.getJavaScriptEnabled()) {
return false;
}
int result = getContext().checkCallingOrSelfPermission(
android.Manifest.permission.INTERNET);
if (result != PackageManager.PERMISSION_GRANTED) {
return false;
}
Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION");
field.setAccessible(true);
String accessibilityScriptInjection = (String) field.get(null);
ContentResolver contentResolver = getContext().getContentResolver();
if (mAccessibilityScriptInjectionObserver == null) {
ContentObserver contentObserver = new ContentObserver(new Handler()) {
public void onChange(boolean selfChange, Uri uri) {
setAccessibilityState(mAccessibilityManager.isEnabled());
}
};
contentResolver.registerContentObserver(
Settings.Secure.getUriFor(accessibilityScriptInjection),
false,
contentObserver);
mAccessibilityScriptInjectionObserver = contentObserver;
}
return Settings.Secure.getInt(contentResolver, accessibilityScriptInjection, 0) == 1;
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
return false;
}
/**
* Returns whether or not accessibility injection is being used.
*/
public boolean isInjectingAccessibilityScript() {
return mAccessibilityInjector.accessibilityIsAvailable();
}
/**
* 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) {
setInjectedAccessibility(false);
mNativeAccessibilityAllowed = false;
} else {
boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled();
setInjectedAccessibility(useScriptInjection);
mNativeAccessibilityAllowed = !useScriptInjection;
}
}
/**
* Enable or disable injected accessibility features
*/
public void setInjectedAccessibility(boolean enabled) {
mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
mAccessibilityInjector.setScriptEnabled(enabled);
}
/**
* Stop any TTS notifications that are currently going on.
*/
public void stopCurrentAccessibilityNotifications() {
mAccessibilityInjector.onPageLostFocus();
}
/**
* Inform WebKit that Fullscreen mode has been exited by the user.
*/
public void exitFullscreen() {
nativeExitFullscreen(mNativeContentViewCore);
}
/**
* Changes whether hiding the top controls is enabled.
*
* @param enableHiding Whether hiding the top controls should be enabled or not.
* @param enableShowing Whether showing the top controls should be enabled or not.
* @param animate Whether the transition should be animated or not.
*/
public void updateTopControlsState(boolean enableHiding, boolean enableShowing,
boolean animate) {
nativeUpdateTopControlsState(mNativeContentViewCore, enableHiding, enableShowing, animate);
}
/**
* Callback factory method for nativeGetNavigationHistory().
*/
@CalledByNative
private void addToNavigationHistory(Object history, int index, String url, String virtualUrl,
String originalUrl, String title, Bitmap favicon) {
NavigationEntry entry = new NavigationEntry(
index, url, virtualUrl, originalUrl, title, favicon);
((NavigationHistory) history).addEntry(entry);
}
/**
* Get a copy of the navigation history of the view.
*/
public NavigationHistory getNavigationHistory() {
NavigationHistory history = new NavigationHistory();
if (mNativeContentViewCore != 0) {
int currentIndex = nativeGetNavigationHistory(mNativeContentViewCore, history);
history.setCurrentEntryIndex(currentIndex);
}
return history;
}
@Override
public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
NavigationHistory history = new NavigationHistory();
if (mNativeContentViewCore != 0) {
nativeGetDirectedNavigationHistory(mNativeContentViewCore, history, isForward, itemLimit);
}
return history;
}
/**
* @return The original request URL for the current navigation entry, or null if there is no
* current entry.
*/
public String getOriginalUrlForActiveNavigationEntry() {
if (mNativeContentViewCore != 0) {
return nativeGetOriginalUrlForActiveNavigationEntry(mNativeContentViewCore);
}
return "";
}
/**
* @return The cached copy of render positions and scales.
*/
public RenderCoordinates getRenderCoordinates() {
return mRenderCoordinates;
}
@CalledByNative
private static Rect createRect(int x, int y, int right, int bottom) {
return new Rect(x, y, right, bottom);
}
public void attachExternalVideoSurface(int playerId, Surface surface) {
if (mNativeContentViewCore != 0) {
nativeAttachExternalVideoSurface(mNativeContentViewCore, playerId, surface);
}
}
public void detachExternalVideoSurface(int playerId) {
if (mNativeContentViewCore != 0) {
nativeDetachExternalVideoSurface(mNativeContentViewCore, playerId);
}
}
private boolean onAnimate(long frameTimeMicros) {
if (mNativeContentViewCore == 0) return false;
return nativeOnAnimate(mNativeContentViewCore, frameTimeMicros);
}
private void animateIfNecessary(long frameTimeMicros) {
if (mNeedAnimate) {
mNeedAnimate = onAnimate(frameTimeMicros);
if (!mNeedAnimate) setVSyncNotificationEnabled(false);
}
}
@CalledByNative
private void notifyExternalSurface(
int playerId, boolean isRequest, float x, float y, float width, float height) {
if (isRequest) getContentViewClient().onExternalVideoSurfaceRequested(playerId);
getContentViewClient().onGeometryChanged(playerId, new RectF(x, y, x + width, y + height));
}
/**
* Offer a subset of gesture events to the embedding View,
* primarily for WebView compatibility.
*
* @param type The type of the event.
*
* @return true if the embedder handled the event.
*/
private boolean offerGestureToEmbedder(int type) {
if (type == ContentViewGestureHandler.GESTURE_LONG_PRESS) {
return mContainerView.performLongClick();
}
return false;
}
private native int nativeInit(boolean hardwareAccelerated, int webContentsPtr,
int viewAndroidPtr, int windowAndroidPtr);
@CalledByNative
private ContentVideoViewClient getContentVideoViewClient() {
return mContentViewClient.getContentVideoViewClient();
}
private native void nativeOnJavaContentViewCoreDestroyed(int nativeContentViewCoreImpl);
private native void nativeLoadUrl(
int nativeContentViewCoreImpl,
String url,
int loadUrlType,
int transitionType,
int uaOverrideOption,
String extraHeaders,
byte[] postData,
String baseUrlForDataUrl,
String virtualUrlForDataUrl,
boolean canLoadLocalResources);
private native String nativeGetURL(int nativeContentViewCoreImpl);
private native String nativeGetTitle(int nativeContentViewCoreImpl);
private native void nativeShowInterstitialPage(
int nativeContentViewCoreImpl, String url, int nativeInterstitialPageDelegateAndroid);
private native boolean nativeIsShowingInterstitialPage(int nativeContentViewCoreImpl);
private native boolean nativeIsIncognito(int nativeContentViewCoreImpl);
// Returns true if the native side crashed so that java side can draw a sad tab.
private native boolean nativeCrashed(int nativeContentViewCoreImpl);
private native void nativeSetFocus(int nativeContentViewCoreImpl, boolean focused);
private native void nativeSendOrientationChangeEvent(
int nativeContentViewCoreImpl, int orientation);
// All touch events (including flings, scrolls etc) accept coordinates in physical pixels.
private native boolean nativeSendTouchEvent(
int nativeContentViewCoreImpl, long timeMs, int action, TouchPoint[] pts);
private native int nativeSendMouseMoveEvent(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native int nativeSendMouseWheelEvent(
int nativeContentViewCoreImpl, long timeMs, float x, float y, float verticalAxis);
private native void nativeScrollBegin(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeScrollEnd(int nativeContentViewCoreImpl, long timeMs);
private native void nativeScrollBy(
int nativeContentViewCoreImpl, long timeMs, float x, float y,
float deltaX, float deltaY, boolean lastInputEventForVSync);
private native void nativeFlingStart(
int nativeContentViewCoreImpl, long timeMs, float x, float y, float vx, float vy);
private native void nativeFlingCancel(int nativeContentViewCoreImpl, long timeMs);
private native void nativeSingleTap(
int nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
private native void nativeSingleTapUnconfirmed(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeShowPressState(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeShowPressCancel(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeDoubleTap(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativeLongPress(
int nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
private native void nativeLongTap(
int nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap);
private native void nativePinchBegin(
int nativeContentViewCoreImpl, long timeMs, float x, float y);
private native void nativePinchEnd(int nativeContentViewCoreImpl, long timeMs);
private native void nativePinchBy(int nativeContentViewCoreImpl, long timeMs,
float anchorX, float anchorY, float deltaScale, boolean lastInputEventForVSync);
private native void nativeSelectBetweenCoordinates(
int nativeContentViewCoreImpl, float x1, float y1, float x2, float y2);
private native void nativeMoveCaret(int nativeContentViewCoreImpl, float x, float y);
private native boolean nativeCanGoBack(int nativeContentViewCoreImpl);
private native boolean nativeCanGoForward(int nativeContentViewCoreImpl);
private native boolean nativeCanGoToOffset(int nativeContentViewCoreImpl, int offset);
private native void nativeGoBack(int nativeContentViewCoreImpl);
private native void nativeGoForward(int nativeContentViewCoreImpl);
private native void nativeGoToOffset(int nativeContentViewCoreImpl, int offset);
private native void nativeGoToNavigationIndex(int nativeContentViewCoreImpl, int index);
private native void nativeStopLoading(int nativeContentViewCoreImpl);
private native void nativeReload(int nativeContentViewCoreImpl);
private native void nativeCancelPendingReload(int nativeContentViewCoreImpl);
private native void nativeContinuePendingReload(int nativeContentViewCoreImpl);
private native void nativeSelectPopupMenuItems(int nativeContentViewCoreImpl, int[] indices);
private native void nativeScrollFocusedEditableNodeIntoView(int nativeContentViewCoreImpl);
private native void nativeUndoScrollFocusedEditableNodeIntoView(int nativeContentViewCoreImpl);
private native boolean nativeNeedsReload(int nativeContentViewCoreImpl);
private native void nativeClearHistory(int nativeContentViewCoreImpl);
private native void nativeEvaluateJavaScript(int nativeContentViewCoreImpl,
String script, JavaScriptCallback callback, boolean startRenderer);
private native int nativeGetNativeImeAdapter(int nativeContentViewCoreImpl);
private native int nativeGetCurrentRenderProcessId(int nativeContentViewCoreImpl);
private native int nativeGetBackgroundColor(int nativeContentViewCoreImpl);
private native void nativeOnShow(int nativeContentViewCoreImpl);
private native void nativeOnHide(int nativeContentViewCoreImpl);
private native void nativeSetUseDesktopUserAgent(int nativeContentViewCoreImpl,
boolean enabled, boolean reloadOnChange);
private native boolean nativeGetUseDesktopUserAgent(int nativeContentViewCoreImpl);
private native void nativeClearSslPreferences(int nativeContentViewCoreImpl);
private native void nativeAddJavascriptInterface(int nativeContentViewCoreImpl, Object object,
String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet);
private native void nativeRemoveJavascriptInterface(int nativeContentViewCoreImpl, String name);
private native int nativeGetNavigationHistory(int nativeContentViewCoreImpl, Object context);
private native void nativeGetDirectedNavigationHistory(int nativeContentViewCoreImpl,
Object context, boolean isForward, int maxEntries);
private native String nativeGetOriginalUrlForActiveNavigationEntry(
int nativeContentViewCoreImpl);
private native void nativeUpdateVSyncParameters(int nativeContentViewCoreImpl,
long timebaseMicros, long intervalMicros);
private native void nativeOnVSync(int nativeContentViewCoreImpl, long frameTimeMicros);
private native boolean nativeOnAnimate(int nativeContentViewCoreImpl, long frameTimeMicros);
private native boolean nativePopulateBitmapFromCompositor(int nativeContentViewCoreImpl,
Bitmap bitmap);
private native void nativeWasResized(int nativeContentViewCoreImpl);
private native boolean nativeIsRenderWidgetHostViewReady(int nativeContentViewCoreImpl);
private native void nativeExitFullscreen(int nativeContentViewCoreImpl);
private native void nativeUpdateTopControlsState(int nativeContentViewCoreImpl,
boolean enableHiding, boolean enableShowing, boolean animate);
private native void nativeShowImeIfNeeded(int nativeContentViewCoreImpl);
private native void nativeAttachExternalVideoSurface(
int nativeContentViewCoreImpl, int playerId, Surface surface);
private native void nativeDetachExternalVideoSurface(
int nativeContentViewCoreImpl, int playerId);
private native void nativeSetAccessibilityEnabled(
int nativeContentViewCoreImpl, boolean enabled);
}