Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / content / public / android / java / src / org / chromium / content / browser / ContentViewCore.java
index dc18f6e..8e4645f 100644 (file)
@@ -75,7 +75,6 @@ import org.chromium.content.browser.input.SelectPopupItem;
 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;
@@ -88,8 +87,10 @@ import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Provides a Java-side 'wrapper' around a WebContent (native) instance.
@@ -144,6 +145,136 @@ public class ContentViewCore
     private final HashSet<Object> mRetainedJavaScriptObjects = new HashSet<Object>();
 
     /**
+     * A {@link ViewAndroidDelegate} that delegates to the current container view.
+     *
+     * <p>This delegate handles the replacement of container views transparently so
+     * that clients can safely hold to instances of this class.
+     */
+    private class ContentViewAndroidDelegate implements ViewAndroidDelegate {
+        /**
+         * Represents the position of an anchor view.
+         */
+        @VisibleForTesting
+        private class Position {
+            private final float mX;
+            private final float mY;
+            private final float mWidth;
+            private final float mHeight;
+
+            public Position(float x, float y, float width, float height) {
+                mX = x;
+                mY = y;
+                mWidth = width;
+                mHeight = height;
+            }
+        }
+
+        /**
+         * The current container view. This view can be updated with
+         * {@link #updateCurrentContainerView()}.
+         */
+        private ViewGroup mCurrentContainerView;
+
+        /**
+         * List of anchor views stored in the order in which they were acquired mapped
+         * to their position.
+         */
+        private Map<View, Position> mAnchorViews = new LinkedHashMap<View, Position>();
+
+        @Override
+        public View acquireAnchorView() {
+            View anchorView = new View(mContext);
+            mAnchorViews.put(anchorView, null);
+            mCurrentContainerView.addView(anchorView);
+            return anchorView;
+        }
+
+        @Override
+        public void setAnchorViewPosition(
+                View view, float x, float y, float width, float height) {
+            mAnchorViews.put(view, new Position(x, y, width, height));
+            doSetAnchorViewPosition(view, x, y, width, height);
+        }
+
+        @SuppressWarnings("deprecation")  // AbsoluteLayout
+        private void doSetAnchorViewPosition(
+                View view, float x, float y, float width, float height) {
+            if (view.getParent() == null) {
+                // Ignore. setAnchorViewPosition has been called after the anchor view has
+                // already been released.
+                return;
+            }
+            assert view.getParent() == mCurrentContainerView;
+
+            float scale = (float) DeviceDisplayInfo.create(mContext).getDIPScale();
+
+            // The anchor view should not go outside the bounds of the ContainerView.
+            int leftMargin = Math.round(x * scale);
+            int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale);
+            int scaledWidth = Math.round(width * scale);
+            // ContentViewCore currently only supports these two container view types.
+            if (mCurrentContainerView instanceof FrameLayout) {
+                int startMargin;
+                if (ApiCompatibilityUtils.isLayoutRtl(mCurrentContainerView)) {
+                    startMargin = mCurrentContainerView.getMeasuredWidth()
+                            - Math.round((width + x) * scale);
+                } else {
+                    startMargin = leftMargin;
+                }
+                if (scaledWidth + startMargin > mCurrentContainerView.getWidth()) {
+                    scaledWidth = mCurrentContainerView.getWidth() - startMargin;
+                }
+                FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
+                        scaledWidth, Math.round(height * scale));
+                ApiCompatibilityUtils.setMarginStart(lp, startMargin);
+                lp.topMargin = topMargin;
+                view.setLayoutParams(lp);
+            } else if (mCurrentContainerView instanceof android.widget.AbsoluteLayout) {
+                // This fixes the offset due to a difference in
+                // scrolling model of WebView vs. Chrome.
+                // TODO(sgurun) fix this to use mContainerViewAtCreation.getScroll[X/Y]()
+                // as it naturally accounts for scroll differences between
+                // these models.
+                leftMargin += mRenderCoordinates.getScrollXPixInt();
+                topMargin += mRenderCoordinates.getScrollYPixInt();
+
+                android.widget.AbsoluteLayout.LayoutParams lp =
+                        new android.widget.AbsoluteLayout.LayoutParams(
+                            scaledWidth, (int) (height * scale), leftMargin, topMargin);
+                view.setLayoutParams(lp);
+            } else {
+                Log.e(TAG, "Unknown layout " + mCurrentContainerView.getClass().getName());
+            }
+        }
+
+        @Override
+        public void releaseAnchorView(View anchorView) {
+            mAnchorViews.remove(anchorView);
+            mCurrentContainerView.removeView(anchorView);
+        }
+
+        /**
+         * Updates (or sets for the first time) the current container view to which
+         * this class delegates. Existing anchor views are transferred from the old to
+         * the new container view.
+         */
+        void updateCurrentContainerView() {
+            ViewGroup oldContainerView = mCurrentContainerView;
+            mCurrentContainerView = mContainerView;
+            for (Entry<View, Position> entry : mAnchorViews.entrySet()) {
+                View anchorView = entry.getKey();
+                Position position = entry.getValue();
+                oldContainerView.removeView(anchorView);
+                mCurrentContainerView.addView(anchorView);
+                if (position != null) {
+                    doSetAnchorViewPosition(anchorView,
+                            position.mX, position.mY, position.mWidth, position.mHeight);
+                }
+            }
+        }
+    }
+
+    /**
      * Interface that consumers of {@link ContentViewCore} must implement to allow the proper
      * dispatching of view methods through the containing view.
      *
@@ -231,7 +362,7 @@ public class ContentViewCore
     private ViewGroup mContainerView;
     private InternalAccessDelegate mContainerViewInternals;
     private WebContents mWebContents;
-    private WebContentsObserverAndroid mWebContentsObserver;
+    private WebContentsObserver mWebContentsObserver;
 
     private ContentViewClient mContentViewClient;
 
@@ -284,6 +415,7 @@ public class ContentViewCore
     private ActionMode mActionMode;
     private boolean mUnselectAllOnActionModeDismiss;
     private boolean mPreserveSelectionOnNextLossOfFocus;
+    private SelectActionModeCallback.ActionHandler mActionHandler;
 
     // Delegate that will handle GET downloads, and be notified of completion of POST downloads.
     private ContentViewDownloadDelegate mDownloadDelegate;
@@ -361,6 +493,9 @@ public class ContentViewCore
     // screen orientation.
     private boolean mFullscreenRequiredForOrientationLock = true;
 
+    // A ViewAndroidDelegate that delegates to the current container view.
+    private ContentViewAndroidDelegate mViewAndroidDelegate;
+
     /**
      * Constructs a new ContentViewCore. Embedders must call initialize() after constructing
      * a ContentViewCore and before using it.
@@ -430,9 +565,11 @@ public class ContentViewCore
     }
 
     /**
-     * Returns a delegate that can be used to add and remove views from the ContainerView.
+     * Returns a delegate that can be used to add and remove views from the current
+     * container view. Clients can safely hold to instances of this class as it handles the
+     * replacement of container views transparently.
      *
-     * NOTE: Use with care, as not all ContentViewCore users setup their ContainerView in the same
+     * NOTE: Use with care, as not all ContentViewCore users setup their container view in the same
      * way. In particular, the Android WebView has limitations on what implementation details can
      * be provided via a child view, as they are visible in the API and could introduce
      * compatibility breaks with existing applications. If in doubt, contact the
@@ -440,77 +577,8 @@ public class ContentViewCore
      *
      * @return A ViewAndroidDelegate that can be used to add and remove views.
      */
