Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ContentViewCore.java
index ec9d756..85e2da8 100644 (file)
@@ -7,6 +7,7 @@ package org.chromium.content.browser;
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.SearchManager;
+import android.content.ClipboardManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -15,7 +16,6 @@ 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.net.Uri;
 import android.os.Build;
@@ -29,6 +29,7 @@ import android.text.Editable;
 import android.text.Selection;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 import android.view.ActionMode;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
@@ -46,32 +47,37 @@ import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
-import com.google.common.annotations.VisibleForTesting;
-
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.CalledByNative;
 import org.chromium.base.CommandLine;
 import org.chromium.base.JNINamespace;
 import org.chromium.base.ObserverList;
 import org.chromium.base.ObserverList.RewindableIterator;
 import org.chromium.base.TraceEvent;
+import org.chromium.base.VisibleForTesting;
 import org.chromium.content.R;
 import org.chromium.content.browser.ScreenOrientationListener.ScreenOrientationObserver;
 import org.chromium.content.browser.accessibility.AccessibilityInjector;
 import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
 import org.chromium.content.browser.input.AdapterInputConnection;
-import org.chromium.content.browser.input.HandleView;
+import org.chromium.content.browser.input.GamepadList;
 import org.chromium.content.browser.input.ImeAdapter;
 import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory;
 import org.chromium.content.browser.input.InputMethodManagerWrapper;
-import org.chromium.content.browser.input.InsertionHandleController;
+import org.chromium.content.browser.input.PastePopupMenu;
+import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
+import org.chromium.content.browser.input.PopupTouchHandleDrawable;
+import org.chromium.content.browser.input.PopupTouchHandleDrawable.PopupTouchHandleDrawableDelegate;
 import org.chromium.content.browser.input.SelectPopup;
 import org.chromium.content.browser.input.SelectPopupDialog;
 import org.chromium.content.browser.input.SelectPopupDropdown;
 import org.chromium.content.browser.input.SelectPopupItem;
-import org.chromium.content.browser.input.SelectionHandleController;
+import org.chromium.content.browser.input.SelectionEventType;
 import org.chromium.content.common.ContentSwitches;
 import org.chromium.content_public.browser.GestureStateListener;
+import org.chromium.content_public.browser.JavaScriptCallback;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ViewAndroid;
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid;
@@ -92,7 +98,7 @@ import java.util.Map;
  */
 @JNINamespace("content")
 public class ContentViewCore
