Disable page refresh on overscroll when precision pointer is attached.
When mouse or touchpad is available, do not allow page refresh on overscroll. tools/autotest.py -C out/android_x64 DeviceInputTest Bug: 352167190 Test: tools/autotest.py -C out/android_x64 SwipeRefreshHandlerTest Change-Id: Ied3360f2ecf13a865a2814dd293ff1a89710976b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6475171 Auto-Submit: Eric Lok <lokeric@google.com> Reviewed-by: Theresa Sullivan <twellington@chromium.org> Commit-Queue: Eric Lok <lokeric@google.com> Reviewed-by: Sirisha Kavuluru <skavuluru@google.com> Cr-Commit-Position: refs/heads/main@{#1454256}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
b13518c296
commit
f4cd5091b9
chrome/android
java
src
org
chromium
chrome
browser
javatests
src
org
chromium
chrome
browser
ui/android/java/src/org/chromium/ui/base
@@ -37,6 +37,7 @@ import org.chromium.third_party.android.swiperefresh.SwipeRefreshLayout;
|
|||||||
import org.chromium.ui.OverscrollAction;
|
import org.chromium.ui.OverscrollAction;
|
||||||
import org.chromium.ui.OverscrollRefreshHandler;
|
import org.chromium.ui.OverscrollRefreshHandler;
|
||||||
import org.chromium.ui.base.BackGestureEventSwipeEdge;
|
import org.chromium.ui.base.BackGestureEventSwipeEdge;
|
||||||
|
import org.chromium.ui.base.DeviceInput;
|
||||||
import org.chromium.ui.base.WindowAndroid;
|
import org.chromium.ui.base.WindowAndroid;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@@ -281,23 +282,26 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
|
|||||||
public boolean start(
|
public boolean start(
|
||||||
@OverscrollAction int type, @BackGestureEventSwipeEdge int initiatingEdge) {
|
@OverscrollAction int type, @BackGestureEventSwipeEdge int initiatingEdge) {
|
||||||
mSwipeType = type;
|
mSwipeType = type;
|
||||||
if (type == OverscrollAction.PULL_TO_REFRESH) {
|
if (isRefreshOnOverscrollSupported()) {
|
||||||
if (mSwipeRefreshLayout == null) initSwipeRefreshLayout(mTab.getContext());
|
if (type == OverscrollAction.PULL_TO_REFRESH) {
|
||||||
attachSwipeRefreshLayoutIfNecessary();
|
if (mSwipeRefreshLayout == null) initSwipeRefreshLayout(mTab.getContext());
|
||||||
return mSwipeRefreshLayout.start();
|
attachSwipeRefreshLayoutIfNecessary();
|
||||||
} else if (type == OverscrollAction.HISTORY_NAVIGATION) {
|
return mSwipeRefreshLayout.start();
|
||||||
if (mNavigationCoordinator != null) {
|
} else if (type == OverscrollAction.HISTORY_NAVIGATION) {
|
||||||
mNavigationCoordinator.startGesture();
|
if (mNavigationCoordinator != null) {
|
||||||
// Note: triggerUi returns true as long as the handler is in a valid state, i.e.
|
mNavigationCoordinator.startGesture();
|
||||||
// even if the navigation direction doesn't have further history entries.
|
// Note: triggerUi returns true as long as the handler is in a valid state, i.e.
|
||||||
boolean navigable = mNavigationCoordinator.triggerUi(initiatingEdge);
|
// even if the navigation direction doesn't have further history entries.
|
||||||
return navigable;
|
boolean navigable = mNavigationCoordinator.triggerUi(initiatingEdge);
|
||||||
}
|
return navigable;
|
||||||
} else if (type == OverscrollAction.PULL_FROM_BOTTOM_EDGE) {
|
}
|
||||||
if (mBrowserControls != null) {
|
} else if (type == OverscrollAction.PULL_FROM_BOTTOM_EDGE) {
|
||||||
recordEdgeToEdgeOverscrollFromBottom(mBrowserControls);
|
if (mBrowserControls != null) {
|
||||||
|
recordEdgeToEdgeOverscrollFromBottom(mBrowserControls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mSwipeType = OverscrollAction.NONE;
|
mSwipeType = OverscrollAction.NONE;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -419,4 +423,22 @@ public class SwipeRefreshHandler extends TabWebContentsUserData
|
|||||||
sample,
|
sample,
|
||||||
BottomControlsStatus.NUM_TOTAL);
|
BottomControlsStatus.NUM_TOTAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if page refresh on overscroll is supported Wrapped so we can stub behavior in
|
||||||
|
* tests.
|
||||||
|
*
|
||||||
|
* <p>Currently, overscroll to refresh is disabled if a precision pointer device is attached.
|
||||||
|
* For example, this will disable it for touch screen when a mouse is attaached. However
|
||||||
|
* long-term, the plan is to selectively enable for things such as touchscreen only.
|
||||||
|
*
|
||||||
|
* <p>TODO(crbug.com/412465463): enable overscroll refresh for touch even when precision pointer
|
||||||
|
* is attached
|
||||||
|
*
|
||||||
|
* @return true if page refresh on overscroll is supported.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
|
boolean isRefreshOnOverscrollSupported() {
|
||||||
|
return !DeviceInput.supportsPrecisionPointer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,10 +5,13 @@
|
|||||||
package org.chromium.chrome.browser;
|
package org.chromium.chrome.browser;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -63,6 +66,9 @@ public class SwipeRefreshHandlerTest {
|
|||||||
private OnRefreshListener mOnRefreshListener;
|
private OnRefreshListener mOnRefreshListener;
|
||||||
private OnResetListener mOnResetListener;
|
private OnResetListener mOnResetListener;
|
||||||
private SwipeRefreshLayout mSwipeRefreshLayout;
|
private SwipeRefreshLayout mSwipeRefreshLayout;
|
||||||
|
|
||||||
|
private SwipeRefreshHandler mHandler;
|
||||||
|
|
||||||
private final SwipeRefreshHandler.SwipeRefreshLayoutCreator mSwipeRefreshLayoutCreator =
|
private final SwipeRefreshHandler.SwipeRefreshLayoutCreator mSwipeRefreshLayoutCreator =
|
||||||
context -> {
|
context -> {
|
||||||
mSwipeRefreshLayout = mock();
|
mSwipeRefreshLayout = mock();
|
||||||
@@ -87,14 +93,18 @@ public class SwipeRefreshHandlerTest {
|
|||||||
when(mTab.getContext()).thenReturn(activityTestRule.getActivity());
|
when(mTab.getContext()).thenReturn(activityTestRule.getActivity());
|
||||||
when(mTab.getUserDataHost()).thenReturn(new UserDataHost());
|
when(mTab.getUserDataHost()).thenReturn(new UserDataHost());
|
||||||
when(mTab.getContentView()).thenReturn(mock());
|
when(mTab.getContentView()).thenReturn(mock());
|
||||||
|
|
||||||
|
// Limited use of spy() so we can test actual object, while changing some behaviors
|
||||||
|
// dynamically (such as whether mouse is attached or not)
|
||||||
|
mHandler = spy(SwipeRefreshHandler.from(mTab, mSwipeRefreshLayoutCreator));
|
||||||
|
mHandler.initWebContents(mock()); // Needed to enable the overscroll refresh handler.
|
||||||
|
doReturn(true).when(mHandler).isRefreshOnOverscrollSupported(); // Default no mouse/touchpad
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public void testAccessibilityAnnouncement() {
|
public void testAccessibilityAnnouncement() {
|
||||||
var handler = SwipeRefreshHandler.from(mTab, mSwipeRefreshLayoutCreator);
|
triggerRefresh(mHandler);
|
||||||
handler.initWebContents(mock()); // Needed to enable the overscroll refresh handler.
|
|
||||||
triggerRefresh(handler);
|
|
||||||
|
|
||||||
InOrder orderVerifier = inOrder(mSwipeRefreshLayout);
|
InOrder orderVerifier = inOrder(mSwipeRefreshLayout);
|
||||||
orderVerifier
|
orderVerifier
|
||||||
@@ -104,17 +114,25 @@ public class SwipeRefreshHandlerTest {
|
|||||||
.verify(mSwipeRefreshLayout, times(1))
|
.verify(mSwipeRefreshLayout, times(1))
|
||||||
.setContentDescription(sAccessibilitySwipeRefreshString);
|
.setContentDescription(sAccessibilitySwipeRefreshString);
|
||||||
|
|
||||||
reset(handler);
|
reset(mHandler);
|
||||||
|
|
||||||
orderVerifier.verify(mSwipeRefreshLayout, times(1)).setContentDescription(null);
|
orderVerifier.verify(mSwipeRefreshLayout, times(1)).setContentDescription(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Ensures that we do not trigger refresh if precision pointing device is attached */
|
||||||
|
@Test
|
||||||
|
@SmallTest
|
||||||
|
public void testOverscrollButNoRefresh() {
|
||||||
|
doReturn(false).when(mHandler).isRefreshOnOverscrollSupported(); // pointer device attached
|
||||||
|
triggerRefresh(mHandler);
|
||||||
|
// When refresh is NOT triggered, then refresh layout is NOT created
|
||||||
|
assertNull(mSwipeRefreshLayout);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
public void testAccessibilityAnnouncement_swipingASecondTime() {
|
public void testAccessibilityAnnouncement_swipingASecondTime() {
|
||||||
var handler = SwipeRefreshHandler.from(mTab, mSwipeRefreshLayoutCreator);
|
triggerRefresh(mHandler);
|
||||||
handler.initWebContents(mock()); // Needed to enable the overscroll refresh handler.
|
|
||||||
triggerRefresh(handler);
|
|
||||||
|
|
||||||
var firstSwipeRefreshLayout = mSwipeRefreshLayout;
|
var firstSwipeRefreshLayout = mSwipeRefreshLayout;
|
||||||
|
|
||||||
@@ -126,11 +144,11 @@ public class SwipeRefreshHandlerTest {
|
|||||||
.verify(firstSwipeRefreshLayout, times(1))
|
.verify(firstSwipeRefreshLayout, times(1))
|
||||||
.setContentDescription(sAccessibilitySwipeRefreshString);
|
.setContentDescription(sAccessibilitySwipeRefreshString);
|
||||||
|
|
||||||
reset(handler);
|
reset(mHandler);
|
||||||
|
|
||||||
orderVerifier.verify(mSwipeRefreshLayout, times(1)).setContentDescription(null);
|
orderVerifier.verify(mSwipeRefreshLayout, times(1)).setContentDescription(null);
|
||||||
|
|
||||||
triggerRefresh(handler);
|
triggerRefresh(mHandler);
|
||||||
|
|
||||||
var secondSwipeRefreshLayout = mSwipeRefreshLayout;
|
var secondSwipeRefreshLayout = mSwipeRefreshLayout;
|
||||||
|
|
||||||
|
@@ -52,7 +52,9 @@ public class DeviceInput implements InputDeviceListener {
|
|||||||
for (int i = 0; i < deviceIds.length; i++) {
|
for (int i = 0; i < deviceIds.length; i++) {
|
||||||
int deviceId = deviceIds[i];
|
int deviceId = deviceIds[i];
|
||||||
InputDevice device = InputDevice.getDevice(deviceId);
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
if (device != null) mDeviceSnapshotsById.put(deviceId, DeviceSnapshot.from(device));
|
if (device != null) {
|
||||||
|
mDeviceSnapshotsById.put(deviceId, DeviceSnapshot.from(device));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register listener to perform cache updates.
|
// Register listener to perform cache updates.
|
||||||
@@ -89,7 +91,9 @@ public class DeviceInput implements InputDeviceListener {
|
|||||||
return sSupportsAlphabeticKeyboardForTesting;
|
return sSupportsAlphabeticKeyboardForTesting;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < mDeviceSnapshotsById.size(); i++) {
|
for (int i = 0; i < mDeviceSnapshotsById.size(); i++) {
|
||||||
if (mDeviceSnapshotsById.valueAt(i).supportsAlphabeticKeyboard) return true;
|
if (mDeviceSnapshotsById.valueAt(i).supportsAlphabeticKeyboard) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -117,7 +121,9 @@ public class DeviceInput implements InputDeviceListener {
|
|||||||
return sSupportsPrecisionPointerForTesting;
|
return sSupportsPrecisionPointerForTesting;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < mDeviceSnapshotsById.size(); i++) {
|
for (int i = 0; i < mDeviceSnapshotsById.size(); i++) {
|
||||||
if (mDeviceSnapshotsById.valueAt(i).supportsPrecisionPointer) return true;
|
if (mDeviceSnapshotsById.valueAt(i).supportsPrecisionPointer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -126,15 +132,20 @@ public class DeviceInput implements InputDeviceListener {
|
|||||||
public void onInputDeviceAdded(int deviceId) {
|
public void onInputDeviceAdded(int deviceId) {
|
||||||
ThreadUtils.assertOnUiThread();
|
ThreadUtils.assertOnUiThread();
|
||||||
InputDevice device = InputDevice.getDevice(deviceId);
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
if (device != null) mDeviceSnapshotsById.put(deviceId, DeviceSnapshot.from(device));
|
if (device != null) {
|
||||||
|
mDeviceSnapshotsById.put(deviceId, DeviceSnapshot.from(device));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInputDeviceChanged(int deviceId) {
|
public void onInputDeviceChanged(int deviceId) {
|
||||||
ThreadUtils.assertOnUiThread();
|
ThreadUtils.assertOnUiThread();
|
||||||
InputDevice device = InputDevice.getDevice(deviceId);
|
InputDevice device = InputDevice.getDevice(deviceId);
|
||||||
if (device != null) mDeviceSnapshotsById.put(deviceId, DeviceSnapshot.from(device));
|
if (device != null) {
|
||||||
else mDeviceSnapshotsById.remove(deviceId);
|
mDeviceSnapshotsById.put(deviceId, DeviceSnapshot.from(device));
|
||||||
|
} else {
|
||||||
|
mDeviceSnapshotsById.remove(deviceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -171,6 +182,7 @@ public class DeviceInput implements InputDeviceListener {
|
|||||||
return new DeviceSnapshot(
|
return new DeviceSnapshot(
|
||||||
/* supportsAlphabeticKeyboard= */ isPhysical
|
/* supportsAlphabeticKeyboard= */ isPhysical
|
||||||
&& device.getKeyboardType() == KEYBOARD_TYPE_ALPHABETIC,
|
&& device.getKeyboardType() == KEYBOARD_TYPE_ALPHABETIC,
|
||||||
|
// SOURCE_MOUSE applies to pointer devices, including mouse and touchpad
|
||||||
/* supportsPrecisionPointer= */ isPhysical
|
/* supportsPrecisionPointer= */ isPhysical
|
||||||
&& device.supportsSource(SOURCE_MOUSE));
|
&& device.supportsSource(SOURCE_MOUSE));
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user