-    @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(mContext);
-                mContainerViewAtCreation.addView(anchorView);
-                return anchorView;
-            }
-
-            @Override
-            @SuppressWarnings("deprecation")  // AbsoluteLayout
-            public void setAnchorViewPosition(
-                    View view, float x, float y, float width, float height) {
-                if (view.getParent() == null) {
-                    // Ignore. setAnchorViewPosition has been called after the anchor view has
-                    // already been released.
-                    return;
-                }
-                assert view.getParent() == mContainerViewAtCreation;
-
-                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 (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));
-                    ApiCompatibilityUtils.setMarginStart(lp, startMargin);
-                    lp.topMargin = topMargin;
-                    view.setLayoutParams(lp);
-                } 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 mContainerViewAtCreation.getScroll[X/Y]()
-                    // as it naturally accounts for scroll differences between
-                    // these models.
-                    leftMargin += mRenderCoordinates.getScrollXPixInt();
-                    topMargin += mRenderCoordinates.getScrollYPixInt();
-
-                    android.widget.AbsoluteLayout.LayoutParams lp =
-                            new android.widget.AbsoluteLayout.LayoutParams(
-                                scaledWidth, (int) (height * scale), leftMargin, topMargin);
-                    view.setLayoutParams(lp);
-                } else {
-                    Log.e(TAG, "Unknown layout " + mContainerViewAtCreation.getClass().getName());
-                }
-            }
-
-            @Override
-            public void releaseAnchorView(View anchorView) {
-                mContainerViewAtCreation.removeView(anchorView);
-            }
-        };
+        return mViewAndroidDelegate;
     }
 
     @VisibleForTesting
