Commit 9736c525 authored by chaopeng's avatar chaopeng Committed by Commit Bot

Improve overscroll user experience on Windows

This patch includes 2 changes, These changes only effect Windows
touchpad because only fling events on Windows touchpad can reach
overscroll controller.

1. Prevent fling event start overscroll.
2. Only fling events in first 0.3 second will processed for
   overscroll. On Windows, we don't generate the inertial
   events (fling) but receive them from Win API. In some cases,
   we get a long tail of inertial events for a couple of seconds.
   The overscroll animation feels like stuck in these cases. So
   we only process 0.3 second inertial events then cancel the
   overscroll if it is not completed yet.

Bug: 647140
Change-Id: Iea7941bff77a84f95acf8f342c12273cb909a5d3
Reviewed-on: https://chromium-review.googlesource.com/1025391
Commit-Queue: Jianpeng Chao <chaopeng@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#556890}
parent 3d883670
...@@ -29,6 +29,10 @@ const float kThresholdStartTouchscreen = 50.f; ...@@ -29,6 +29,10 @@ const float kThresholdStartTouchscreen = 50.f;
bool g_is_touchpad_overscroll_history_navigation_enabled_initialized = false; bool g_is_touchpad_overscroll_history_navigation_enabled_initialized = false;
bool g_touchpad_overscroll_history_navigation_enabled = false; bool g_touchpad_overscroll_history_navigation_enabled = false;
// On Windows, we only process 0.3 second inertial events then cancel the
// overscroll if it is not completed yet.
int g_max_inertial_events_before_overscroll_cancellation_in_ms = 300;
float GetStartThresholdMultiplier() { float GetStartThresholdMultiplier() {
base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
if (!cmd->HasSwitch(switches::kOverscrollStartThreshold)) if (!cmd->HasSwitch(switches::kOverscrollStartThreshold))
...@@ -148,4 +152,11 @@ void OverscrollConfig::ResetTouchpadOverscrollHistoryNavigationEnabled() { ...@@ -148,4 +152,11 @@ void OverscrollConfig::ResetTouchpadOverscrollHistoryNavigationEnabled() {
g_is_touchpad_overscroll_history_navigation_enabled_initialized = false; g_is_touchpad_overscroll_history_navigation_enabled_initialized = false;
} }
// static
base::TimeDelta
OverscrollConfig::MaxInertialEventsBeforeOverscrollCancellation() {
return base::TimeDelta::FromMilliseconds(
g_max_inertial_events_before_overscroll_cancellation_in_ms);
}
} // namespace content } // namespace content
...@@ -29,6 +29,16 @@ bool IsGestureEventFromTouchpad(const blink::WebInputEvent& event) { ...@@ -29,6 +29,16 @@ bool IsGestureEventFromTouchpad(const blink::WebInputEvent& event) {
return gesture.SourceDevice() == blink::kWebGestureDeviceTouchpad; return gesture.SourceDevice() == blink::kWebGestureDeviceTouchpad;
} }
bool IsGestureScrollUpdateInertialEvent(const blink::WebInputEvent& event) {
if (event.GetType() != blink::WebInputEvent::kGestureScrollUpdate)
return false;
const blink::WebGestureEvent& gesture =
static_cast<const blink::WebGestureEvent&>(event);
return gesture.data.scroll_update.inertial_phase ==
blink::WebGestureEvent::kMomentumPhase;
}
float ClampAbsoluteValue(float value, float max_abs) { float ClampAbsoluteValue(float value, float max_abs) {
DCHECK_LT(0.f, max_abs); DCHECK_LT(0.f, max_abs);
return std::max(-max_abs, std::min(value, max_abs)); return std::max(-max_abs, std::min(value, max_abs));
...@@ -86,17 +96,10 @@ bool OverscrollController::ShouldProcessEvent( ...@@ -86,17 +96,10 @@ bool OverscrollController::ShouldProcessEvent(
bool OverscrollController::ShouldIgnoreInertialEvent( bool OverscrollController::ShouldIgnoreInertialEvent(
const blink::WebInputEvent& event) const { const blink::WebInputEvent& event) const {
if (!ignore_following_inertial_events_ || return ignore_following_inertial_events_ &&
event.GetType() != blink::WebInputEvent::kGestureScrollUpdate) { IsGestureScrollUpdateInertialEvent(event);
return false;
} }
const blink::WebGestureEvent& gesture =
static_cast<const blink::WebGestureEvent&>(event);
return gesture.data.scroll_update.inertial_phase ==
blink::WebGestureEvent::kMomentumPhase;
}
bool OverscrollController::WillHandleEvent(const blink::WebInputEvent& event) { bool OverscrollController::WillHandleEvent(const blink::WebInputEvent& event) {
if (!ShouldProcessEvent(event)) if (!ShouldProcessEvent(event))
return false; return false;
...@@ -108,6 +111,7 @@ bool OverscrollController::WillHandleEvent(const blink::WebInputEvent& event) { ...@@ -108,6 +111,7 @@ bool OverscrollController::WillHandleEvent(const blink::WebInputEvent& event) {
if (event.GetType() == blink::WebInputEvent::kGestureScrollBegin) { if (event.GetType() == blink::WebInputEvent::kGestureScrollBegin) {
ignore_following_inertial_events_ = false; ignore_following_inertial_events_ = false;
first_inertial_event_time_.reset();
time_since_last_ignored_scroll_ = time_since_last_ignored_scroll_ =
event.TimeStamp() - last_ignored_scroll_time_; event.TimeStamp() - last_ignored_scroll_time_;
// Will handle events when processing ACKs to ensure the correct order. // Will handle events when processing ACKs to ensure the correct order.
...@@ -379,10 +383,29 @@ bool OverscrollController::ProcessEventForOverscroll( ...@@ -379,10 +383,29 @@ bool OverscrollController::ProcessEventForOverscroll(
case blink::WebInputEvent::kGestureScrollUpdate: { case blink::WebInputEvent::kGestureScrollUpdate: {
const blink::WebGestureEvent& gesture = const blink::WebGestureEvent& gesture =
static_cast<const blink::WebGestureEvent&>(event); static_cast<const blink::WebGestureEvent&>(event);
bool is_gesture_scroll_update_inertial_event =
IsGestureScrollUpdateInertialEvent(event);
event_processed = ProcessOverscroll( event_processed = ProcessOverscroll(
gesture.data.scroll_update.delta_x, gesture.data.scroll_update.delta_x,
gesture.data.scroll_update.delta_y, gesture.data.scroll_update.delta_y,
gesture.SourceDevice() == blink::kWebGestureDeviceTouchpad); gesture.SourceDevice() == blink::kWebGestureDeviceTouchpad,
is_gesture_scroll_update_inertial_event);
if (is_gesture_scroll_update_inertial_event) {
// Record the timestamp of first inertial event.
if (!first_inertial_event_time_) {
first_inertial_event_time_ = event.TimeStamp();
break;
}
base::TimeDelta inertial_event_interval =
event.TimeStamp() - first_inertial_event_time_.value();
if (inertial_event_interval >=
OverscrollConfig::MaxInertialEventsBeforeOverscrollCancellation()) {
ignore_following_inertial_events_ = true;
// Reset overscroll state if fling didn't complete the overscroll
// gesture within the first 20 inertial events.
Cancel();
}
}
break; break;
} }
case blink::WebInputEvent::kGestureFlingStart: { case blink::WebInputEvent::kGestureFlingStart: {
...@@ -422,10 +445,15 @@ bool OverscrollController::ProcessEventForOverscroll( ...@@ -422,10 +445,15 @@ bool OverscrollController::ProcessEventForOverscroll(
bool OverscrollController::ProcessOverscroll(float delta_x, bool OverscrollController::ProcessOverscroll(float delta_x,
float delta_y, float delta_y,
bool is_touchpad) { bool is_touchpad,
bool is_inertial) {
if (scroll_state_ == ScrollState::CONTENT_CONSUMING) if (scroll_state_ == ScrollState::CONTENT_CONSUMING)
return false; return false;
// Do not start overscroll for inertial events.
if (overscroll_mode_ == OVERSCROLL_NONE && is_inertial)
return false;
overscroll_delta_x_ += delta_x; overscroll_delta_x_ += delta_x;
overscroll_delta_y_ += delta_y; overscroll_delta_y_ += delta_y;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/compiler_specific.h" #include "base/compiler_specific.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "cc/input/overscroll_behavior.h" #include "cc/input/overscroll_behavior.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
...@@ -112,7 +113,10 @@ class CONTENT_EXPORT OverscrollController { ...@@ -112,7 +113,10 @@ class CONTENT_EXPORT OverscrollController {
// and the over scroll amount (i.e. |overscroll_mode_|, |overscroll_delta_x_| // and the over scroll amount (i.e. |overscroll_mode_|, |overscroll_delta_x_|
// and |overscroll_delta_y_|). Returns true if overscroll was handled by the // and |overscroll_delta_y_|). Returns true if overscroll was handled by the
// delegate. // delegate.
bool ProcessOverscroll(float delta_x, float delta_y, bool is_touchpad); bool ProcessOverscroll(float delta_x,
float delta_y,
bool is_touchpad,
bool is_inertial);
// Completes the desired action from the current gesture. // Completes the desired action from the current gesture.
void CompleteAction(); void CompleteAction();
...@@ -162,8 +166,9 @@ class CONTENT_EXPORT OverscrollController { ...@@ -162,8 +166,9 @@ class CONTENT_EXPORT OverscrollController {
bool wheel_scroll_latching_enabled_; bool wheel_scroll_latching_enabled_;
// A inertial scroll (fling) event may complete an overscroll gesture and // A inertial scroll (fling) event may complete an overscroll gesture and
// navigate to a new page, but the inertial scroll can continue to generate // navigate to a new page or cancel the overscroll animation. In both cases
// scroll-update events. These events need to be ignored. // inertial scroll can continue to generate scroll-update events. These events
// need to be ignored.
bool ignore_following_inertial_events_ = false; bool ignore_following_inertial_events_ = false;
// Specifies whether last overscroll was ignored, either due to a command line // Specifies whether last overscroll was ignored, either due to a command line
...@@ -177,6 +182,14 @@ class CONTENT_EXPORT OverscrollController { ...@@ -177,6 +182,14 @@ class CONTENT_EXPORT OverscrollController {
// of the current one. // of the current one.
base::TimeDelta time_since_last_ignored_scroll_; base::TimeDelta time_since_last_ignored_scroll_;
// On Windows, we don't generate the inertial events (fling) but receive them
// from Win API. In some cases, we get a long tail of inertial events for a
// couple of seconds. The overscroll animation feels like stuck in these
// cases. So we only process 0.3 second inertial events then cancel the
// overscroll if it is not completed yet.
// Timestamp for the first inertial event (fling) in current stream.
base::Optional<base::TimeTicks> first_inertial_event_time_;
DISALLOW_COPY_AND_ASSIGN(OverscrollController); DISALLOW_COPY_AND_ASSIGN(OverscrollController);
}; };
......
...@@ -201,6 +201,75 @@ TEST_F(OverscrollControllerTest, ...@@ -201,6 +201,75 @@ TEST_F(OverscrollControllerTest,
100, 0, blink::kWebGestureDeviceTouchpad, timestamp, true)); 100, 0, blink::kWebGestureDeviceTouchpad, timestamp, true));
} }
// Ensure inertial gesture scroll update can not start overscroll.
TEST_F(OverscrollControllerTest, InertialGSUsDoNotStartOverscroll) {
base::TimeTicks timestamp =
blink::WebInputEvent::GetStaticTimeStampForTests();
// Inertial update event complete the overscroll action.
EXPECT_FALSE(SimulateGestureScrollUpdate(
100, 0, blink::kWebGestureDeviceTouchpad, timestamp, true));
SimulateAck(false);
EXPECT_EQ(OVERSCROLL_NONE, controller_mode());
EXPECT_EQ(OverscrollSource::NONE, controller_source());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->current_mode());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->completed_mode());
}
// After 300ms inertial gesture scroll updates, overscroll must get cancelled
// if not completed.
TEST_F(OverscrollControllerTest, OnlyProcessLimitedInertialGSUEvents) {
base::TimeTicks timestamp =
blink::WebInputEvent::GetStaticTimeStampForTests();
EXPECT_FALSE(SimulateGestureEvent(blink::WebInputEvent::kGestureScrollBegin,
blink::kWebGestureDeviceTouchpad,
timestamp));
SimulateAck(false);
EXPECT_FALSE(SimulateGestureScrollUpdate(
61, 0, blink::kWebGestureDeviceTouchpad, timestamp, false));
SimulateAck(false);
EXPECT_EQ(OVERSCROLL_EAST, controller_mode());
EXPECT_EQ(OverscrollSource::TOUCHPAD, controller_source());
EXPECT_EQ(OVERSCROLL_EAST, delegate()->current_mode());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->completed_mode());
// First inertial.
timestamp += base::TimeDelta::FromSeconds(1);
EXPECT_TRUE(SimulateGestureScrollUpdate(
1, 0, blink::kWebGestureDeviceTouchpad, timestamp, true));
SimulateAck(true);
EXPECT_EQ(OVERSCROLL_EAST, controller_mode());
EXPECT_EQ(OverscrollSource::TOUCHPAD, controller_source());
EXPECT_EQ(OVERSCROLL_EAST, delegate()->current_mode());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->completed_mode());
// Not cancel in 10ms.
timestamp += base::TimeDelta::FromMilliseconds(10);
EXPECT_TRUE(SimulateGestureScrollUpdate(
1, 0, blink::kWebGestureDeviceTouchpad, timestamp, true));
SimulateAck(true);
EXPECT_EQ(OVERSCROLL_EAST, controller_mode());
EXPECT_EQ(OverscrollSource::TOUCHPAD, controller_source());
EXPECT_EQ(OVERSCROLL_EAST, delegate()->current_mode());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->completed_mode());
// Cancel after 300ms.
timestamp += base::TimeDelta::FromMilliseconds(291);
EXPECT_TRUE(SimulateGestureScrollUpdate(
1, 0, blink::kWebGestureDeviceTouchpad, timestamp, true));
SimulateAck(true);
EXPECT_EQ(OVERSCROLL_NONE, controller_mode());
EXPECT_EQ(OverscrollSource::NONE, controller_source());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->current_mode());
EXPECT_EQ(OVERSCROLL_NONE, delegate()->completed_mode());
// Next event should be ignored.
timestamp += base::TimeDelta::FromMilliseconds(100);
EXPECT_TRUE(SimulateGestureScrollUpdate(
1, 0, blink::kWebGestureDeviceTouchpad, timestamp, true));
}
// Verifies that when pull-to-refresh is disabled, it is not triggered for // Verifies that when pull-to-refresh is disabled, it is not triggered for
// neither touchpad nor touchscreen. // neither touchpad nor touchscreen.
TEST_F(OverscrollControllerTest, PullToRefreshDisabled) { TEST_F(OverscrollControllerTest, PullToRefreshDisabled) {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CONTENT_PUBLIC_BROWSER_OVERSCROLL_CONFIGURATION_H_ #define CONTENT_PUBLIC_BROWSER_OVERSCROLL_CONFIGURATION_H_
#include "base/macros.h" #include "base/macros.h"
#include "base/time/time.h"
#include "content/common/content_export.h" #include "content/common/content_export.h"
namespace content { namespace content {
...@@ -62,6 +63,8 @@ class CONTENT_EXPORT OverscrollConfig { ...@@ -62,6 +63,8 @@ class CONTENT_EXPORT OverscrollConfig {
static bool TouchpadOverscrollHistoryNavigationEnabled(); static bool TouchpadOverscrollHistoryNavigationEnabled();
static base::TimeDelta MaxInertialEventsBeforeOverscrollCancellation();
private: private:
friend class ScopedHistoryNavigationMode; friend class ScopedHistoryNavigationMode;
friend class ScopedPullToRefreshMode; friend class ScopedPullToRefreshMode;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment