diff options
10 files changed, 179 insertions, 126 deletions
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index e47e0cc207..3b45089e0a 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -20,6 +20,7 @@ import android.net.http.SslCertificate; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.util.Log; @@ -48,6 +49,7 @@ import org.chromium.content.browser.ContentSettings; import org.chromium.content.browser.ContentViewClient; import org.chromium.content.browser.ContentViewCore; import org.chromium.content.browser.ContentViewStatics; +import org.chromium.content.browser.SmartClipProvider; import org.chromium.content.browser.WebContentsObserverAndroid; import org.chromium.content.common.CleanupReference; import org.chromium.content_public.browser.GestureStateListener; @@ -80,7 +82,7 @@ import java.util.concurrent.Callable; * continuous build & test in the open source SDK-based tree). */ @JNINamespace("android_webview") -public class AwContents { +public class AwContents implements SmartClipProvider { private static final String TAG = "AwContents"; private static final String WEB_ARCHIVE_EXTENSION = ".mht"; @@ -2227,12 +2229,34 @@ public class AwContents { return null; } + @Override public void extractSmartClipData(int x, int y, int width, int height) { if (!isDestroyed()) mContentViewCore.extractSmartClipData(x, y, width, height); } - public void setSmartClipDataListener(ContentViewCore.SmartClipDataListener listener) { - if (!isDestroyed()) mContentViewCore.setSmartClipDataListener(listener); + @Override + public void setSmartClipResultHandler(final Handler resultHandler) { + if (resultHandler == null) { + mContentViewCore.setSmartClipDataListener(null); + return; + } + mContentViewCore.setSmartClipDataListener(new ContentViewCore.SmartClipDataListener() { + public void onSmartClipDataExtracted(String text, String html, Rect clipRect) { + Bundle bundle = new Bundle(); + bundle.putString("url", mContentViewCore.getWebContents().getVisibleUrl()); + bundle.putString("title", mContentViewCore.getWebContents().getTitle()); + bundle.putParcelable("rect", clipRect); + bundle.putString("text", text); + bundle.putString("html", html); + try { + Message msg = Message.obtain(resultHandler, 0); + msg.setData(bundle); + msg.sendToTarget(); + } catch (Exception e) { + Log.e(TAG, "Error calling handler for smart clip data: ", e); + } + } + }); } // -------------------------------------------------------------------------------------------- diff --git a/base/android/java/src/org/chromium/base/SystemMessageHandler.java b/base/android/java/src/org/chromium/base/SystemMessageHandler.java index 42c8987ec6..fd3dc5a08b 100644 --- a/base/android/java/src/org/chromium/base/SystemMessageHandler.java +++ b/base/android/java/src/org/chromium/base/SystemMessageHandler.java @@ -5,12 +5,9 @@ package org.chromium.base; import android.os.Handler; -import android.os.Looper; import android.os.Message; -import android.os.MessageQueue; import android.util.Log; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -25,23 +22,31 @@ class SystemMessageHandler extends Handler { private long mMessagePumpDelegateNative = 0; private long mDelayedScheduledTimeTicks = 0; - // The following members are used to detect and trace the presence of sync - // barriers in Android's MessageQueue. Note that this detection is - // experimental, temporary and intended only for diagnostic purposes. - private MessageQueue mMessageQueue; - private Field mMessageQueueMessageField; - private Field mMessageTargetField; - private boolean mQueueHasSyncBarrier; - private long mSyncBarrierTraceId; + // Reflected API for marking a message as asynchronous. This is a workaround + // to provide fair Chromium task dispatch when served by the Android UI + // thread's Looper, avoiding stalls when the Looper has a sync barrier. + // Note: Use of this API is experimental and likely to evolve in the future. + private Method mMessageMethodSetAsynchronous; private SystemMessageHandler(long messagePumpDelegateNative) { mMessagePumpDelegateNative = messagePumpDelegateNative; - tryEnableSyncBarrierDetection(); - } + + try { + Class<?> messageClass = Class.forName("android.os.Message"); + mMessageMethodSetAsynchronous = messageClass.getMethod( + "setAsynchronous", new Class[]{boolean.class}); + } catch (ClassNotFoundException e) { + Log.e(TAG, "Failed to find android.os.Message class:" + e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Failed to load Message.setAsynchronous method:" + e); + } catch (RuntimeException e) { + Log.e(TAG, "Exception while loading Message.setAsynchronous method: " + e); + } + + } @Override public void handleMessage(Message msg) { - updateWhetherQueueHasBlockingSyncBarrier(); if (msg.what == DELAYED_SCHEDULED_WORK) { mDelayedScheduledTimeTicks = 0; } @@ -51,9 +56,7 @@ class SystemMessageHandler extends Handler { @SuppressWarnings("unused") @CalledByNative private void scheduleWork() { - updateWhetherQueueHasBlockingSyncBarrier(); - if (mQueueHasSyncBarrier) TraceEvent.instant("SystemMessageHandler:immediateWorkBlocked"); - sendEmptyMessage(SCHEDULED_WORK); + sendMessage(obtainAsyncMessage(SCHEDULED_WORK)); } @SuppressWarnings("unused") @@ -63,99 +66,41 @@ class SystemMessageHandler extends Handler { removeMessages(DELAYED_SCHEDULED_WORK); } mDelayedScheduledTimeTicks = delayedTimeTicks; - updateWhetherQueueHasBlockingSyncBarrier(); - if (mQueueHasSyncBarrier) TraceEvent.instant("SystemMessageHandler:delayedWorkBlocked"); - sendEmptyMessageDelayed(DELAYED_SCHEDULED_WORK, millis); + sendMessageDelayed(obtainAsyncMessage(DELAYED_SCHEDULED_WORK), millis); } @SuppressWarnings("unused") @CalledByNative private void removeAllPendingMessages() { - updateWhetherQueueHasBlockingSyncBarrier(); removeMessages(SCHEDULED_WORK); removeMessages(DELAYED_SCHEDULED_WORK); } - private void updateWhetherQueueHasBlockingSyncBarrier() { - if (mMessageQueue == null) return; - // As barrier detection is only used for tracing, early out when tracing - // is disabled to avoid any potential performance penalties. - if (!TraceEvent.enabled()) { - mQueueHasSyncBarrier = false; - return; - } - Message queueHead = (Message) getField(mMessageQueue, mMessageQueueMessageField); - setqueueHasSyncBarrier(isSyncBarrierMessage(queueHead)); - } - - private boolean isSyncBarrierMessage(Message message) { - if (message == null) return false; - // Sync barrier messages have null targets. - return getField(message, mMessageTargetField) == null; - } - - private void tryEnableSyncBarrierDetection() { - assert mMessageQueue == null; - - boolean success = false; - try { - Method getQueueMethod = Looper.class.getMethod("getQueue", new Class[]{}); - mMessageQueue = (MessageQueue) getQueueMethod.invoke(getLooper()); - - mMessageQueueMessageField = mMessageQueue.getClass().getDeclaredField("mMessages"); - mMessageQueueMessageField.setAccessible(true); - - mMessageTargetField = Message.class.getDeclaredField("target"); - mMessageTargetField.setAccessible(true); - - mSyncBarrierTraceId = hashCode(); - - success = true; - } catch (NoSuchMethodException e) { - Log.e(TAG, "Failed to load method: " + e); - } catch (NoSuchFieldException e) { - Log.e(TAG, "Failed to load field: " + e); - } catch (InvocationTargetException e) { - Log.e(TAG, "Failed invocation: " + e); - } catch (IllegalAccessException e) { - Log.e(TAG, "Illegal access to reflected invocation: " + e); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Illegal argument to reflected invocation: " + e); - } catch (RuntimeException e) { - Log.e(TAG, e.toString()); - } finally { - if (!success) disableSyncBarrierDetection(); - } - } - - private void disableSyncBarrierDetection() { - Log.e(TAG, "Unexpected error with sync barrier detection, disabling."); - mMessageQueue = null; - mMessageQueueMessageField = null; - mMessageTargetField = null; - setqueueHasSyncBarrier(false); - } - - private void setqueueHasSyncBarrier(boolean queueHasSyncBarrier) { - if (queueHasSyncBarrier == mQueueHasSyncBarrier) return; - mQueueHasSyncBarrier = queueHasSyncBarrier; - if (mQueueHasSyncBarrier) { - TraceEvent.startAsync("SyncBarrier", mSyncBarrierTraceId); - } else { - TraceEvent.finishAsync("SyncBarrier", mSyncBarrierTraceId); + private Message obtainAsyncMessage(int what) { + Message msg = Message.obtain(); + msg.what = what; + if (mMessageMethodSetAsynchronous != null) { + // If invocation fails, assume this is indicative of future + // failures, and avoid log spam by nulling the reflected method. + try { + mMessageMethodSetAsynchronous.invoke(msg, true); + } catch (IllegalAccessException e) { + Log.e(TAG, "Illegal access to asynchronous message creation, disabling."); + mMessageMethodSetAsynchronous = null; + } catch (IllegalArgumentException e) { + Log.e(TAG, "Illegal argument for asynchronous message creation, disabling."); + mMessageMethodSetAsynchronous = null; + } catch (InvocationTargetException e) { + Log.e(TAG, "Invocation exception during asynchronous message creation, disabling."); + mMessageMethodSetAsynchronous = null; + } catch (RuntimeException e) { + Log.e(TAG, "Runtime exception during asynchronous message creation, disabling."); + mMessageMethodSetAsynchronous = null; + } } + return msg; } - private Object getField(Object object, Field field) { - try { - return field.get(object); - } catch (IllegalAccessException e) { - Log.e(TAG, "Failed field access: " + e); - disableSyncBarrierDetection(); - } - return null; - } - @CalledByNative private static SystemMessageHandler create(long messagePumpDelegateNative) { return new SystemMessageHandler(messagePumpDelegateNative); diff --git a/content/browser/android/content_view_core_impl.cc b/content/browser/android/content_view_core_impl.cc index 9cd9679d40..fece49f90f 100644 --- a/content/browser/android/content_view_core_impl.cc +++ b/content/browser/android/content_view_core_impl.cc @@ -1077,15 +1077,23 @@ void ContentViewCoreImpl::SelectBetweenCoordinates(JNIEnv* env, jobject obj, void ContentViewCoreImpl::MoveCaret(JNIEnv* env, jobject obj, jfloat x, jfloat y) { - if (GetRenderWidgetHostViewAndroid()) { - GetRenderWidgetHostViewAndroid()->MoveCaret( - gfx::Point(x / dpi_scale_, y / dpi_scale_)); - } + RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid(); + if (rwhv) + rwhv->MoveCaret(gfx::Point(x / dpi_scale_, y / dpi_scale_)); } -void ContentViewCoreImpl::HideTextHandles(JNIEnv* env, jobject obj) { - if (GetRenderWidgetHostViewAndroid()) - GetRenderWidgetHostViewAndroid()->HideTextHandles(); +void ContentViewCoreImpl::DismissTextHandles(JNIEnv* env, jobject obj) { + RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid(); + if (rwhv) + rwhv->DismissTextHandles(); +} + +void ContentViewCoreImpl::SetTextHandlesTemporarilyHidden(JNIEnv* env, + jobject obj, + jboolean hidden) { + RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid(); + if (rwhv) + rwhv->SetTextHandlesTemporarilyHidden(hidden); } void ContentViewCoreImpl::ResetGestureDetection(JNIEnv* env, jobject obj) { diff --git a/content/browser/android/content_view_core_impl.h b/content/browser/android/content_view_core_impl.h index 9887c0e404..d7f2711c10 100644 --- a/content/browser/android/content_view_core_impl.h +++ b/content/browser/android/content_view_core_impl.h @@ -143,7 +143,10 @@ class ContentViewCoreImpl : public ContentViewCore, jfloat x1, jfloat y1, jfloat x2, jfloat y2); void MoveCaret(JNIEnv* env, jobject obj, jfloat x, jfloat y); - void HideTextHandles(JNIEnv* env, jobject obj); + void DismissTextHandles(JNIEnv* env, jobject obj); + void SetTextHandlesTemporarilyHidden(JNIEnv* env, + jobject obj, + jboolean hidden); void ResetGestureDetection(JNIEnv* env, jobject obj); void SetDoubleTapSupportEnabled(JNIEnv* env, jobject obj, jboolean enabled); diff --git a/content/browser/renderer_host/input/touch_selection_controller.cc b/content/browser/renderer_host/input/touch_selection_controller.cc index d57b96f0aa..e904e283ee 100644 --- a/content/browser/renderer_host/input/touch_selection_controller.cc +++ b/content/browser/renderer_host/input/touch_selection_controller.cc @@ -160,6 +160,20 @@ void TouchSelectionController::HideAndDisallowShowingAutomatically() { activate_selection_automatically_ = false; } +void TouchSelectionController::SetTemporarilyHidden(bool hidden) { + if (temporarily_hidden_ == hidden) + return; + temporarily_hidden_ = hidden; + + TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true); + if (is_selection_active_) { + start_selection_handle_->SetVisible(GetStartVisible(), animation_style); + end_selection_handle_->SetVisible(GetEndVisible(), animation_style); + } + if (is_insertion_active_) + insertion_handle_->SetVisible(GetStartVisible(), animation_style); +} + void TouchSelectionController::OnSelectionEditable(bool editable) { if (selection_editable_ == editable) return; diff --git a/content/browser/renderer_host/input/touch_selection_controller.h b/content/browser/renderer_host/input/touch_selection_controller.h index 452dde0cc3..13031e12b9 100644 --- a/content/browser/renderer_host/input/touch_selection_controller.h +++ b/content/browser/renderer_host/input/touch_selection_controller.h @@ -69,6 +69,9 @@ class CONTENT_EXPORT TouchSelectionController : public TouchHandleClient { // showing allowance. void HideAndDisallowShowingAutomatically(); + // Override the handle visibility according to |hidden|. + void SetTemporarilyHidden(bool hidden); + // To be called when the editability of the focused region changes. void OnSelectionEditable(bool editable); diff --git a/content/browser/renderer_host/input/touch_selection_controller_unittest.cc b/content/browser/renderer_host/input/touch_selection_controller_unittest.cc index c3422077dd..5bdf57273f 100644 --- a/content/browser/renderer_host/input/touch_selection_controller_unittest.cc +++ b/content/browser/renderer_host/input/touch_selection_controller_unittest.cc @@ -632,6 +632,31 @@ TEST_F(TouchSelectionControllerTest, Animation) { EXPECT_FALSE(GetAndResetNeedsAnimate()); } +TEST_F(TouchSelectionControllerTest, TemporarilyHidden) { + controller().OnTapEvent(); + controller().OnSelectionEditable(true); + + gfx::RectF insertion_rect(5, 5, 0, 10); + + bool visible = true; + ChangeInsertion(insertion_rect, visible); + EXPECT_FALSE(GetAndResetNeedsAnimate()); + + controller().SetTemporarilyHidden(true); + EXPECT_TRUE(GetAndResetNeedsAnimate()); + + visible = false; + ChangeInsertion(insertion_rect, visible); + EXPECT_FALSE(GetAndResetNeedsAnimate()); + + visible = true; + ChangeInsertion(insertion_rect, visible); + EXPECT_FALSE(GetAndResetNeedsAnimate()); + + controller().SetTemporarilyHidden(false); + EXPECT_TRUE(GetAndResetNeedsAnimate()); +} + TEST_F(TouchSelectionControllerTest, SelectionClearOnTap) { gfx::RectF start_rect(5, 5, 0, 10); gfx::RectF end_rect(50, 5, 0, 10); diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc index 66c9fcdd74..a20a7da14e 100644 --- a/content/browser/renderer_host/render_widget_host_view_android.cc +++ b/content/browser/renderer_host/render_widget_host_view_android.cc @@ -1523,11 +1523,16 @@ void RenderWidgetHostViewAndroid::MoveCaret(const gfx::Point& point) { host_->MoveCaret(point); } -void RenderWidgetHostViewAndroid::HideTextHandles() { +void RenderWidgetHostViewAndroid::DismissTextHandles() { if (selection_controller_) selection_controller_->HideAndDisallowShowingAutomatically(); } +void RenderWidgetHostViewAndroid::SetTextHandlesTemporarilyHidden(bool hidden) { + if (selection_controller_) + selection_controller_->SetTemporarilyHidden(hidden); +} + void RenderWidgetHostViewAndroid::OnShowingPastePopup( const gfx::PointF& point) { if (!selection_controller_) @@ -1542,7 +1547,7 @@ void RenderWidgetHostViewAndroid::OnShowingPastePopup( insertion_bound.visible = true; insertion_bound.edge_top = point; insertion_bound.edge_bottom = point; - HideTextHandles(); + DismissTextHandles(); ShowSelectionHandlesAutomatically(); selection_controller_->OnSelectionEditable(true); selection_controller_->OnSelectionEmpty(true); diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h index 5393856a92..22bb69446c 100644 --- a/content/browser/renderer_host/render_widget_host_view_android.h +++ b/content/browser/renderer_host/render_widget_host_view_android.h @@ -259,7 +259,8 @@ class CONTENT_EXPORT RenderWidgetHostViewAndroid bool HasValidFrame() const; void MoveCaret(const gfx::Point& point); - void HideTextHandles(); + void DismissTextHandles(); + void SetTextHandlesTemporarilyHidden(bool hidden); void OnShowingPastePopup(const gfx::PointF& point); void SynchronousFrameMetadata( diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java index 001238487c..196f83d9de 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java @@ -545,7 +545,7 @@ public class ContentViewCore public void onImeEvent() { mPopupZoomer.hide(true); getContentViewClient().onImeEvent(); - if (mFocusedNodeEditable) hideTextHandles(); + if (mFocusedNodeEditable) dismissTextHandles(); } @Override @@ -1289,6 +1289,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 +1300,23 @@ public class ContentViewCore hidePopups(); } + private void clearUserSelection() { + if (isSelectionEditable()) { + 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 +1345,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 +1358,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(); } /** @@ -1908,13 +1931,8 @@ public class ContentViewCore public void onDestroyActionMode() { mActionMode = null; if (mUnselectAllOnActionModeDismiss) { - hideTextHandles(); - if (isSelectionEditable()) { - int selectionEnd = Selection.getSelectionEnd(mEditable); - mInputConnection.setSelection(selectionEnd, selectionEnd); - } else { - mImeAdapter.unselect(); - } + dismissTextHandles(); + clearUserSelection(); } getContentViewClient().onContextualActionBarHidden(); } @@ -2043,10 +2061,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); } /** @@ -2296,7 +2319,7 @@ public class ContentViewCore @Override public void paste() { mImeAdapter.paste(); - hideTextHandles(); + dismissTextHandles(); } }); } @@ -3009,7 +3032,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); |