@@ -538,6 +606,11 @@ public class ContentViewCore
         return mInputConnection;
     }
 
+    @VisibleForTesting
+    ViewAndroid getViewAndroid() {
+        return mViewAndroid;
+    }
+
     private ImeAdapter createImeAdapter(Context context) {
         return new ImeAdapter(mInputMethodManagerWrapper,
                 new ImeAdapter.ImeAdapterDelegate() {
@@ -545,7 +618,7 @@ public class ContentViewCore
                     public void onImeEvent() {
                         mPopupZoomer.hide(true);
                         getContentViewClient().onImeEvent();
-                        if (mFocusedNodeEditable) hideTextHandles();
+                        if (mFocusedNodeEditable) dismissTextHandles();
                     }
 
                     @Override
@@ -564,8 +637,8 @@ public class ContentViewCore
                             @Override
                             public void onReceiveResult(int resultCode, Bundle resultData) {
                                 getContentViewClient().onImeStateChangeRequested(
-                                        resultCode == InputMethodManager.RESULT_SHOWN ||
-                                        resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
+                                        resultCode == InputMethodManager.RESULT_SHOWN
+                                        || resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN);
                                 if (resultCode == InputMethodManager.RESULT_SHOWN) {
                                     // If OSK is newly shown, delay the form focus until
                                     // the onSizeChanged (in order to adjust relative to the
@@ -574,10 +647,11 @@ public class ContentViewCore
                                     // always be called, crbug.com/294908.
                                     getContainerView().getWindowVisibleDisplayFrame(
                                             mFocusPreOSKViewportRect);
-                                } else if (hasFocus() && resultCode ==
-                                        InputMethodManager.RESULT_UNCHANGED_SHOWN) {
+                                } else if (hasFocus() && resultCode
+                                        == InputMethodManager.RESULT_UNCHANGED_SHOWN) {
                                     // If the OSK was already there, focus the form immediately.
-                                    scrollFocusedEditableNodeIntoView();
+                                    assert mWebContents != null;
+                                    mWebContents.scrollFocusedEditableNodeIntoView();
                                 }
                             }
                         };
@@ -605,11 +679,12 @@ public class ContentViewCore
     // deleting it after destroying the ContentViewCore.
     public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher,
             long nativeWebContents, WindowAndroid windowAndroid) {
+        createContentViewAndroidDelegate();
         setContainerView(containerView);
-
         long windowNativePointer = windowAndroid.getNativePointer();
         assert windowNativePointer != 0;
-        mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate());
+        createViewAndroid(windowAndroid);
+
         long viewAndroidNativePointer = mViewAndroid.getNativePointer();
         assert viewAndroidNativePointer != 0;
 
@@ -625,10 +700,20 @@ public class ContentViewCore
         mRenderCoordinates.reset();
         initPopupZoomer(mContext);
         mImeAdapter = createImeAdapter(mContext);
+        attachImeAdapter();
 
         mAccessibilityInjector = AccessibilityInjector.newInstance(this);
 
-        mWebContentsObserver = new WebContentsObserverAndroid(mWebContents) {
+        mWebContentsObserver = new WebContentsObserver(mWebContents) {
+            @Override
+            public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode,
+                    String description, String failingUrl) {
+                // Navigation that fails the provisional load will have the strong binding removed
+                // here. One for which the provisional load is commited will have the strong binding
+                // removed in navigationEntryCommitted() below.
+                if (isProvisionalLoad) determinedProcessVisibility();
+            }
+
             @Override
             public void didNavigateMainFrame(String url, String baseUrl,
                     boolean isNavigationToDifferentPage, boolean isFragmentNavigation) {
@@ -645,16 +730,39 @@ public class ContentViewCore
                 // No need to reset gesture detection as the detector will have
                 // been destroyed in the RenderWidgetHostView.
             }
+
+            @Override
+            public void navigationEntryCommitted() {
+                determinedProcessVisibility();
+            }
+
+            private void determinedProcessVisibility() {
+                // Signal to the process management logic that we can now rely on the process
+                // visibility signal for binding management. Before the navigation commits, its
+                // renderer is considered background even if the pending navigation happens in the
+                // foreground renderer.
+                ChildProcessLauncher.determinedVisibility(getCurrentRenderProcessId());
+            }
         };
     }
 