-        implements NavigationClient, AccessibilityStateChangeListener, ScreenOrientationObserver {
+        implements AccessibilityStateChangeListener, ScreenOrientationObserver {
 
     private static final String TAG = "ContentViewCore";
 
@@ -104,8 +110,15 @@ public class ContentViewCore
     private static final int IS_LONG_PRESS = 1;
     private static final int IS_LONG_TAP = 2;
 
-    // Length of the delay (in ms) before fading in handles after the last page movement.
-    private static final int TEXT_HANDLE_FADE_IN_DELAY = 300;
+    private static final ZoomControlsDelegate NO_OP_ZOOM_CONTROLS_DELEGATE =
+            new ZoomControlsDelegate() {
+        @Override
+        public void invokeZoomPicker() {}
+        @Override
+        public void dismissZoomPicker() {}
+        @Override
+        public void updateZoomControls() {}
+    };
 
     // If the embedder adds a JavaScript interface object that contains an indirect reference to
     // the ContentViewCore, then storing a strong ref to the interface object on the native
@@ -115,9 +128,12 @@ public class ContentViewCore
     // native side. However we still need a strong reference on the Java side to
     // prevent garbage collection if the embedder doesn't maintain their own ref to the
     // interface object - the Java side ref won't create a new GC root.
-    // This map stores those refernces. We put into the map on addJavaScriptInterface()
-    // and remove from it in removeJavaScriptInterface().
-    private final Map<String, Object> mJavaScriptInterfaces = new HashMap<String, Object>();
+    // This map stores those references. We put into the map on addJavaScriptInterface()
+    // and remove from it in removeJavaScriptInterface(). The annotation class is stored for
+    // the purpose of migrating injected objects from one instance of CVC to another, which
+    // is used by Android WebView to support WebChromeClient.onCreateWindow scenario.
+    private final Map<String, Pair<Object, Class>> mJavaScriptInterfaces =
+            new HashMap<String, Pair<Object, Class>>();
 
     // Additionally, we keep track of all Java bound JS objects that are in use on the
     // current page to ensure that they are not garbage collected until the page is
@@ -208,7 +224,7 @@ public class ContentViewCore
      * extractSmartClipData are available.
      */
     public interface SmartClipDataListener {
-        public void onSmartClipDataExtracted(String result);
+        public void onSmartClipDataExtracted(String text, String html, Rect clipRect);
     }
 
     private final Context mContext;
@@ -230,6 +246,7 @@ public class ContentViewCore
 
     private PopupZoomer mPopupZoomer;
     private SelectPopup mSelectPopup;
+    private long mNativeSelectPopupSourceFrame = 0;
 
     private Runnable mFakeMouseMoveRunnable = null;
 
@@ -239,39 +256,34 @@ public class ContentViewCore
     private AdapterInputConnection mInputConnection;
     private InputMethodManagerWrapper mInputMethodManagerWrapper;
 
-    private SelectionHandleController mSelectionHandleController;
-    private InsertionHandleController mInsertionHandleController;
+    // Lazily created paste popup menu, triggered either via long press in an
+    // editable region or from tapping the insertion handle.
+    private PastePopupMenu mPastePopupMenu;
+    private boolean mWasPastePopupShowingOnInsertionDragStart;
 
-    private Runnable mDeferredHandleFadeInRunnable;
+    private PopupTouchHandleDrawableDelegate mTouchHandleDelegate;
 
     private PositionObserver mPositionObserver;
-    private PositionObserver.Listener mPositionListener;
 
     // Size of the viewport in physical pixels as set from onSizeChanged.
     private int mViewportWidthPix;
     private int mViewportHeightPix;
     private int mPhysicalBackingWidthPix;
     private int mPhysicalBackingHeightPix;
-    private int mOverdrawBottomHeightPix;
-    private int mViewportSizeOffsetWidthPix;
-    private int mViewportSizeOffsetHeightPix;
-    private int mLocationInWindowX;
-    private int mLocationInWindowY;
+    private int mTopControlsLayoutHeightPix;
 
     // Cached copy of all positions and scales as reported by the renderer.
     private final RenderCoordinates mRenderCoordinates;
 
-    private final RenderCoordinates.NormalizedPoint mStartHandlePoint;
-    private final RenderCoordinates.NormalizedPoint mEndHandlePoint;
-    private final RenderCoordinates.NormalizedPoint mInsertionHandlePoint;
-
     // Tracks whether a selection is currently active.  When applied to selected text, indicates
     // whether the last selected text is still highlighted.
     private boolean mHasSelection;
+    private boolean mHasInsertion;
     private String mLastSelectedText;
-    private boolean mSelectionEditable;
+    private boolean mFocusedNodeEditable;
     private ActionMode mActionMode;
     private boolean mUnselectAllOnActionModeDismiss;
+    private boolean mPreserveSelectionOnNextLossOfFocus;
 
     // Delegate that will handle GET downloads, and be notified of completion of POST downloads.
     private ContentViewDownloadDelegate mDownloadDelegate;
@@ -294,6 +306,12 @@ public class ContentViewCore
     // Accessibility touch exploration state.
     private boolean mTouchExplorationEnabled;
 
+    // Whether accessibility focus should be set to the page when it finishes loading.
+    // This only applies if an accessibility service like TalkBack is running.
+    // This is desirable behavior for a browser window, but not for an embedded
+    // WebView.
+    private boolean mShouldSetAccessibilityFocusOnPageLoad;
+
     // Allows us to dynamically respond when the accessibility script injection flag changes.
     private ContentObserver mAccessibilityScriptInjectionObserver;
 
@@ -301,12 +319,9 @@ public class ContentViewCore
     // because the OSK was just brought up.
     private final Rect mFocusPreOSKViewportRect = new Rect();
 
-    // Whether we received a new frame since consumePendingRendererFrame() was last called.
-    private boolean mPendingRendererFrame = false;
-
-    // On single tap this will store the x, y coordinates of the touch.
-    private int mSingleTapX;
-    private int mSingleTapY;
+    // On tap this will store the x, y coordinates of the touch.
+    private int mLastTapX;
+    private int mLastTapY;
 
     // Whether a touch scroll sequence is active, used to hide text selection
     // handles. Note that a scroll sequence will *always* bound a pinch
@@ -338,6 +353,14 @@ public class ContentViewCore
     private float mCurrentTouchOffsetX;
     private float mCurrentTouchOffsetY;
 
+    // Offsets for smart clip
+    private int mSmartClipOffsetX;
+    private int mSmartClipOffsetY;
+
+    // Whether the ContentViewCore requires the WebContents to be fullscreen in order to lock the
+    // screen orientation.
+    private boolean mFullscreenRequiredForOrientationLock = true;
+
     /**
      * Constructs a new ContentViewCore. Embedders must call initialize() after constructing
      * a ContentViewCore and before using it.
@@ -358,9 +381,6 @@ public class ContentViewCore
             deviceScaleFactor = Float.valueOf(forceScaleFactor);
         }
         mRenderCoordinates.setDeviceScaleFactor(deviceScaleFactor);
-        mStartHandlePoint = mRenderCoordinates.createNormalizedPoint();
-        mEndHandlePoint = mRenderCoordinates.createNormalizedPoint();
-        mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint();
         mAccessibilityManager = (AccessibilityManager)
                 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
         mGestureStateListeners = new ObserverList<GestureStateListener>();
@@ -392,17 +412,19 @@ public class ContentViewCore
         return mWebContents;
     }
 
+    /* TODO(aelias): Remove this after downstream callers switch to setTopControlsLayoutHeight. */
+    public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
+        setTopControlsLayoutHeight(offsetYPix);
+    }
+
     /**
-     * Specifies how much smaller the WebKit layout size should be relative to the size of this
+     * Specifies how much smaller the Blink layout size should be relative to the size of this
      * view.
-     * @param offsetXPix The X amount in pixels to shrink the viewport by.
-     * @param offsetYPix The Y amount in pixels to shrink the viewport by.
+     * @param topControlsLayoutHeightPix The Y amount in pixels to shrink the viewport by.
      */
-    public void setViewportSizeOffset(int offsetXPix, int offsetYPix) {
-        if (offsetXPix != mViewportSizeOffsetWidthPix ||
-                offsetYPix != mViewportSizeOffsetHeightPix) {
-            mViewportSizeOffsetWidthPix = offsetXPix;
-            mViewportSizeOffsetHeightPix = offsetYPix;
+    public void setTopControlsLayoutHeight(int topControlsLayoutHeightPix) {
+        if (topControlsLayoutHeightPix != mTopControlsLayoutHeightPix) {
+            mTopControlsLayoutHeightPix = topControlsLayoutHeightPix;
             if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore);
         }
     }
@@ -421,10 +443,14 @@ public class ContentViewCore
     @VisibleForTesting
     public ViewAndroidDelegate getViewAndroidDelegate() {
         return new ViewAndroidDelegate() {
+            // mContainerView can change, but this ViewAndroidDelegate can only be used to
+            // add and remove views from the mContainerViewAtCreation.
+            private final ViewGroup mContainerViewAtCreation = mContainerView;
+
             @Override
             public View acquireAnchorView() {
-                View anchorView = new View(getContext());
-                mContainerView.addView(anchorView);
+                View anchorView = new View(mContext);
+                mContainerViewAtCreation.addView(anchorView);
                 return anchorView;
             }
 
@@ -432,28 +458,40 @@ public class ContentViewCore
             @SuppressWarnings("deprecation")  // AbsoluteLayout
             public void setAnchorViewPosition(
                     View view, float x, float y, float width, float height) {
-                assert view.getParent() == mContainerView;
+                if (view.getParent() == null) {
+                    // Ignore. setAnchorViewPosition has been called after the anchor view has
+                    // already been released.
+                    return;
+                }
+                assert view.getParent() == mContainerViewAtCreation;
 
-                float scale = (float) DeviceDisplayInfo.create(getContext()).getDIPScale();
+                float scale = (float) DeviceDisplayInfo.create(mContext).getDIPScale();
 
                 // The anchor view should not go outside the bounds of the ContainerView.
                 int leftMargin = Math.round(x * scale);
                 int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
                 int scaledWidth = Math.round(width * scale);
                 // ContentViewCore currently only supports these two container view types.
-                if (mContainerView instanceof FrameLayout) {
-                    if (scaledWidth + leftMargin > mContainerView.getWidth()) {
-                        scaledWidth = mContainerView.getWidth() - leftMargin;
+                if (mContainerViewAtCreation instanceof FrameLayout) {
+                    int startMargin;
+                    if (ApiCompatibilityUtils.isLayoutRtl(mContainerViewAtCreation)) {
+                        startMargin = mContainerViewAtCreation.getMeasuredWidth()
+                                - Math.round((width + x) * scale);
+                    } else {
+                        startMargin = leftMargin;
+                    }
+                    if (scaledWidth + startMargin > mContainerViewAtCreation.getWidth()) {
+                        scaledWidth = mContainerViewAtCreation.getWidth() - startMargin;
                     }
                     FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                         scaledWidth, Math.round(height * scale));
-                    lp.leftMargin = leftMargin;
+                    ApiCompatibilityUtils.setMarginStart(lp, startMargin);
                     lp.topMargin = topMargin;
                     view.setLayoutParams(lp);
-                } else if (mContainerView instanceof android.widget.AbsoluteLayout) {
+                } else if (mContainerViewAtCreation instanceof android.widget.AbsoluteLayout) {
                     // This fixes the offset due to a difference in
                     // scrolling model of WebView vs. Chrome.
-                    // TODO(sgurun) fix this to use mContainerView.getScroll[X/Y]()
+                    // TODO(sgurun) fix this to use mContainerViewAtCreation.getScroll[X/Y]()
                     // as it naturally accounts for scroll differences between
                     // these models.
                     leftMargin += mRenderCoordinates.getScrollXPixInt();
@@ -464,13 +502,13 @@ public class ContentViewCore
                                 scaledWidth, (int) (height * scale), leftMargin, topMargin);
                     view.setLayoutParams(lp);
                 } else {
-                    Log.e(TAG, "Unknown layout " + mContainerView.getClass().getName());
+                    Log.e(TAG, "Unknown layout " + mContainerViewAtCreation.getClass().getName());
                 }
             }
 
             @Override
             public void releaseAnchorView(View anchorView) {
-                mContainerView.removeView(anchorView);
+                mContainerViewAtCreation.removeView(anchorView);
             }
         };
     }
@@ -500,20 +538,14 @@ public class ContentViewCore
         return mInputConnection;
     }
 
-    @VisibleForTesting
-    public void setContainerViewForTest(ViewGroup view) {
-        mContainerView = view;
-    }
-
     private ImeAdapter createImeAdapter(Context context) {
         return new ImeAdapter(mInputMethodManagerWrapper,
                 new ImeAdapter.ImeAdapterDelegate() {
                     @Override
-                    public void onImeEvent(boolean isFinish) {
+                    public void onImeEvent() {
+                        mPopupZoomer.hide(true);
                         getContentViewClient().onImeEvent();
-                        if (!isFinish) {
-                            hideHandles();
-                        }
+                        if (mFocusedNodeEditable) hideTextHandles();
                     }
 
                     @Override
@@ -542,7 +574,7 @@ public class ContentViewCore
                                     // always be called, crbug.com/294908.
                                     getContainerView().getWindowVisibleDisplayFrame(
                                             mFocusPreOSKViewportRect);
-                                } else if (resultCode ==
+                                } else if (hasFocus() && resultCode ==
                                         InputMethodManager.RESULT_UNCHANGED_SHOWN) {
                                     // If the OSK was already there, focus the form immediately.
                                     scrollFocusedEditableNodeIntoView();
@@ -573,63 +605,42 @@ public class ContentViewCore
     // deleting it after destroying the ContentViewCore.
     public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
             long nativeWebContents, WindowAndroid windowAndroid) {
-        mContainerView = containerView;
-        mPositionObserver = new ViewPositionObserver(mContainerView);
-        mPositionListener = new PositionObserver.Listener() {
-            @Override
-            public void onPositionChanged(int x, int y) {
-                if (isSelectionHandleShowing() || isInsertionHandleShowing()) {
-                    temporarilyHideTextHandles();
-                }
-            }
-        };
-
-        long windowNativePointer = windowAndroid != null ? windowAndroid.getNativePointer() : 0;
+        setContainerView(containerView);
 
-        long viewAndroidNativePointer = 0;
-        if (windowNativePointer != 0) {
-            mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate());
-            viewAndroidNativePointer = mViewAndroid.getNativePointer();
-        }
+        long windowNativePointer = windowAndroid.getNativePointer();
+        assert windowNativePointer != 0;
+        mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate());
+        long viewAndroidNativePointer = mViewAndroid.getNativePointer();
+        assert viewAndroidNativePointer != 0;
 
-        mZoomControlsDelegate = new ZoomControlsDelegate() {
-            @Override
-            public void invokeZoomPicker() {}
-            @Override
-            public void dismissZoomPicker() {}
-            @Override
-            public void updateZoomControls() {}
-        };
+        mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE;
 
         mNativeContentViewCore = nativeInit(
-                nativeWebContents, viewAndroidNativePointer, windowNativePointer);
+                nativeWebContents, viewAndroidNativePointer, windowNativePointer,
+                mRetainedJavaScriptObjects);
         mWebContents = nativeGetWebContentsAndroid(mNativeContentViewCore);
         mContentSettings = new ContentSettings(this, mNativeContentViewCore);
-        initializeContainerView(internalDispatcher);
+
+        setContainerViewInternals(internalDispatcher);
+        mRenderCoordinates.reset();
+        initPopupZoomer(mContext);
+        mImeAdapter = createImeAdapter(mContext);
 
         mAccessibilityInjector = AccessibilityInjector.newInstance(this);
 
-        String contentDescription = "Web View";
-        if (R.string.accessibility_content_view == 0) {
-            Log.w(TAG, "Setting contentDescription to 'Web View' as no value was specified.");
-        } else {
-            contentDescription = mContext.getResources().getString(
-                    R.string.accessibility_content_view);
-        }
-        mContainerView.setContentDescription(contentDescription);
-        mWebContentsObserver = new WebContentsObserverAndroid(this) {
+        mWebContentsObserver = new WebContentsObserverAndroid(mWebContents) {
             @Override
             public void didNavigateMainFrame(String url, String baseUrl,
-                    boolean isNavigationToDifferentPage, boolean isNavigationInPage) {
+                    boolean isNavigationToDifferentPage, boolean isFragmentNavigation) {
                 if (!isNavigationToDifferentPage) return;
-                hidePopups();
+                hidePopupsAndClearSelection();
                 resetScrollInProgress();
                 resetGestureDetection();
             }
 
             @Override
             public void renderProcessGone(boolean wasOomProtected) {
-                hidePopups();
+                hidePopupsAndClearSelection();
                 resetScrollInProgress();
                 // No need to reset gesture detection as the detector will have
                 // been destroyed in the RenderWidgetHostView.
@@ -637,6 +648,47 @@ public class ContentViewCore
         };
     }
 
+    /**
+     * Sets a new container view for this {@link ContentViewCore}.
+     *
+     * <p>WARNING: This is not a general purpose method and has been designed with WebView
+     * fullscreen in mind. Please be aware that it might not be appropriate for other use cases
+     * and that it has a number of limitations. For example the PopupZoomer only works with the
+     * container view with which this ContentViewCore has been initialized.
+     *
+     * <p>This method only performs a small part of replacing the container view and
+     * embedders are responsible for:
+     * <ul>
+     *     <li>Disconnecting the old container view from this ContentViewCore</li>
+     *     <li>Updating the InternalAccessDelegate</li>
+     *     <li>Reconciling the state of this ContentViewCore with the new container view</li>
+     *     <li>Tearing down and recreating the native GL rendering where appropriate</li>
+     *     <li>etc.</li>
+     * </ul>
+     */
+    public void setContainerView(ViewGroup containerView) {
+        TraceEvent.begin();
+        if (mContainerView != null) {
+            mPastePopupMenu = null;
+            mInputConnection = null;
+            hidePopupsAndClearSelection();
+        }
+
+        mContainerView = containerView;
+        mPositionObserver = new ViewPositionObserver(mContainerView);
+        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);
+        mContainerView.setWillNotDraw(false);
+        mContainerView.setClickable(true);
+        TraceEvent.end();
+    }
+
     @CalledByNative
     void onNativeContentViewCoreDestroyed(long nativeContentViewCore) {
         assert nativeContentViewCore == mNativeContentViewCore;
@@ -652,36 +704,20 @@ public class ContentViewCore
         mContainerViewInternals = internalDispatcher;
     }
 
-    /**
-     * Initializes the View that will contain all Views created by the ContentViewCore.
-     *
-     * @param internalDispatcher Handles dispatching all hidden or super methods to the
-     *                           containerView.
-     */
-    private void initializeContainerView(InternalAccessDelegate internalDispatcher) {
-        TraceEvent.begin();
-        mContainerViewInternals = internalDispatcher;
-
-        mContainerView.setWillNotDraw(false);
-        mContainerView.setClickable(true);
-
-        mRenderCoordinates.reset();
-
-        initPopupZoomer(mContext);
-        mImeAdapter = createImeAdapter(mContext);
-        TraceEvent.end();
-    }
-
     private void initPopupZoomer(Context context) {
         mPopupZoomer = new PopupZoomer(context);
         mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() {
+            // mContainerView can change, but this OnVisibilityChangedListener can only be used
+            // to add and remove views from the mContainerViewAtCreation.
+            private final ViewGroup mContainerViewAtCreation = mContainerView;
+
             @Override
             public void onPopupZoomerShown(final PopupZoomer zoomer) {
-                mContainerView.post(new Runnable() {
+                mContainerViewAtCreation.post(new Runnable() {
                     @Override
                     public void run() {
-                        if (mContainerView.indexOfChild(zoomer) == -1) {
-                            mContainerView.addView(zoomer);
+                        if (mContainerViewAtCreation.indexOfChild(zoomer) == -1) {
+                            mContainerViewAtCreation.addView(zoomer);
                         } else {
                             assert false : "PopupZoomer should never be shown without being hidden";
                         }
@@ -691,12 +727,12 @@ public class ContentViewCore
 
             @Override
             public void onPopupZoomerHidden(final PopupZoomer zoomer) {
-                mContainerView.post(new Runnable() {
+                mContainerViewAtCreation.post(new Runnable() {
                     @Override
                     public void run() {
-                        if (mContainerView.indexOfChild(zoomer) != -1) {
-                            mContainerView.removeView(zoomer);
-                            mContainerView.invalidate();
+                        if (mContainerViewAtCreation.indexOfChild(zoomer) != -1) {
+                            mContainerViewAtCreation.removeView(zoomer);
+                            mContainerViewAtCreation.invalidate();
                         } else {
                             assert false : "PopupZoomer should never be hidden without being shown";
                         }
@@ -707,9 +743,13 @@ public class ContentViewCore
         // TODO(yongsheng): LONG_TAP is not enabled in PopupZoomer. So need to dispatch a LONG_TAP
         // gesture if a user completes a tap on PopupZoomer UI after a LONG_PRESS gesture.
         PopupZoomer.OnTapListener listener = new PopupZoomer.OnTapListener() {
+            // mContainerView can change, but this OnTapListener can only be used
+            // with the mContainerViewAtCreation.
+            private final ViewGroup mContainerViewAtCreation = mContainerView;
+
             @Override
             public boolean onSingleTap(View v, MotionEvent e) {
-                mContainerView.requestFocus();
+                mContainerViewAtCreation.requestFocus();
                 if (mNativeContentViewCore != 0) {
                     nativeSingleTap(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY());
                 }
@@ -727,6 +767,11 @@ public class ContentViewCore
         mPopupZoomer.setOnTapListener(listener);
     }
 
+    @VisibleForTesting
+    public void setPopupZoomerForTest(PopupZoomer popupZoomer) {
+        mPopupZoomer = popupZoomer;
+    }
+
     /**
      * Destroy the internal state of the ContentView. This method may only be
      * called after the ContentView has been removed from the view system. No
@@ -737,6 +782,16 @@ public class ContentViewCore
         if (mNativeContentViewCore != 0) {
             nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore);
         }
+        mWebContentsObserver.detachFromWebContents();
+        mWebContentsObserver = null;
+        setSmartClipDataListener(null);
+        setZoomControlsDelegate(null);
+        // TODO(igsolla): address TODO in ContentViewClient because ContentViewClient is not
+        // currently a real Null Object.
+        //
+        // Instead of deleting the client we use the Null Object pattern to avoid null checks
+        // in this class.
+        mContentViewClient = new ContentViewClient();
         mWebContents = null;
         if (mViewAndroid != null) mViewAndroid.destroy();
         mNativeContentViewCore = 0;
@@ -746,6 +801,7 @@ public class ContentViewCore
         unregisterAccessibilityContentObserver();
         mGestureStateListeners.clear();
         ScreenOrientationListener.getInstance().removeObserver(this);
+        mPositionObserver.clearListener();
     }
 
     private void unregisterAccessibilityContentObserver() {
@@ -795,69 +851,12 @@ public class ContentViewCore
         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.getReferrer() != null ? params.getReferrer().getUrl() : null,
-                params.getReferrer() != null ? params.getReferrer().getPolicy() : 0,
-                params.mUaOverrideOption,
-                params.getExtraHeadersString(),
-                params.mPostData,
-                params.mBaseUrlForDataUrl,
-                params.mVirtualUrlForDataUrl,
-                params.mCanLoadLocalResources);
-    }
-
-    /**
-     * Stops loading the current web contents.
-     */
-    public void stopLoading() {
-        if (mWebContents != null) mWebContents.stop();
-    }
-
-    /**
-     * 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() {
-        return mWebContents == null ? null : mWebContents.getTitle();
-    }
-
-    /**
      * Shows an interstitial page driven by the passed in delegate.
      *
      * @param url The URL being blocked by the interstitial.
@@ -866,27 +865,16 @@ public class ContentViewCore
     @VisibleForTesting
     public void showInterstitialPage(
             String url, InterstitialPageDelegateAndroid delegate) {
-        if (mNativeContentViewCore == 0) return;
-        nativeShowInterstitialPage(mNativeContentViewCore, url, delegate.getNative());
+        assert mWebContents != null;
+        mWebContents.showInterstitialPage(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;
+        assert mWebContents != null;
+        return mWebContents.isShowingInterstitialPage();
     }
 
     /**
@@ -913,23 +901,17 @@ public class ContentViewCore
     @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; }
+    /* TODO(aelias): Remove these when downstream callers disappear. */
+    @VisibleForTesting
+    public int getViewportSizeOffsetWidthPix() { return 0; }
+    @VisibleForTesting
+    public int getViewportSizeOffsetHeightPix() { return getTopControlsLayoutHeightPix(); }
 
     /**
-     * @return The amount to shrink the viewport relative to {@link #getViewportHeightPix()}.
+     * @return The amount that the viewport size given to Blink is shrunk by the URL-bar..
      */
     @CalledByNative
-    public int getViewportSizeOffsetHeightPix() { return mViewportSizeOffsetHeightPix; }
+    public int getTopControlsLayoutHeightPix() { return mTopControlsLayoutHeightPix; }
 
     /**
      * @see android.webkit.WebView#getContentHeight()
@@ -945,117 +927,6 @@ public class ContentViewCore
         return mRenderCoordinates.getContentWidthCss();
     }
 
-    // TODO(teddchoc): Remove all these navigation controller methods from here and have the
-    //                 embedders manage it.
-    /**
-     * @return Whether the current WebContents has a previous navigation entry.
-     */
-    public boolean canGoBack() {
-        return mWebContents != null && mWebContents.getNavigationController().canGoBack();
-    }
-
-    /**
-     * @return Whether the current WebContents has a navigation entry after the current one.
-     */
-    public boolean canGoForward() {
-        return mWebContents != null && mWebContents.getNavigationController().canGoForward();
-    }
-
-    /**
-     * @param offset The offset into the navigation history.
-     * @return Whether we can move in history by given offset
-     */
-    public boolean canGoToOffset(int offset) {
-        return mWebContents != null &&
-                mWebContents.getNavigationController().canGoToOffset(offset);
-    }
-
-    /**
-     * Navigates to the specified offset from the "current entry". Does nothing if the offset is out
-     * of bounds.
-     * @param offset The offset into the navigation history.
-     */
-    public void goToOffset(int offset) {
-        if (mWebContents != null) mWebContents.getNavigationController().goToOffset(offset);
-    }
-
-    @Override
-    public void goToNavigationIndex(int index) {
-        if (mWebContents != null) {
-            mWebContents.getNavigationController().goToNavigationIndex(index);
-        }
-    }
-
-    /**
-     * Goes to the navigation entry before the current one.
-     */
-    public void goBack() {
-        if (mWebContents != null) mWebContents.getNavigationController().goBack();
-    }
-
-    /**
-     * Goes to the navigation entry following the current one.
-     */
-    public void goForward() {
-        if (mWebContents != null) mWebContents.getNavigationController().goForward();
-    }
-
-    /**
-     * Loads the current navigation if there is a pending lazy load (after tab restore).
-     */
-    public void loadIfNecessary() {
-        if (mNativeContentViewCore != 0) nativeLoadIfNecessary(mNativeContentViewCore);
-    }
-
-    /**
-     * Requests the current navigation to be loaded upon the next call to loadIfNecessary().
-     */
-    public void requestRestoreLoad() {
-        if (mNativeContentViewCore != 0) nativeRequestRestoreLoad(mNativeContentViewCore);
-    }
-
-    /**
-     * Reload the current page.
-     */
-    public void reload(boolean checkForRepost) {
-        mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
-        if (mNativeContentViewCore != 0) {
-            nativeReload(mNativeContentViewCore, checkForRepost);
-        }
-    }
-
-    /**
-     * Reload the current page, ignoring the contents of the cache.
-     */
-    public void reloadIgnoringCache(boolean checkForRepost) {
-        mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary();
-        if (mNativeContentViewCore != 0) {
-            nativeReloadIgnoringCache(mNativeContentViewCore, checkForRepost);
-        }
-    }
-
-    /**
-     * 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).
      */
@@ -1067,7 +938,14 @@ public class ContentViewCore
      * @return Whether the current selection is editable (false if no text selected).
      */
     public boolean isSelectionEditable() {
-        return mHasSelection ? mSelectionEditable : false;
+        return mHasSelection ? mFocusedNodeEditable : false;
+    }
+
+    /**
+     * @return Whether the current focused node is editable.
+     */
+    public boolean isFocusedNodeEditable() {
+        return mFocusedNodeEditable;
     }
 
     // End FrameLayout overrides.
@@ -1076,24 +954,23 @@ public class ContentViewCore
      * @see View#onTouchEvent(MotionEvent)
      */
     public boolean onTouchEvent(MotionEvent event) {
+        final boolean isTouchHandleEvent = false;
+        return onTouchEventImpl(event, isTouchHandleEvent);
+    }
+
+    private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) {
         TraceEvent.begin("onTouchEvent");
         try {
-            cancelRequestToScrollFocusedEditableNodeIntoView();
+            int eventAction = event.getActionMasked();
 
-            final int eventAction = event.getActionMasked();
-
-            // Only these actions have any effect on gesture detection.  Other
-            // actions have no corresponding WebTouchEvent type and may confuse the
-            // touch pipline, so we ignore them entirely.
-            if (eventAction != MotionEvent.ACTION_DOWN
-                    && eventAction != MotionEvent.ACTION_UP
-                    && eventAction != MotionEvent.ACTION_CANCEL
-                    && eventAction != MotionEvent.ACTION_MOVE
-                    && eventAction != MotionEvent.ACTION_POINTER_DOWN
-                    && eventAction != MotionEvent.ACTION_POINTER_UP) {
-                return false;
+            if (eventAction == MotionEvent.ACTION_DOWN) {
+                cancelRequestToScrollFocusedEditableNodeIntoView();
             }
 
+            if (SPenSupport.isSPenSupported(mContext))
+                eventAction = SPenSupport.convertSPenEventAction(eventAction);
+            if (!isValidTouchEventActionForNative(eventAction)) return false;
+
             if (mNativeContentViewCore == 0) return false;
 
             // A zero offset is quite common, in which case the unnecessary copy should be avoided.
@@ -1111,7 +988,15 @@ public class ContentViewCore
                     pointerCount > 1 ? event.getX(1) : 0,
                     pointerCount > 1 ? event.getY(1) : 0,
                     event.getPointerId(0), pointerCount > 1 ? event.getPointerId(1) : -1,
-                    event.getTouchMajor(), pointerCount > 1 ? event.getTouchMajor(1) : 0);
+                    event.getTouchMajor(), pointerCount > 1 ? event.getTouchMajor(1) : 0,
+                    event.getTouchMinor(), pointerCount > 1 ? event.getTouchMinor(1) : 0,
+                    event.getOrientation(), pointerCount > 1 ? event.getOrientation(1) : 0,
+                    event.getRawX(), event.getRawY(),
+                    event.getToolType(0),
+                    pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN,
+                    event.getButtonState(),
+                    event.getMetaState(),
+                    isTouchHandleEvent);
 
             if (offset != null) offset.recycle();
             return consumed;
@@ -1120,6 +1005,18 @@ public class ContentViewCore
         }
     }
 
+    private static boolean isValidTouchEventActionForNative(int eventAction) {
+        // Only these actions have any effect on gesture detection.  Other
+        // actions have no corresponding WebTouchEvent type and may confuse the
+        // touch pipline, so we ignore them entirely.
+        return eventAction == MotionEvent.ACTION_DOWN
+                || eventAction == MotionEvent.ACTION_UP
+                || eventAction == MotionEvent.ACTION_CANCEL
+                || eventAction == MotionEvent.ACTION_MOVE
+                || eventAction == MotionEvent.ACTION_POINTER_DOWN
+                || eventAction == MotionEvent.ACTION_POINTER_UP;
+    }
+
     public void setIgnoreRemainingTouchEvents() {
         resetGestureDetection();
     }
@@ -1133,7 +1030,6 @@ public class ContentViewCore
     private void onFlingStartEventConsumed(int vx, int vy) {
         mTouchScrollInProgress = false;
         mPotentiallyActiveFlingCount++;
-        temporarilyHideTextHandles();
         for (mGestureStateListenersIterator.rewind();
                     mGestureStateListenersIterator.hasNext();) {
             mGestureStateListenersIterator.next().onFlingStartGesture(
@@ -1161,7 +1057,7 @@ public class ContentViewCore
     @CalledByNative
     private void onScrollBeginEventAck() {
         mTouchScrollInProgress = true;
-        temporarilyHideTextHandles();
+        hidePastePopup();
         mZoomControlsDelegate.invokeZoomPicker();
         updateGestureStateListener(GestureEventType.SCROLL_START);
     }
@@ -1187,7 +1083,6 @@ public class ContentViewCore
     @SuppressWarnings("unused")
     @CalledByNative
     private void onPinchBeginEventAck() {
-        temporarilyHideTextHandles();
         updateGestureStateListener(GestureEventType.PINCH_BEGIN);
     }
 
@@ -1199,19 +1094,13 @@ public class ContentViewCore
 
     @SuppressWarnings("unused")
     @CalledByNative
-    private void onTapEventNotConsumed(int x, int y) {
+    private void onSingleTapEventAck(boolean consumed, int x, int y) {
         for (mGestureStateListenersIterator.rewind();
                 mGestureStateListenersIterator.hasNext();) {
-            mGestureStateListenersIterator.next().onUnhandledTapEvent(x, y);
+            mGestureStateListenersIterator.next().onSingleTap(consumed, x, y);
         }
     }
 
-    @SuppressWarnings("unused")
-    @CalledByNative
-    private void onDoubleTapEventAck() {
-        temporarilyHideTextHandles();
-    }
-
     /**
      * Called just prior to a tap or press gesture being forwarded to the renderer.
      */
@@ -1299,9 +1188,30 @@ public class ContentViewCore
         }
     }
 
-    /** Callback interface for evaluateJavaScript(). */
-    public interface JavaScriptCallback {
-        void handleJavaScriptResult(String jsonResult);
+    /**
+     * Inserts the provided markup sandboxed into the frame.
+     */
+    public void setupTransitionView(String markup) {
+        assert mWebContents != null;
+        mWebContents.setupTransitionView(markup);
+    }
+
+    /**
+     * Hides transition elements specified by the selector, and activates any
+     * exiting-transition stylesheets.
+     */
+    public void beginExitTransition(String cssSelector) {
+        assert mWebContents != null;
+        mWebContents.beginExitTransition(cssSelector);
+    }
+
+    /**
+     * Requests the renderer insert a link to the specified stylesheet in the
+     * main frame's document.
+     */
+    public void addStyleSheetByURL(String url) {
+        assert mWebContents != null;
+        mWebContents.addStyleSheetByURL(url);
     }
 
     /**
@@ -1316,34 +1226,42 @@ public class ContentViewCore
      *                 If no result is required, pass null.
      */
     public void evaluateJavaScript(String script, JavaScriptCallback callback) {
-        if (mNativeContentViewCore == 0) return;
-        nativeEvaluateJavaScript(mNativeContentViewCore, script, callback, false);
+        assert mWebContents != null;
+        mWebContents.evaluateJavaScript(script, callback);
     }
 
     /**
-     * Injects the passed Javascript code in the current page and evaluates it.
-     * If there is no page existing, a new one will be created.
+     * Post a message to a frame.
+     * TODO(sgurun) also add support for transferring a message channel port.
      *
-     * @param script The Javascript to execute.
-     */
-    public void evaluateJavaScriptEvenIfNotYetNavigated(String script) {
+     * @param frameName The name of the frame. If the name is null the message is posted
+     *                  to the main frame.
+     * @param message   The message
+     * @param sourceOrigin  The source origin
+     * @param targetOrigin  The target origin
+     */
+    public void postMessageToFrame(String frameName, String message,
+            String sourceOrigin, String targetOrigin) {
         if (mNativeContentViewCore == 0) return;
-        nativeEvaluateJavaScript(mNativeContentViewCore, script, null, true);
+        nativePostMessageToFrame(mNativeContentViewCore, frameName, message, sourceOrigin,
+            targetOrigin);
     }
 
     /**
      * To be called when the ContentView is shown.
      */
     public void onShow() {
-        assert mNativeContentViewCore != 0;
-        nativeOnShow(mNativeContentViewCore);
+        assert mWebContents != null;
+        mWebContents.onShow();
         setAccessibilityState(mAccessibilityManager.isEnabled());
+        restoreSelectionPopupsIfNecessary();
     }
 
     /**
      * @return The ID of the renderer process that backs this tab or
      *         {@link #INVALID_RENDER_PROCESS_PID} if there is none.
      */
+    @VisibleForTesting
     public int getCurrentRenderProcessId() {
         return nativeGetCurrentRenderProcessId(mNativeContentViewCore);
     }
@@ -1352,10 +1270,10 @@ public class ContentViewCore
      * To be called when the ContentView is hidden.
      */
     public void onHide() {
-        assert mNativeContentViewCore != 0;
-        hidePopups();
+        assert mWebContents != null;
+        hidePopupsAndPreserveSelection();
         setInjectedAccessibility(false);
-        nativeOnHide(mNativeContentViewCore);
+        mWebContents.onHide();
     }
 
     /**
@@ -1368,10 +1286,26 @@ public class ContentViewCore
         return mContentSettings;
     }
 
+    private void hidePopupsAndClearSelection() {
+        mUnselectAllOnActionModeDismiss = true;
+        hidePopups();
+    }
+
+    private void hidePopupsAndPreserveSelection() {
+        mUnselectAllOnActionModeDismiss = false;
+        hidePopups();
+    }
+
     private void hidePopups() {
-        hideSelectPopup();
-        hideHandles();
         hideSelectActionBar();
+        hidePastePopup();
+        hideSelectPopup();
+        mPopupZoomer.hide(false);
+        if (mUnselectAllOnActionModeDismiss) hideTextHandles();
+    }
+
+    private void restoreSelectionPopupsIfNecessary() {
+        if (mHasSelection && mActionMode == null) showSelectActionBar();
     }
 
     public void hideSelectActionBar() {
@@ -1396,8 +1330,9 @@ public class ContentViewCore
     @SuppressWarnings("javadoc")
     public void onAttachedToWindow() {
         setAccessibilityState(mAccessibilityManager.isEnabled());
-
+        restoreSelectionPopupsIfNecessary();
         ScreenOrientationListener.getInstance().addObserver(this, mContext);
+        GamepadList.onAttachedToWindow(mContext);
     }
 
     /**
@@ -1407,11 +1342,12 @@ public class ContentViewCore
     @SuppressLint("MissingSuperCall")
     public void onDetachedFromWindow() {
         setInjectedAccessibility(false);
-        hidePopups();
+        hidePopupsAndPreserveSelection();
         mZoomControlsDelegate.dismissZoomPicker();
         unregisterAccessibilityContentObserver();
 
         ScreenOrientationListener.getInstance().removeObserver(this);
+        GamepadList.onDetachedFromWindow();
     }
 
     /**
@@ -1465,7 +1401,7 @@ public class ContentViewCore
         if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) {
             if (mNativeContentViewCore != 0) {
                 mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore),
-                        ImeAdapter.getTextInputTypeNone());
+                        ImeAdapter.getTextInputTypeNone(), 0 /* no flags */);
             }
             mInputMethodManagerWrapper.restartInput(mContainerView);
         }
@@ -1494,15 +1430,6 @@ public class ContentViewCore
     }
 
     /**
-     * Called when the ContentView's position in the activity window changed. This information is
-     * used for cropping screenshots.
-     */
-    public void onLocationInWindowChanged(int x, int y) {
-        mLocationInWindowX = x;
-        mLocationInWindowY = y;
-    }
-
-    /**
      * Called when the underlying surface the compositor draws to changes size.
      * This may be larger than the viewport size.
      */
@@ -1517,18 +1444,8 @@ public class ContentViewCore
         }
     }
 
-    /**
-     * Called when the amount the surface is overdrawing off the bottom has changed.
-     * @param overdrawHeightPix The overdraw height.
-     */
+    /* TODO(aelias): Remove this after downstream callers disappear. */
     public void onOverdrawBottomHeightChanged(int overdrawHeightPix) {
-        if (mOverdrawBottomHeightPix == overdrawHeightPix) return;
-
-        mOverdrawBottomHeightPix = overdrawHeightPix;
-
-        if (mNativeContentViewCore != 0) {
-            nativeWasResized(mNativeContentViewCore);
-        }
     }
 
     private void updateAfterSizeChanged() {
@@ -1556,11 +1473,17 @@ public class ContentViewCore
     }
 
     private void scrollFocusedEditableNodeIntoView() {
-        if (mNativeContentViewCore == 0) return;
-        // 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.
-        nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore);
+        assert mWebContents != null;
+        mWebContents.scrollFocusedEditableNodeIntoView();
+    }
+
+    /**
+     * Selects the word around the caret, if any.
+     * The caller can check if selection actually occurred by listening to OnSelectionChanged.
+     */
+    public void selectWordAroundCaret() {
+        assert mWebContents != null;
+        mWebContents.selectWordAroundCaret();
     }
 
     /**
@@ -1571,8 +1494,17 @@ public class ContentViewCore
     }
 
     public void onFocusChanged(boolean gainFocus) {
-        if (!gainFocus) {
+        if (gainFocus) {
+            restoreSelectionPopupsIfNecessary();
+        } else {
             hideImeIfNeeded();
+            cancelRequestToScrollFocusedEditableNodeIntoView();
+            if (mPreserveSelectionOnNextLossOfFocus) {
+                mPreserveSelectionOnNextLossOfFocus = false;
+                hidePopupsAndPreserveSelection();
+            } else {
+                hidePopupsAndClearSelection();
+            }
         }
         if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus);
     }
@@ -1604,6 +1536,7 @@ public class ContentViewCore
      * @see View#dispatchKeyEvent(KeyEvent)
      */
     public boolean dispatchKeyEvent(KeyEvent event) {
+        if (GamepadList.dispatchKeyEvent(event)) return true;
         if (getContentViewClient().shouldOverrideKeyEvent(event)) {
             return mContainerViewInternals.super_dispatchKeyEvent(event);
         }
@@ -1649,6 +1582,7 @@ public class ContentViewCore
      * @see View#onGenericMotionEvent(MotionEvent)
      */
     public boolean onGenericMotionEvent(MotionEvent event) {
+        if (GamepadList.onGenericMotionEvent(event)) return true;
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
             switch (event.getAction()) {
                 case MotionEvent.ACTION_SCROLL:
@@ -1818,36 +1752,29 @@ public class ContentViewCore
 
         if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix);
 
-        if (type == GestureEventType.LONG_PRESS
-                || type == GestureEventType.LONG_TAP) {
-            getInsertionHandleController().allowAutomaticShowing();
-            getSelectionHandleController().allowAutomaticShowing();
-        } else {
-            setClickXAndY((int) xPix, (int) yPix);
-            if (mSelectionEditable) getInsertionHandleController().allowAutomaticShowing();
-        }
-    }
-
-    private void setClickXAndY(int x, int y) {
-        mSingleTapX = x;
-        mSingleTapY = y;
+        mLastTapX = (int) xPix;
+        mLastTapY = (int) yPix;
     }
 
     /**
-     * @return The x coordinate for the last point that a singleTap gesture was initiated from.
+     * @return The x coordinate for the last point that a tap or press gesture was initiated from.
      */
-    public int getSingleTapX()  {
-        return mSingleTapX;
+    public int getLastTapX()  {
+        return mLastTapX;
     }
 
     /**
-     * @return The y coordinate for the last point that a singleTap gesture was initiated from.
+     * @return The y coordinate for the last point that a tap or press gesture was initiated from.
      */
-    public int getSingleTapY()  {
-        return mSingleTapY;
+    public int getLastTapY()  {
+        return mLastTapY;
     }
 
     public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) {
+        if (zoomControlsDelegate == null) {
+            mZoomControlsDelegate = NO_OP_ZOOM_CONTROLS_DELEGATE;
+            return;
+        }
         mZoomControlsDelegate = zoomControlsDelegate;
     }
 
@@ -1863,8 +1790,10 @@ public class ContentViewCore
 
     public void selectPopupMenuItems(int[] indices) {
         if (mNativeContentViewCore != 0) {
-            nativeSelectPopupMenuItems(mNativeContentViewCore, indices);
+            nativeSelectPopupMenuItems(mNativeContentViewCore, mNativeSelectPopupSourceFrame,
+                                       indices);
         }
+        mNativeSelectPopupSourceFrame = 0;
         mSelectPopup = null;
     }
 
@@ -1893,105 +1822,6 @@ public class ContentViewCore
         return mDownloadDelegate;
     }
 
-    private SelectionHandleController getSelectionHandleController() {
-        if (mSelectionHandleController == null) {
-            mSelectionHandleController = new SelectionHandleController(
-                    getContainerView(), mPositionObserver) {
-                @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(), mPositionObserver) {
-                private static final int AVERAGE_LINE_HEIGHT = 14;
-
-                @Override
-                public void setCursorPosition(int x, int y) {
-                    if (mNativeContentViewCore != 0) {
-                        nativeMoveCaret(mNativeContentViewCore,
-                                x, y - mRenderCoordinates.getContentOffsetYPix());
-                    }
-                }
-
-                @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();
-        }
-        mPositionObserver.removeListener(mPositionListener);
-    }
-
     private void showSelectActionBar() {
         if (mActionMode != null) {
             mActionMode.invalidate();
@@ -2065,14 +1895,27 @@ public class ContentViewCore
             }
 
             @Override
+            public boolean isSelectionPassword() {
+                return mImeAdapter.isSelectionPassword();
+            }
+
+            @Override
             public boolean isSelectionEditable() {
-                return mSelectionEditable;
+                return mFocusedNodeEditable;
             }
 
             @Override
             public void onDestroyActionMode() {
                 mActionMode = null;
-                if (mUnselectAllOnActionModeDismiss) mImeAdapter.unselect();
+                if (mUnselectAllOnActionModeDismiss) {
+                    hideTextHandles();
+                    if (isSelectionEditable()) {
+                        int selectionEnd = Selection.getSelectionEnd(mEditable);
+                        mInputConnection.setSelection(selectionEnd, selectionEnd);
+                    } else {
+                        mImeAdapter.unselect();
+                    }
+                }
                 getContentViewClient().onContextualActionBarHidden();
             }
 
@@ -2096,9 +1939,10 @@ public class ContentViewCore
         mActionMode = null;
         // On ICS, startActionMode throws an NPE when getParent() is null.
         if (mContainerView.getParent() != null) {
+            assert mWebContents != null;
             mActionMode = mContainerView.startActionMode(
                     getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
-                            nativeIsIncognito(mNativeContentViewCore)));
+                            mWebContents.isIncognito()));
         }
         mUnselectAllOnActionModeDismiss = true;
         if (mActionMode == null) {
@@ -2109,88 +1953,100 @@ public class ContentViewCore
         }
     }
 
-    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.
+     * Clears the current text selection.
      */
-    public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) {
-        if (mNativeContentViewCore != 0) {
-            nativeSetUseDesktopUserAgent(mNativeContentViewCore, override, reloadOnChange);
-        }
+    public void clearSelection() {
+        mImeAdapter.unselect();
     }
 
-    public void clearSslPreferences() {
-        if (mNativeContentViewCore != 0) nativeClearSslPreferences(mNativeContentViewCore);
+    /**
+     * Ensure the selection is preserved the next time the view loses focus.
+     */
+    public void preserveSelectionOnNextLossOfFocus() {
+        mPreserveSelectionOnNextLossOfFocus = true;
     }
 
-    private boolean isSelectionHandleShowing() {
-        return mSelectionHandleController != null && mSelectionHandleController.isShowing();
+    /**
+     * @return Whether the page has an active, touch-controlled selection region.
+     */
+    @VisibleForTesting
+    public boolean hasSelection() {
+        return mHasSelection;
     }
 
-    private boolean isInsertionHandleShowing() {
-        return mInsertionHandleController != null && mInsertionHandleController.isShowing();
+    private void hidePastePopup() {
+        if (mPastePopupMenu == null) return;
+        mPastePopupMenu.hide();
     }
 
-    // Makes the insertion/selection handles invisible. They will fade back in shortly after the
-    // last call to scheduleTextHandleFadeIn (or temporarilyHideTextHandles).
-    private void temporarilyHideTextHandles() {
-        if (isSelectionHandleShowing() && !mSelectionHandleController.isDragging()) {
-            mSelectionHandleController.setHandleVisibility(HandleView.INVISIBLE);
-        }
-        if (isInsertionHandleShowing() && !mInsertionHandleController.isDragging()) {
-            mInsertionHandleController.setHandleVisibility(HandleView.INVISIBLE);
-        }
-        scheduleTextHandleFadeIn();
-    }
+    @CalledByNative
+    private void onSelectionEvent(int eventType, float posXDip, float posYDip) {
+        switch (eventType) {
+            case SelectionEventType.SELECTION_SHOWN:
+                mHasSelection = true;
+                mUnselectAllOnActionModeDismiss = true;
+                // TODO(cjhopman): Remove this when there is a better signal that long press caused
+                // a selection. See http://crbug.com/150151.
+                mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+                showSelectActionBar();
+                break;
 
-    private boolean allowTextHandleFadeIn() {
-        if (mTouchScrollInProgress) return false;
+            case SelectionEventType.SELECTION_CLEARED:
+                mHasSelection = false;
+                mUnselectAllOnActionModeDismiss = false;
+                hideSelectActionBar();
+                break;
 
-        if (mPopupZoomer.isShowing()) return false;
+            case SelectionEventType.SELECTION_DRAG_STARTED:
+                break;
 
-        return true;
-    }
+            case SelectionEventType.SELECTION_DRAG_STOPPED:
+                break;
 
-    // Cancels any pending fade in and schedules a new one.
-    private void scheduleTextHandleFadeIn() {
-        if (!isInsertionHandleShowing() && !isSelectionHandleShowing()) return;
+            case SelectionEventType.INSERTION_SHOWN:
+                mHasInsertion = true;
+                break;
 
-        if (mDeferredHandleFadeInRunnable == null) {
-            mDeferredHandleFadeInRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    if (!allowTextHandleFadeIn()) {
-                        // Delay fade in until it is allowed.
-                        scheduleTextHandleFadeIn();
-                    } else {
-                        if (isSelectionHandleShowing()) {
-                            mSelectionHandleController.beginHandleFadeIn();
-                        }
-                        if (isInsertionHandleShowing()) {
-                            mInsertionHandleController.beginHandleFadeIn();
-                        }
-                    }
+            case SelectionEventType.INSERTION_MOVED:
+                if (mPastePopupMenu == null) break;
+                if (!isScrollInProgress() && mPastePopupMenu.isShowing()) {
+                    showPastePopup((int) posXDip, (int) posYDip);
+                } else {
+                    hidePastePopup();
                 }
-            };
+                break;
+
+            case SelectionEventType.INSERTION_TAPPED:
+                if (mWasPastePopupShowingOnInsertionDragStart)
+                    hidePastePopup();
+                else
+                    showPastePopup((int) posXDip, (int) posYDip);
+                break;
+
+            case SelectionEventType.INSERTION_CLEARED:
+                mHasInsertion = false;
+                hidePastePopup();
+                break;
+
+            case SelectionEventType.INSERTION_DRAG_STARTED:
+                mWasPastePopupShowingOnInsertionDragStart =
+                        mPastePopupMenu != null && mPastePopupMenu.isShowing();
+                hidePastePopup();
+                break;
+
+            default:
+                assert false : "Invalid selection event type.";
         }
 
-        mContainerView.removeCallbacks(mDeferredHandleFadeInRunnable);
-        mContainerView.postDelayed(mDeferredHandleFadeInRunnable, TEXT_HANDLE_FADE_IN_DELAY);
+        final float scale = mRenderCoordinates.getDeviceScaleFactor();
+        getContentViewClient().onSelectionEvent(eventType, posXDip * scale, posYDip * scale);
     }
 
-    /**
-     * Shows the IME if the focused widget could accept text input.
-     */
-    public void showImeIfNeeded() {
-        if (mNativeContentViewCore != 0) nativeShowImeIfNeeded(mNativeContentViewCore);
+    private void hideTextHandles() {
+        mHasSelection = false;
+        mHasInsertion = false;
+        if (mNativeContentViewCore != 0) nativeHideTextHandles(mNativeContentViewCore);
     }
 
     /**
@@ -2218,9 +2074,8 @@ public class ContentViewCore
             float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor,
             float contentWidth, float contentHeight,
             float viewportWidth, float viewportHeight,
-            float controlsOffsetYCss, float contentOffsetYCss,
-            float overdrawBottomHeightCss) {
-        TraceEvent.instant("ContentViewCore:updateFrameInfo");
+            float controlsOffsetYCss, float contentOffsetYCss) {
+        TraceEvent.begin("ContentViewCore:updateFrameInfo");
         // Adjust contentWidth/Height to be always at least as big as
         // the actual viewport (as set by onSizeChanged).
         final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
@@ -2247,7 +2102,6 @@ public class ContentViewCore
 
         final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged;
         final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged;
-        final boolean needTemporarilyHideHandles = scrollChanged;
 
         if (needHidePopupZoomer) mPopupZoomer.hide(true);
 
@@ -2275,39 +2129,38 @@ public class ContentViewCore
             }
         }
 
-        if (needTemporarilyHideHandles) temporarilyHideTextHandles();
         if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls();
-        if (contentOffsetChanged) updateHandleScreenPositions();
 
         // Update offsets for fullscreen.
         final float controlsOffsetPix = controlsOffsetYCss * deviceScale;
-        final float overdrawBottomHeightPix = overdrawBottomHeightCss * deviceScale;
+        // TODO(aelias): Remove last argument after downstream removes it.
         getContentViewClient().onOffsetsForFullscreenChanged(
-                controlsOffsetPix, contentOffsetYPix, overdrawBottomHeightPix);
+                controlsOffsetPix, contentOffsetYPix, 0);
 
-        mPendingRendererFrame = true;
         if (mBrowserAccessibilityManager != null) {
             mBrowserAccessibilityManager.notifyFrameInfoInitialized();
         }
+        TraceEvent.end("ContentViewCore:updateFrameInfo");
     }
 
     @CalledByNative
     private void updateImeAdapter(long nativeImeAdapterAndroid, int textInputType,
-            String text, int selectionStart, int selectionEnd,
+            int textInputFlags, String text, int selectionStart, int selectionEnd,
             int compositionStart, int compositionEnd, boolean showImeIfNeeded,
             boolean isNonImeChange) {
         TraceEvent.begin();
-        mSelectionEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
-
-        if (mActionMode != null) mActionMode.invalidate();
+        mFocusedNodeEditable = (textInputType != ImeAdapter.getTextInputTypeNone());
+        if (!mFocusedNodeEditable) hidePastePopup();
 
         mImeAdapter.updateKeyboardVisibility(
-                nativeImeAdapterAndroid, textInputType, showImeIfNeeded);
+                nativeImeAdapterAndroid, textInputType, textInputFlags, showImeIfNeeded);
 
         if (mInputConnection != null) {
             mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart,
                     compositionEnd, isNonImeChange);
         }
+
+        if (mActionMode != null) mActionMode.invalidate();
         TraceEvent.end();
     }
 
@@ -2319,6 +2172,7 @@ public class ContentViewCore
 
     /**
      * Called (from native) when the <select> popup needs to be shown.
+     * @param nativeSelectPopupSourceFrame The native RenderFrameHost that owns the popup.
      * @param items           Items to show.
      * @param enabled         POPUP_ITEM_TYPEs for items.
      * @param multiple        Whether the popup menu should support multi-select.
@@ -2326,24 +2180,28 @@ public class ContentViewCore
      */
     @SuppressWarnings("unused")
     @CalledByNative
-    private void showSelectPopup(Rect bounds, String[] items, int[] enabled, boolean multiple,
-            int[] selectedIndices) {
+    private void showSelectPopup(long nativeSelectPopupSourceFrame, Rect bounds, String[] items,
+            int[] enabled, boolean multiple, int[] selectedIndices) {
         if (mContainerView.getParent() == null || mContainerView.getVisibility() != View.VISIBLE) {
+            mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame;
             selectPopupMenuItems(null);
             return;
         }
 
+        hidePopupsAndClearSelection();
+        assert mNativeSelectPopupSourceFrame == 0 : "Zombie popup did not clear the frame source";
+
         assert items.length == enabled.length;
         List<SelectPopupItem> popupItems = new ArrayList<SelectPopupItem>();
         for (int i = 0; i < items.length; i++) {
             popupItems.add(new SelectPopupItem(items[i], enabled[i]));
         }
-        hidePopups();
-        if (DeviceUtils.isTablet(mContext) && !multiple) {
+        if (DeviceFormFactor.isTablet(mContext) && !multiple) {
             mSelectPopup = new SelectPopupDropdown(this, popupItems, bounds, selectedIndices);
         } else {
             mSelectPopup = new SelectPopupDialog(this, popupItems, multiple, selectedIndices);
         }
+        mNativeSelectPopupSourceFrame = nativeSelectPopupSourceFrame;
         mSelectPopup.show();
     }
 
@@ -2367,7 +2225,6 @@ public class ContentViewCore
     private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) {
         mPopupZoomer.setBitmap(zoomedBitmap);
         mPopupZoomer.show(targetRect);
-        temporarilyHideTextHandles();
     }
 
     @SuppressWarnings("unused")
@@ -2378,105 +2235,83 @@ public class ContentViewCore
 
     @SuppressWarnings("unused")
     @CalledByNative
-    private void onSelectionChanged(String text) {
-        mLastSelectedText = text;
-        getContentViewClient().onSelectionChanged(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();
-                if (mInputMethodManagerWrapper.isWatchingCursor(mContainerView)) {
-                    final int xPix = (int) mInsertionHandlePoint.getXPix();
-                    final int yPix = (int) mInsertionHandlePoint.getYPix();
-                    mInputMethodManagerWrapper.updateCursor(
-                            mContainerView, xPix, yPix, xPix, yPix);
+    private PopupTouchHandleDrawable createPopupTouchHandleDrawable() {
+        if (mTouchHandleDelegate == null) {
+            mTouchHandleDelegate = new PopupTouchHandleDrawableDelegate() {
+                @Override
+                public View getParent() {
+                    return getContainerView();
                 }
-            } else {
-                // Deselection
-                if (mSelectionHandleController != null) {
-                    mSelectionHandleController.hideAndDisallowAutomaticShowing();
+
+                @Override
+                public PositionObserver getParentPositionObserver() {
+                    return mPositionObserver;
                 }
-                if (mInsertionHandleController != null) {
-                    mInsertionHandleController.hideAndDisallowAutomaticShowing();
+
+                @Override
+                public boolean onTouchHandleEvent(MotionEvent event) {
+                    final boolean isTouchHandleEvent = true;
+                    return onTouchEventImpl(event, isTouchHandleEvent);
                 }
-            }
-            mHasSelection = false;
-        }
-        if (isSelectionHandleShowing() || isInsertionHandleShowing()) {
-            mPositionObserver.addListener(mPositionListener);
+            };
         }
+        return new PopupTouchHandleDrawable(mTouchHandleDelegate);
     }
 
     @SuppressWarnings("unused")
     @CalledByNative
-    private static void onEvaluateJavaScriptResult(
-            String jsonResult, JavaScriptCallback callback) {
-        callback.handleJavaScriptResult(jsonResult);
+    private void onSelectionChanged(String text) {
+        mLastSelectedText = text;
+        getContentViewClient().onSelectionChanged(text);
     }
 
     @SuppressWarnings("unused")
     @CalledByNative
-    private void showPastePopup(int xDip, int yDip) {
-        mInsertionHandlePoint.setLocalDip(xDip, yDip);
-        getInsertionHandleController().showHandle();
-        updateHandleScreenPositions();
-        getInsertionHandleController().showHandleWithPastePopup();
+    private void showPastePopupWithFeedback(int xDip, int yDip) {
+        // TODO(jdduke): Remove this when there is a better signal that long press caused
+        // showing of the paste popup. See http://crbug.com/150151.
+        if (showPastePopup(xDip, yDip)) {
+            mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+        }
     }
 
-    @SuppressWarnings("unused")
-    @CalledByNative
-    private void onRenderProcessSwap() {
-        attachImeAdapter();
+    private boolean showPastePopup(int xDip, int yDip) {
+        if (!mHasInsertion || !canPaste()) return false;
+        final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix();
+        getPastePopup().showAt(
+            (int) mRenderCoordinates.fromDipToPix(xDip),
+            (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
+        return true;
+    }
+
+    private PastePopupMenu getPastePopup() {
+        if (mPastePopupMenu == null) {
+            mPastePopupMenu = new PastePopupMenu(getContainerView(),
+                new PastePopupMenuDelegate() {
+                    @Override
+                    public void paste() {
+                        mImeAdapter.paste();
+                        hideTextHandles();
+                    }
+                });
+        }
+        return mPastePopupMenu;
+    }
+
+    @VisibleForTesting
+    public PastePopupMenu getPastePopupForTest() {
+        return getPastePopup();
+    }
+
+    private boolean canPaste() {
+        if (!mFocusedNodeEditable) return false;
+        return ((ClipboardManager) mContext.getSystemService(
+                Context.CLIPBOARD_SERVICE)).hasPrimaryClip();
     }
 
     @SuppressWarnings("unused")
     @CalledByNative
-    private void onWebContentsConnected() {
+    private void onRenderProcessChange() {
         attachImeAdapter();
     }
 
@@ -2610,6 +2445,16 @@ public class ContentViewCore
     }
 
     /**
+     * Returns JavaScript interface objects previously injected via
+     * {@link #addJavascriptInterface(Object, String)}.
+     *
+     * @return the mapping of names to interface objects and corresponding annotation classes
+     */
+    public Map<String, Pair<Object, Class>> getJavascriptInterfaces() {
+        return mJavaScriptInterfaces;
+    }
+
+    /**
      * This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)}
      * and automatically pass in {@link JavascriptInterface} as the required annotation.
      *
@@ -2666,9 +2511,8 @@ public class ContentViewCore
     public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
             Class<? extends Annotation> requiredAnnotation) {
         if (mNativeContentViewCore != 0 && object != null) {
-            mJavaScriptInterfaces.put(name, object);
-            nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation,
-                    mRetainedJavaScriptObjects);
+            mJavaScriptInterfaces.put(name, new Pair<Object, Class>(object, requiredAnnotation));
+            nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation);
         }
     }
 
@@ -2688,6 +2532,7 @@ public class ContentViewCore
      * Return the current scale of the ContentView.
      * @return The current page scale factor.
      */
+    @VisibleForTesting
     public float getScale() {
         return mRenderCoordinates.getPageScaleFactor();
     }
@@ -2699,8 +2544,8 @@ public class ContentViewCore
      * once the texture is actually ready.
      */
     public boolean isReady() {
-        if (mNativeContentViewCore == 0) return false;
-        return nativeIsRenderWidgetHostViewReady(mNativeContentViewCore);
+        assert mWebContents != null;
+        return mWebContents.isReady();
     }
 
     @CalledByNative
@@ -2809,8 +2654,8 @@ public class ContentViewCore
         event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0);
 
         // Setting the maximum scroll values requires API level 15 or higher.
-        final int SDK_VERSION_REQUIRED_TO_SET_SCROLL = 15;
-        if (Build.VERSION.SDK_INT >= SDK_VERSION_REQUIRED_TO_SET_SCROLL) {
+        final int sdkVersionRequiredToSetScroll = 15;
+        if (Build.VERSION.SDK_INT >= sdkVersionRequiredToSetScroll) {
             event.setMaxScrollX(maxScrollXPix);
             event.setMaxScrollY(maxScrollYPix);
         }
@@ -2916,10 +2761,28 @@ public class ContentViewCore
     }
 
     /**
+     * Return whether or not we should set accessibility focus on page load.
+     */
+    public boolean shouldSetAccessibilityFocusOnPageLoad() {
+        return mShouldSetAccessibilityFocusOnPageLoad;
+    }
+
+    /**
+     * Sets whether or not we should set accessibility focus on page load.
+     * This only applies if an accessibility service like TalkBack is running.
+     * This is desirable behavior for a browser window, but not for an embedded
+     * WebView.
+     */
+    public void setShouldSetAccessibilityFocusOnPageLoad(boolean on) {
+        mShouldSetAccessibilityFocusOnPageLoad = on;
+    }
+
+    /**
      * Inform WebKit that Fullscreen mode has been exited by the user.
      */
     public void exitFullscreen() {
-        if (mNativeContentViewCore != 0) nativeExitFullscreen(mNativeContentViewCore);
+        assert mWebContents != null;
+        mWebContents.exitFullscreen();
     }
 
     /**
@@ -2931,54 +2794,9 @@ public class ContentViewCore
      */
     public void updateTopControlsState(boolean enableHiding, boolean enableShowing,
             boolean animate) {
-        if (mNativeContentViewCore != 0) {
-            nativeUpdateTopControlsState(
-                    mNativeContentViewCore, enableHiding, enableShowing, animate);
-        }
-    }
-
-    /**
-     * Callback factory method for nativeGetNavigationHistory().
-     */
-    @CalledByNative
-    private void addToNavigationHistory(Object history, int index, String url, String virtualUrl,
-            String originalUrl, String title, Bitmap favicon) {
-        NavigationEntry entry = new NavigationEntry(
-                index, url, virtualUrl, originalUrl, title, favicon);
-        ((NavigationHistory) history).addEntry(entry);
-    }
-
-    /**
-     * Get a copy of the navigation history of the view.
-     */
-    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 "";
+        assert mWebContents != null;
+        mWebContents.updateTopControlsState(
+                enableHiding, enableShowing, animate);
     }
 
     /**
@@ -2989,30 +2807,49 @@ public class ContentViewCore
     }
 
     @CalledByNative
-    private int getLocationInWindowX() {
-        return mLocationInWindowX;
-    }
-
-    @CalledByNative
-    private int getLocationInWindowY() {
-        return mLocationInWindowY;
-    }
-
-    @CalledByNative
     private static Rect createRect(int x, int y, int right, int bottom) {
         return new Rect(x, y, right, bottom);
     }
 
     public void extractSmartClipData(int x, int y, int width, int height) {
         if (mNativeContentViewCore != 0) {
+            x += mSmartClipOffsetX;
+            y += mSmartClipOffsetY;
             nativeExtractSmartClipData(mNativeContentViewCore, x, y, width, height);
         }
     }
 
+    /**
+     * Set offsets for smart clip.
+     *
+     * <p>This should be called if there is a viewport change introduced by,
+     * e.g., show and hide of a location bar.
+     *
+     * @param offsetX Offset for X position.
+     * @param offsetY Offset for Y position.
+     */
+    public void setSmartClipOffsets(int offsetX, int offsetY) {
+        mSmartClipOffsetX = offsetX;
+        mSmartClipOffsetY = offsetY;
+    }
+
     @CalledByNative
-    private void onSmartClipDataExtracted(String result) {
-        if (mSmartClipDataListener != null ) {
-            mSmartClipDataListener.onSmartClipDataExtracted(result);
+    private void onSmartClipDataExtracted(String text, String html, Rect clipRect) {
+        // Translate the positions by the offsets introduced by location bar. Note that the
+        // coordinates are in dp scale, and that this definitely has the potential to be
+        // different from the offsets when extractSmartClipData() was called. However,
+        // as long as OEM has a UI that consumes all the inputs and waits until the
+        // callback is called, then there shouldn't be any difference.
+        // TODO(changwan): once crbug.com/416432 is resolved, try to pass offsets as
+        // separate params for extractSmartClipData(), and apply them not the new offset
+        // values in the callback.
+        final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
+        final int offsetXInDp = (int) (mSmartClipOffsetX / deviceScale);
+        final int offsetYInDp = (int) (mSmartClipOffsetY / deviceScale);
+        clipRect.offset(-offsetXInDp, -offsetYInDp);
+
+        if (mSmartClipDataListener != null) {
+            mSmartClipDataListener.onSmartClipDataExtracted(text, html, clipRect);
         }
     }
 
@@ -3020,6 +2857,12 @@ public class ContentViewCore
         mSmartClipDataListener = listener;
     }
 
+    public void setBackgroundOpaque(boolean opaque) {
+        if (mNativeContentViewCore != 0) {
+            nativeSetBackgroundOpaque(mNativeContentViewCore, opaque);
+        }
+    }
+
     /**
      * Offer a long press gesture to the embedding View, primarily for WebView compatibility.
      *
@@ -3047,7 +2890,7 @@ public class ContentViewCore
     }
 
     private native long nativeInit(long webContentsPtr,
-            long viewAndroidPtr, long windowAndroidPtr);
+            long viewAndroidPtr, long windowAndroidPtr, HashSet<Object> retainedObjectSet);
 
     @CalledByNative
     private ContentVideoViewClient getContentVideoViewClient() {
@@ -3074,31 +2917,27 @@ public class ContentViewCore
         sendOrientationChangeEvent(orientation);
     }
 
-    private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
-
-    private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
+    public void resumeResponseDeferredAtStart() {
+        assert mWebContents != null;
+        mWebContents.resumeResponseDeferredAtStart();
+    }
 
-    private native void nativeLoadUrl(
-            long nativeContentViewCoreImpl,
-            String url,
-            int loadUrlType,
-            int transitionType,
-            String referrerUrl,
-            int referrerPolicy,
-            int uaOverrideOption,
-            String extraHeaders,
-            byte[] postData,
-            String baseUrlForDataUrl,
-            String virtualUrlForDataUrl,
-            boolean canLoadLocalResources);
+    /**
+     * Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock
+     * the screen orientation.
+     */
+    public void setFullscreenRequiredForOrientationLock(boolean value) {
+        mFullscreenRequiredForOrientationLock = value;
+    }
 
-    private native String nativeGetURL(long nativeContentViewCoreImpl);
+    @CalledByNative
+    private boolean isFullscreenRequiredForOrientationLock() {
+        return mFullscreenRequiredForOrientationLock;
+    }
 
-    private native void nativeShowInterstitialPage(
-            long nativeContentViewCoreImpl, String url, long nativeInterstitialPageDelegateAndroid);
-    private native boolean nativeIsShowingInterstitialPage(long nativeContentViewCoreImpl);
+    private native WebContents nativeGetWebContentsAndroid(long nativeContentViewCoreImpl);
 
-    private native boolean nativeIsIncognito(long nativeContentViewCoreImpl);
+    private native void nativeOnJavaContentViewCoreDestroyed(long nativeContentViewCoreImpl);
 
     private native void nativeSetFocus(long nativeContentViewCoreImpl, boolean focused);
 
@@ -3111,7 +2950,13 @@ public class ContentViewCore
             long timeMs, int action, int pointerCount, int historySize, int actionIndex,
             float x0, float y0, float x1, float y1,
             int pointerId0, int pointerId1,
-            float touchMajor0, float touchMajor1);
+            float touchMajor0, float touchMajor1,
+            float touchMinor0, float touchMinor1,
+            float orientation0, float orientation1,
+            float rawX, float rawY,
+            int androidToolType0, int androidToolType1,
+            int androidButtonState, int androidMetaState,
+            boolean isTouchHandleEvent);
 
     private native int nativeSendMouseMoveEvent(
             long nativeContentViewCoreImpl, long timeMs, float x, float y);
@@ -3156,75 +3001,43 @@ public class ContentViewCore
 
     private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
 
+    private native void nativeHideTextHandles(long nativeContentViewCoreImpl);
+
     private native void nativeResetGestureDetection(long nativeContentViewCoreImpl);
+
     private native void nativeSetDoubleTapSupportEnabled(
             long nativeContentViewCoreImpl, boolean enabled);
+
     private native void nativeSetMultiTouchZoomSupportEnabled(
             long nativeContentViewCoreImpl, boolean enabled);
 
-    private native void nativeLoadIfNecessary(long nativeContentViewCoreImpl);
-    private native void nativeRequestRestoreLoad(long nativeContentViewCoreImpl);
-
-    private native void nativeReload(long nativeContentViewCoreImpl, boolean checkForRepost);
-    private native void nativeReloadIgnoringCache(
-            long nativeContentViewCoreImpl, boolean checkForRepost);
-
-    private native void nativeCancelPendingReload(long nativeContentViewCoreImpl);
-
-    private native void nativeContinuePendingReload(long nativeContentViewCoreImpl);
+    private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl,
+            long nativeSelectPopupSourceFrame, int[] indices);
 
-    private native void nativeSelectPopupMenuItems(long nativeContentViewCoreImpl, int[] indices);
 
-    private native void nativeScrollFocusedEditableNodeIntoView(long nativeContentViewCoreImpl);
-
-    private native void nativeClearHistory(long nativeContentViewCoreImpl);
-
-    private native void nativeEvaluateJavaScript(long nativeContentViewCoreImpl,
-            String script, JavaScriptCallback callback, boolean startRenderer);
+    private native void nativePostMessageToFrame(long nativeContentViewCoreImpl, String frameId,
+            String message, String sourceOrigin, String targetOrigin);
 
     private native long nativeGetNativeImeAdapter(long nativeContentViewCoreImpl);
 
     private native int nativeGetCurrentRenderProcessId(long nativeContentViewCoreImpl);
 
-    private native int nativeGetBackgroundColor(long nativeContentViewCoreImpl);
-
-    private native void nativeOnShow(long nativeContentViewCoreImpl);
-    private native void nativeOnHide(long nativeContentViewCoreImpl);
-
-    private native void nativeSetUseDesktopUserAgent(long nativeContentViewCoreImpl,
-            boolean enabled, boolean reloadOnChange);
-    private native boolean nativeGetUseDesktopUserAgent(long nativeContentViewCoreImpl);
-
-    private native void nativeClearSslPreferences(long nativeContentViewCoreImpl);
-
     private native void nativeSetAllowJavascriptInterfacesInspection(
             long nativeContentViewCoreImpl, boolean allow);
 
     private native void nativeAddJavascriptInterface(long nativeContentViewCoreImpl, Object object,
-            String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet);
+            String name, Class requiredAnnotation);
 
     private native void nativeRemoveJavascriptInterface(long nativeContentViewCoreImpl,
             String name);
 
-    private native int nativeGetNavigationHistory(long nativeContentViewCoreImpl, Object context);
-    private native void nativeGetDirectedNavigationHistory(long nativeContentViewCoreImpl,
-            Object context, boolean isForward, int maxEntries);
-    private native String nativeGetOriginalUrlForActiveNavigationEntry(
-            long nativeContentViewCoreImpl);
-
     private native void nativeWasResized(long nativeContentViewCoreImpl);
 
-    private native boolean nativeIsRenderWidgetHostViewReady(long nativeContentViewCoreImpl);
-
-    private native void nativeExitFullscreen(long nativeContentViewCoreImpl);
-    private native void nativeUpdateTopControlsState(long nativeContentViewCoreImpl,
-            boolean enableHiding, boolean enableShowing, boolean animate);
-
-    private native void nativeShowImeIfNeeded(long nativeContentViewCoreImpl);
-
     private native void nativeSetAccessibilityEnabled(
             long nativeContentViewCoreImpl, boolean enabled);
 
     private native void nativeExtractSmartClipData(long nativeContentViewCoreImpl,
             int x, int y, int w, int h);
+
+    private native void nativeSetBackgroundOpaque(long nativeContentViewCoreImpl, boolean opaque);
 }