+    @VisibleForTesting
+    void createContentViewAndroidDelegate() {
+        mViewAndroidDelegate = new ContentViewAndroidDelegate();
+    }
+
+    @VisibleForTesting
+    void createViewAndroid(WindowAndroid windowAndroid) {
+        mViewAndroid = new ViewAndroid(windowAndroid, mViewAndroidDelegate);
+    }
+
     /**
      * 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>WARNING: This method can also be used to replace the existing container view,
+     * but you should only do it if you have a very good reason to. Replacing the
+     * container view has been designed to support fullscreen in the Webview so it
+     * might not be appropriate for other use cases.
      *
      * <p>This method only performs a small part of replacing the container view and
      * embedders are responsible for:
@@ -676,16 +784,8 @@ public class ContentViewCore
 
         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);
+        mViewAndroidDelegate.updateCurrentContainerView();
         TraceEvent.end();
     }
 
@@ -704,7 +804,8 @@ public class ContentViewCore
         mContainerViewInternals = internalDispatcher;
     }
 
-    private void initPopupZoomer(Context context) {
+    @VisibleForTesting
+    void initPopupZoomer(Context context) {
         mPopupZoomer = new PopupZoomer(context);
         mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() {
             // mContainerView can change, but this OnVisibilityChangedListener can only be used
@@ -718,8 +819,6 @@ public class ContentViewCore
                     public void run() {
                         if (mContainerViewAtCreation.indexOfChild(zoomer) == -1) {
                             mContainerViewAtCreation.addView(zoomer);
-                        } else {
-                            assert false : "PopupZoomer should never be shown without being hidden";
                         }
                     }
                 });
@@ -733,8 +832,6 @@ public class ContentViewCore
                         if (mContainerViewAtCreation.indexOfChild(zoomer) != -1) {
                             mContainerViewAtCreation.removeView(zoomer);
                             mContainerViewAtCreation.invalidate();
-                        } else {
-                            assert false : "PopupZoomer should never be hidden without being shown";
                         }
                     }
                 });
@@ -857,61 +954,55 @@ public class ContentViewCore
     }
 
     /**
-     * Shows an interstitial page driven by the passed in delegate.
-     *
-     * @param url The URL being blocked by the interstitial.
-     * @param delegate The delegate handling the interstitial.
-     */
-    @VisibleForTesting
-    public void showInterstitialPage(
-            String url, InterstitialPageDelegateAndroid delegate) {
-        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() {
-        assert mWebContents != null;
-        return mWebContents.isShowingInterstitialPage();
-    }
-
-    /**
      * @return Viewport width in physical pixels as set from onSizeChanged.
      */
     @CalledByNative
-    public int getViewportWidthPix() { return mViewportWidthPix; }
+    public int getViewportWidthPix() {
+        return mViewportWidthPix;
+    }
 
     /**
      * @return Viewport height in physical pixels as set from onSizeChanged.
      */
     @CalledByNative
-    public int getViewportHeightPix() { return mViewportHeightPix; }
+    public int getViewportHeightPix() {
+        return mViewportHeightPix;
+    }
 
     /**
      * @return Width of underlying physical surface.
      */
     @CalledByNative
-    public int getPhysicalBackingWidthPix() { return mPhysicalBackingWidthPix; }
+    public int getPhysicalBackingWidthPix() {
+        return mPhysicalBackingWidthPix;
+    }
 
     /**
      * @return Height of underlying physical surface.
      */
     @CalledByNative
-    public int getPhysicalBackingHeightPix() { return mPhysicalBackingHeightPix; }
+    public int getPhysicalBackingHeightPix() {
+        return mPhysicalBackingHeightPix;
+    }
 
     /* TODO(aelias): Remove these when downstream callers disappear. */
     @VisibleForTesting
-    public int getViewportSizeOffsetWidthPix() { return 0; }
+    public int getViewportSizeOffsetWidthPix() {
+        return 0;
+    }
+
     @VisibleForTesting
-    public int getViewportSizeOffsetHeightPix() { return getTopControlsLayoutHeightPix(); }
+    public int getViewportSizeOffsetHeightPix() {
+        return getTopControlsLayoutHeightPix();
+    }
 
     /**
      * @return The amount that the viewport size given to Blink is shrunk by the URL-bar..
      */
     @CalledByNative
-    public int getTopControlsLayoutHeightPix() { return mTopControlsLayoutHeightPix; }
+    public int getTopControlsLayoutHeightPix() {
+        return mTopControlsLayoutHeightPix;
+    }
 
     /**
      * @see android.webkit.WebView#getContentHeight()
@@ -1189,65 +1280,6 @@ public class ContentViewCore
     }
 
     /**
-     * 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);
-    }
-
-    /**
-     * Injects the passed Javascript code in the current page and evaluates it.
-     * If a result is required, pass in a callback.
-     * Used in automation tests.
-     *
-     * @param script The Javascript to execute.
-     * @param callback The callback to be fired off when a result is ready. The script's
-     *                 result will be json encoded and passed as the parameter, and the call
-     *                 will be made on the main thread.
-     *                 If no result is required, pass null.
-     */
-    public void evaluateJavaScript(String script, JavaScriptCallback callback) {
-        assert mWebContents != null;
-        mWebContents.evaluateJavaScript(script, callback);
-    }
-
-    /**
-     * Post a message to a frame.
-     * TODO(sgurun) also add support for transferring a message channel port.
-     *
-     * @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;
-        nativePostMessageToFrame(mNativeContentViewCore, frameName, message, sourceOrigin,
-            targetOrigin);
-    }
-
-    /**
      * To be called when the ContentView is shown.
      */
     public void onShow() {
@@ -1261,7 +1293,6 @@ public class ContentViewCore
      * @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);
     }
@@ -1289,6 +1320,10 @@ public class ContentViewCore
     private void hidePopupsAndClearSelection() {
         mUnselectAllOnActionModeDismiss = true;
         hidePopups();
+        // Clear the selection. The selection is cleared on destroying IME
+        // and also here since we may receive destroy first, for example
+        // when focus is lost in webview.
+        clearUserSelection();
     }
 
     private void hidePopupsAndPreserveSelection() {
@@ -1296,12 +1331,23 @@ public class ContentViewCore
         hidePopups();
     }
 
+    private void clearUserSelection() {
+        if (mFocusedNodeEditable) {
+            if (mInputConnection != null) {
+                int selectionEnd = Selection.getSelectionEnd(mEditable);
+                mInputConnection.setSelection(selectionEnd, selectionEnd);
+            }
+        } else if (mImeAdapter != null) {
+            mImeAdapter.unselect();
+        }
+    }
+
     private void hidePopups() {
         hideSelectActionBar();
         hidePastePopup();
         hideSelectPopup();
         mPopupZoomer.hide(false);
-        if (mUnselectAllOnActionModeDismiss) hideTextHandles();
+        if (mUnselectAllOnActionModeDismiss) dismissTextHandles();
     }
 
     private void restoreSelectionPopupsIfNecessary() {
@@ -1330,6 +1376,7 @@ public class ContentViewCore
     @SuppressWarnings("javadoc")
     public void onAttachedToWindow() {
         setAccessibilityState(mAccessibilityManager.isEnabled());
+        setTextHandlesTemporarilyHidden(false);
         restoreSelectionPopupsIfNecessary();
         ScreenOrientationListener.getInstance().addObserver(this, mContext);
         GamepadList.onAttachedToWindow(mContext);
@@ -1342,12 +1389,19 @@ public class ContentViewCore
     @SuppressLint("MissingSuperCall")
     public void onDetachedFromWindow() {
         setInjectedAccessibility(false);
-        hidePopupsAndPreserveSelection();
         mZoomControlsDelegate.dismissZoomPicker();
         unregisterAccessibilityContentObserver();
 
         ScreenOrientationListener.getInstance().removeObserver(this);
         GamepadList.onDetachedFromWindow();
+
+        // WebView uses PopupWindows for handle rendering, which may remain
+        // unintentionally visible even after the WebView has been detached.
+        // Override the handle visibility explicitly to address this, but
+        // preserve the underlying selection for detachment cases like screen
+        // locking and app switching.
+        setTextHandlesTemporarilyHidden(true);
+        hidePopupsAndPreserveSelection();
     }
 
     /**
@@ -1459,7 +1513,8 @@ public class ContentViewCore
             if (!rect.equals(mFocusPreOSKViewportRect)) {
                 // Only assume the OSK triggered the onSizeChanged if width was preserved.
                 if (rect.width() == mFocusPreOSKViewportRect.width()) {
-                    scrollFocusedEditableNodeIntoView();
+                    assert mWebContents != null;
+                    mWebContents.scrollFocusedEditableNodeIntoView();
                 }
                 cancelRequestToScrollFocusedEditableNodeIntoView();
             }
@@ -1472,20 +1527,6 @@ public class ContentViewCore
         mFocusPreOSKViewportRect.setEmpty();
     }
 
-    private void scrollFocusedEditableNodeIntoView() {
-        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();
-    }
-
     /**
      * @see View#onWindowFocusChanged(boolean)
      */
@@ -1822,6 +1863,11 @@ public class ContentViewCore
         return mDownloadDelegate;
     }
 
+    @VisibleForTesting
+    public SelectActionModeCallback.ActionHandler getSelectActionHandler() {
+        return mActionHandler;
+    }
+
     private void showSelectActionBar() {
         if (mActionMode != null) {
             mActionMode.invalidate();
@@ -1829,119 +1875,115 @@ public class ContentViewCore
         }
 
         // Start a new action mode with a SelectActionModeCallback.
-        SelectActionModeCallback.ActionHandler actionHandler =
-                new SelectActionModeCallback.ActionHandler() {
-            @Override
-            public void selectAll() {
-                mImeAdapter.selectAll();
-            }
-
-            @Override
-            public void cut() {
-                mImeAdapter.cut();
-            }
-
-            @Override
-            public void copy() {
-                mImeAdapter.copy();
-            }
+        if (mActionHandler == null) {
+            mActionHandler = new SelectActionModeCallback.ActionHandler() {
+                @Override
+                public void selectAll() {
+                    mImeAdapter.selectAll();
+                }
 
-            @Override
-            public void paste() {
-                mImeAdapter.paste();
-            }
+                @Override
+                public void cut() {
+                    mImeAdapter.cut();
+                }
 
-            @Override
-            public void share() {
-                final String query = getSelectedText();
-                if (TextUtils.isEmpty(query)) return;
-
-                Intent send = new Intent(Intent.ACTION_SEND);
-                send.setType("text/plain");
-                send.putExtra(Intent.EXTRA_TEXT, query);
-                try {
-                    Intent i = Intent.createChooser(send, getContext().getString(
-                            R.string.actionbar_share));
-                    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    getContext().startActivity(i);
-                } catch (android.content.ActivityNotFoundException ex) {
-                    // If no app handles it, do nothing.
+                @Override
+                public void copy() {
+                    mImeAdapter.copy();
                 }
-            }
 
-            @Override
-            public void search() {
-                final String query = getSelectedText();
-                if (TextUtils.isEmpty(query)) return;
-
-                // See if ContentViewClient wants to override
-                if (getContentViewClient().doesPerformWebSearch()) {
-                    getContentViewClient().performWebSearch(query);
-                    return;
+                @Override
+                public void paste() {
+                    mImeAdapter.paste();
                 }
 
-                Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
-                i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
-                i.putExtra(SearchManager.QUERY, query);
-                i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
-                if (!(getContext() instanceof Activity)) {
-                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                @Override
+                public void share() {
+                    final String query = getSelectedText();
+                    if (TextUtils.isEmpty(query)) return;
+
+                    Intent send = new Intent(Intent.ACTION_SEND);
+                    send.setType("text/plain");
+                    send.putExtra(Intent.EXTRA_TEXT, query);
+                    try {
+                        Intent i = Intent.createChooser(send, getContext().getString(
+                                R.string.actionbar_share));
+                        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        getContext().startActivity(i);
+                    } catch (android.content.ActivityNotFoundException ex) {
+                        // If no app handles it, do nothing.
+                    }
                 }
-                try {
-                    getContext().startActivity(i);
-                } catch (android.content.ActivityNotFoundException ex) {
-                    // If no app handles it, do nothing.
+
+                @Override
+                public void search() {
+                    final String query = getSelectedText();
+                    if (TextUtils.isEmpty(query)) return;
+
+                    // See if ContentViewClient wants to override
+                    if (getContentViewClient().doesPerformWebSearch()) {
+                        getContentViewClient().performWebSearch(query);
+                        return;
+                    }
+
+                    Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
+                    i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
+                    i.putExtra(SearchManager.QUERY, query);
+                    i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
+                    if (!(getContext() instanceof Activity)) {
+                        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    }
+                    try {
+                        getContext().startActivity(i);
+                    } catch (android.content.ActivityNotFoundException ex) {
+                        // If no app handles it, do nothing.
+                    }
                 }
-            }
 
-            @Override
-            public boolean isSelectionPassword() {
-                return mImeAdapter.isSelectionPassword();
-            }
+                @Override
+                public boolean isSelectionPassword() {
+                    return mImeAdapter.isSelectionPassword();
+                }
 
-            @Override
-            public boolean isSelectionEditable() {
-                return mFocusedNodeEditable;
-            }
+                @Override
+                public boolean isSelectionEditable() {
+                    return mFocusedNodeEditable;
+                }
 
-            @Override
-            public void onDestroyActionMode() {
-                mActionMode = null;
-                if (mUnselectAllOnActionModeDismiss) {
-                    hideTextHandles();
-                    if (isSelectionEditable()) {
-                        int selectionEnd = Selection.getSelectionEnd(mEditable);
-                        mInputConnection.setSelection(selectionEnd, selectionEnd);
-                    } else {
-                        mImeAdapter.unselect();
+                @Override
+                public void onDestroyActionMode() {
+                    mActionMode = null;
+                    if (mUnselectAllOnActionModeDismiss) {
+                        dismissTextHandles();
+                        clearUserSelection();
                     }
+                    getContentViewClient().onContextualActionBarHidden();
                 }
-                getContentViewClient().onContextualActionBarHidden();
-            }
 
-            @Override
-            public boolean isShareAvailable() {
-                Intent intent = new Intent(Intent.ACTION_SEND);
-                intent.setType("text/plain");
-                return getContext().getPackageManager().queryIntentActivities(intent,
-                        PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
-            }
+                @Override
+                public boolean isShareAvailable() {
+                    Intent intent = new Intent(Intent.ACTION_SEND);
+                    intent.setType("text/plain");
+                    return getContext().getPackageManager().queryIntentActivities(intent,
+                            PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+                }
 
-            @Override
-            public boolean isWebSearchAvailable() {
-                if (getContentViewClient().doesPerformWebSearch()) return true;
-                Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
-                intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
-                return getContext().getPackageManager().queryIntentActivities(intent,
-                        PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
-            }
-        };
+                @Override
+                public boolean isWebSearchAvailable() {
+                    if (getContentViewClient().doesPerformWebSearch()) return true;
+                    Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+                    intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
+                    return getContext().getPackageManager().queryIntentActivities(intent,
+                            PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
+                }
+            };
+        }
         mActionMode = null;
         // On ICS, startActionMode throws an NPE when getParent() is null.
         if (mContainerView.getParent() != null) {
             assert mWebContents != null;
             mActionMode = mContainerView.startActionMode(
-                    getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
+                    getContentViewClient().getSelectActionModeCallback(getContext(), mActionHandler,
                             mWebContents.isIncognito()));
         }
         mUnselectAllOnActionModeDismiss = true;
@@ -1975,6 +2017,14 @@ public class ContentViewCore
         return mHasSelection;
     }
 
+     /**
+     * @return Whether the page has an active, touch-controlled insertion handle.
+     */
+    @VisibleForTesting
+    protected boolean hasInsertion() {
+        return mHasInsertion;
+    }
+
     private void hidePastePopup() {
         if (mPastePopupMenu == null) return;
         mPastePopupMenu.hide();
@@ -2043,10 +2093,15 @@ public class ContentViewCore
         getContentViewClient().onSelectionEvent(eventType, posXDip * scale, posYDip * scale);
     }
 
-    private void hideTextHandles() {
+    private void dismissTextHandles() {
         mHasSelection = false;
         mHasInsertion = false;
-        if (mNativeContentViewCore != 0) nativeHideTextHandles(mNativeContentViewCore);
+        if (mNativeContentViewCore != 0) nativeDismissTextHandles(mNativeContentViewCore);
+    }
+
+    private void setTextHandlesTemporarilyHidden(boolean hide) {
+        if (mNativeContentViewCore == 0) return;
+        nativeSetTextHandlesTemporarilyHidden(mNativeContentViewCore, hide);
     }
 
     /**
@@ -2196,7 +2251,7 @@ public class ContentViewCore
         for (int i = 0; i < items.length; i++) {
             popupItems.add(new SelectPopupItem(items[i], enabled[i]));
         }
-        if (DeviceFormFactor.isTablet(mContext) && !multiple) {
+        if (DeviceFormFactor.isTablet(mContext) && !multiple && !isTouchExplorationEnabled()) {
             mSelectPopup = new SelectPopupDropdown(this, popupItems, bounds, selectedIndices);
         } else {
             mSelectPopup = new SelectPopupDialog(this, popupItems, multiple, selectedIndices);
@@ -2284,8 +2339,8 @@ public class ContentViewCore
         if (!mHasInsertion || !canPaste()) return false;
         final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix();
         getPastePopup().showAt(
-            (int) mRenderCoordinates.fromDipToPix(xDip),
-            (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
+                (int) mRenderCoordinates.fromDipToPix(xDip),
+                (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix));
         return true;
     }
 
@@ -2296,7 +2351,7 @@ public class ContentViewCore
                     @Override
                     public void paste() {
                         mImeAdapter.paste();
-                        hideTextHandles();
+                        dismissTextHandles();
                     }
                 });
         }
@@ -2333,7 +2388,10 @@ public class ContentViewCore
      * @see View#hasFocus()
      */
     @CalledByNative
-    public boolean hasFocus() {
+    private boolean hasFocus() {
+        // If the container view is not focusable, we consider it always focused from
+        // Chromium's point of view.
+        if (!mContainerView.isFocusable()) return true;
         return mContainerView.hasFocus();
     }
 
@@ -2542,17 +2600,6 @@ public class ContentViewCore
         return mRenderCoordinates.getPageScaleFactor();
     }
 
-    /**
-     * If the view is ready to draw contents to the screen. In hardware mode,
-     * the initialization of the surface texture may not occur until after the
-     * view has been added to the layout. This method will return {@code true}
-     * once the texture is actually ready.
-     */
-    public boolean isReady() {
-        assert mWebContents != null;
-        return mWebContents.isReady();
-    }
-
     @CalledByNative
     private void startContentIntent(String contentUrl) {
         getContentViewClient().onStartContentIntent(getContext(), contentUrl);
@@ -2622,10 +2669,9 @@ public class ContentViewCore
             return mBrowserAccessibilityManager.getAccessibilityNodeProvider();
         }
 
-        if (mNativeAccessibilityAllowed &&
-                !mNativeAccessibilityEnabled &&
-                mNativeContentViewCore != 0 &&
-                Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+        if (mNativeAccessibilityAllowed && !mNativeAccessibilityEnabled
+                && mNativeContentViewCore != 0
+                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
             mNativeAccessibilityEnabled = true;
             nativeSetAccessibilityEnabled(mNativeContentViewCore, true);
         }
@@ -2673,8 +2719,8 @@ public class ContentViewCore
         try {
             // On JellyBean and higher, native accessibility is the default so script
             // injection is only allowed if enabled via a flag.
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
-                    !CommandLine.getInstance().hasSwitch(
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
+                    && !CommandLine.getInstance().hasSwitch(
                             ContentSwitches.ENABLE_ACCESSIBILITY_SCRIPT_INJECTION)) {
                 return false;
             }
@@ -2702,9 +2748,9 @@ public class ContentViewCore
                     }
                 };
                 contentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(accessibilityScriptInjection),
-                    false,
-                    contentObserver);
+                        Settings.Secure.getUriFor(accessibilityScriptInjection),
+                        false,
+                        contentObserver);
                 mAccessibilityScriptInjectionObserver = contentObserver;
             }
 
@@ -2783,28 +2829,7 @@ public class ContentViewCore
     }
 
     /**
-     * Inform WebKit that Fullscreen mode has been exited by the user.
-     */
-    public void exitFullscreen() {
-        assert mWebContents != null;
-        mWebContents.exitFullscreen();
-    }
-
-    /**
-     * Changes whether hiding the top controls is enabled.
      *
-     * @param enableHiding Whether hiding the top controls should be enabled or not.
-     * @param enableShowing Whether showing the top controls should be enabled or not.
-     * @param animate Whether the transition should be animated or not.
-     */
-    public void updateTopControlsState(boolean enableHiding, boolean enableShowing,
-            boolean animate) {
-        assert mWebContents != null;
-        mWebContents.updateTopControlsState(
-                enableHiding, enableShowing, animate);
-    }
-
-    /**
      * @return The cached copy of render positions and scales.
      */
     public RenderCoordinates getRenderCoordinates() {
@@ -2897,8 +2922,7 @@ public class ContentViewCore
     private native long nativeInit(long webContentsPtr,
             long viewAndroidPtr, long windowAndroidPtr, HashSet<Object> retainedObjectSet);
 
-    @CalledByNative
-    private ContentVideoViewClient getContentVideoViewClient() {
+    ContentVideoViewClient getContentVideoViewClient() {
         return getContentViewClient().getContentVideoViewClient();
     }
 
@@ -2922,11 +2946,6 @@ public class ContentViewCore
         sendOrientationChangeEvent(orientation);
     }
 
-    public void resumeResponseDeferredAtStart() {
-        assert mWebContents != null;
-        mWebContents.resumeResponseDeferredAtStart();
-    }
-
     /**
      * Set whether the ContentViewCore requires the WebContents to be fullscreen in order to lock
      * the screen orientation.
@@ -3006,7 +3025,9 @@ public class ContentViewCore
 
     private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y);
 
-    private native void nativeHideTextHandles(long nativeContentViewCoreImpl);
+    private native void nativeDismissTextHandles(long nativeContentViewCoreImpl);
+    private native void nativeSetTextHandlesTemporarilyHidden(
+            long nativeContentViewCoreImpl, boolean hidden);
 
     private native void nativeResetGestureDetection(long nativeContentViewCoreImpl);
 
@@ -3020,9 +3041,6 @@ public class ContentViewCore
             long nativeSelectPopupSourceFrame, int[] indices);
 
 
-    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);