Commit 58417b21 authored by Sahel Sharify's avatar Sahel Sharify Committed by Commit Bot

Use ui events to latch while scrolling by touchpad on chrome book.

Scrolling with trackpad on Chrome devices generates GFC when the user
touches the touchpad and GFS when the user lifts their fingers even when
fling velocity is zero. This cl uses these events for scroll latching
instead of the timer. Scrolling with a real mouse wheel is unchanged.

       TouchpadFlingStartResetsWheelPhaseState

Bug: 526463
Test: RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest.
Change-Id: Ie3bb30c57fc8fd50f543d2856e570e401f5a2caf
Reviewed-on: https://chromium-review.googlesource.com/757577
Commit-Queue: Sahel Sharifymoghaddam <sahel@chromium.org>
Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Cr-Commit-Position: refs/heads/master@{#516351}
parent 06f7acc6
...@@ -13,11 +13,15 @@ namespace content { ...@@ -13,11 +13,15 @@ namespace content {
MouseWheelPhaseHandler::MouseWheelPhaseHandler( MouseWheelPhaseHandler::MouseWheelPhaseHandler(
RenderWidgetHostImpl* const host, RenderWidgetHostImpl* const host,
RenderWidgetHostViewBase* const host_view) RenderWidgetHostViewBase* const host_view)
: host_(RenderWidgetHostImpl::From(host)), host_view_(host_view) {} : host_(RenderWidgetHostImpl::From(host)),
host_view_(host_view),
scroll_phase_state_(SCROLL_STATE_UNKNOWN) {}
void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent( void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
blink::WebMouseWheelEvent& mouse_wheel_event, blink::WebMouseWheelEvent& mouse_wheel_event,
bool should_route_event) { bool should_route_event) {
last_mouse_wheel_event_ = mouse_wheel_event;
bool has_phase = bool has_phase =
mouse_wheel_event.phase != blink::WebMouseWheelEvent::kPhaseNone || mouse_wheel_event.phase != blink::WebMouseWheelEvent::kPhaseNone ||
mouse_wheel_event.momentum_phase != blink::WebMouseWheelEvent::kPhaseNone; mouse_wheel_event.momentum_phase != blink::WebMouseWheelEvent::kPhaseNone;
...@@ -25,7 +29,7 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent( ...@@ -25,7 +29,7 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
if (mouse_wheel_event.phase == blink::WebMouseWheelEvent::kPhaseEnded) { if (mouse_wheel_event.phase == blink::WebMouseWheelEvent::kPhaseEnded) {
// Don't send the wheel end event immediately, start a timer instead to // Don't send the wheel end event immediately, start a timer instead to
// see whether momentum phase of the scrolling starts or not. // see whether momentum phase of the scrolling starts or not.
ScheduleMouseWheelEndDispatching(mouse_wheel_event, should_route_event); ScheduleMouseWheelEndDispatching(should_route_event);
} else if (mouse_wheel_event.phase == } else if (mouse_wheel_event.phase ==
blink::WebMouseWheelEvent::kPhaseBegan) { blink::WebMouseWheelEvent::kPhaseBegan) {
// A new scrolling sequence has started, send the pending wheel end // A new scrolling sequence has started, send the pending wheel end
...@@ -39,9 +43,11 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent( ...@@ -39,9 +43,11 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
IgnorePendingWheelEndEvent(); IgnorePendingWheelEndEvent();
} }
} else { // !has_phase } else { // !has_phase
switch (scroll_phase_state_) {
case SCROLL_STATE_UNKNOWN: {
if (!mouse_wheel_end_dispatch_timer_.IsRunning()) { if (!mouse_wheel_end_dispatch_timer_.IsRunning()) {
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan; mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
ScheduleMouseWheelEndDispatching(mouse_wheel_event, should_route_event); ScheduleMouseWheelEndDispatching(should_route_event);
} else { // mouse_wheel_end_dispatch_timer_.IsRunning() } else { // mouse_wheel_end_dispatch_timer_.IsRunning()
bool non_zero_delta = bool non_zero_delta =
mouse_wheel_event.delta_x || mouse_wheel_event.delta_y; mouse_wheel_event.delta_x || mouse_wheel_event.delta_y;
...@@ -50,6 +56,18 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent( ...@@ -50,6 +56,18 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
: blink::WebMouseWheelEvent::kPhaseStationary; : blink::WebMouseWheelEvent::kPhaseStationary;
mouse_wheel_end_dispatch_timer_.Reset(); mouse_wheel_end_dispatch_timer_.Reset();
} }
break;
}
case SCROLL_MAY_BEGIN:
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
scroll_phase_state_ = SCROLL_IN_PROGRESS;
break;
case SCROLL_IN_PROGRESS:
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseChanged;
break;
default:
NOTREACHED();
}
} }
} }
...@@ -66,40 +84,56 @@ void MouseWheelPhaseHandler::IgnorePendingWheelEndEvent() { ...@@ -66,40 +84,56 @@ void MouseWheelPhaseHandler::IgnorePendingWheelEndEvent() {
mouse_wheel_end_dispatch_timer_.Stop(); mouse_wheel_end_dispatch_timer_.Stop();
} }
void MouseWheelPhaseHandler::ResetScrollSequence() {
scroll_phase_state_ = SCROLL_STATE_UNKNOWN;
}
void MouseWheelPhaseHandler::SendWheelEndIfNeeded() {
if (scroll_phase_state_ == SCROLL_IN_PROGRESS) {
DCHECK(host_);
bool should_route_event =
host_->delegate() && host_->delegate()->GetInputEventRouter();
SendSyntheticWheelEventWithPhaseEnded(should_route_event);
}
ResetScrollSequence();
}
void MouseWheelPhaseHandler::ScrollingMayBegin() {
scroll_phase_state_ = SCROLL_MAY_BEGIN;
}
void MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded( void MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded(
blink::WebMouseWheelEvent last_mouse_wheel_event,
bool should_route_event) { bool should_route_event) {
DCHECK(host_view_->wheel_scroll_latching_enabled()); DCHECK(host_view_->wheel_scroll_latching_enabled());
blink::WebMouseWheelEvent mouse_wheel_event = last_mouse_wheel_event; last_mouse_wheel_event_.SetTimeStampSeconds(
mouse_wheel_event.SetTimeStampSeconds(
ui::EventTimeStampToSeconds(ui::EventTimeForNow())); ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
mouse_wheel_event.delta_x = 0; last_mouse_wheel_event_.delta_x = 0;
mouse_wheel_event.delta_y = 0; last_mouse_wheel_event_.delta_y = 0;
mouse_wheel_event.wheel_ticks_x = 0; last_mouse_wheel_event_.wheel_ticks_x = 0;
mouse_wheel_event.wheel_ticks_y = 0; last_mouse_wheel_event_.wheel_ticks_y = 0;
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseEnded; last_mouse_wheel_event_.phase = blink::WebMouseWheelEvent::kPhaseEnded;
mouse_wheel_event.dispatch_type = last_mouse_wheel_event_.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking; blink::WebInputEvent::DispatchType::kEventNonBlocking;
if (should_route_event) { if (should_route_event) {
host_->delegate()->GetInputEventRouter()->RouteMouseWheelEvent( host_->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
host_view_, &mouse_wheel_event, host_view_, &last_mouse_wheel_event_,
ui::LatencyInfo(ui::SourceEventType::WHEEL)); ui::LatencyInfo(ui::SourceEventType::WHEEL));
} else { } else {
host_view_->ProcessMouseWheelEvent( host_view_->ProcessMouseWheelEvent(
mouse_wheel_event, ui::LatencyInfo(ui::SourceEventType::WHEEL)); last_mouse_wheel_event_, ui::LatencyInfo(ui::SourceEventType::WHEEL));
} }
} }
void MouseWheelPhaseHandler::ScheduleMouseWheelEndDispatching( void MouseWheelPhaseHandler::ScheduleMouseWheelEndDispatching(
blink::WebMouseWheelEvent wheel_event,
bool should_route_event) { bool should_route_event) {
mouse_wheel_end_dispatch_timer_.Start( mouse_wheel_end_dispatch_timer_.Start(
FROM_HERE, FROM_HERE,
base::TimeDelta::FromMilliseconds( base::TimeDelta::FromMilliseconds(
kDefaultMouseWheelLatchingTransactionMs), kDefaultMouseWheelLatchingTransactionMs),
base::Bind(&MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded, base::Bind(&MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded,
base::Unretained(this), wheel_event, should_route_event)); base::Unretained(this), should_route_event));
} }
} // namespace content } // namespace content
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h" #include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "third_party/WebKit/public/platform/WebMouseWheelEvent.h"
namespace content { namespace content {
class RenderWidgetHostImpl; class RenderWidgetHostImpl;
...@@ -16,6 +17,18 @@ class RenderWidgetHostViewBase; ...@@ -16,6 +17,18 @@ class RenderWidgetHostViewBase;
// phase = |kPhaseEnded| will be sent after the last wheel event. // phase = |kPhaseEnded| will be sent after the last wheel event.
const int64_t kDefaultMouseWheelLatchingTransactionMs = 100; const int64_t kDefaultMouseWheelLatchingTransactionMs = 100;
// On ChromeOS wheel events don't have phase information; However, whenever the
// user puts down or lifts their fingers a GFC or GFS is received.
enum ScrollPhaseState {
// Scrolling with normal mouse wheels doesn't give any information about the
// state of scrolling.
SCROLL_STATE_UNKNOWN = 0,
// Shows that the user has put their fingers down and a scroll may start.
SCROLL_MAY_BEGIN,
// Scrolling has started and the user hasn't lift their fingers, yet.
SCROLL_IN_PROGRESS,
};
class MouseWheelPhaseHandler { class MouseWheelPhaseHandler {
public: public:
MouseWheelPhaseHandler(RenderWidgetHostImpl* const host, MouseWheelPhaseHandler(RenderWidgetHostImpl* const host,
...@@ -27,20 +40,24 @@ class MouseWheelPhaseHandler { ...@@ -27,20 +40,24 @@ class MouseWheelPhaseHandler {
bool should_route_event); bool should_route_event);
void DispatchPendingWheelEndEvent(); void DispatchPendingWheelEndEvent();
void IgnorePendingWheelEndEvent(); void IgnorePendingWheelEndEvent();
void ResetScrollSequence();
void SendWheelEndIfNeeded();
void ScrollingMayBegin();
bool HasPendingWheelEndEvent() const { bool HasPendingWheelEndEvent() const {
return mouse_wheel_end_dispatch_timer_.IsRunning(); return mouse_wheel_end_dispatch_timer_.IsRunning();
} }
private: private:
void SendSyntheticWheelEventWithPhaseEnded( void SendSyntheticWheelEventWithPhaseEnded(
blink::WebMouseWheelEvent last_mouse_wheel_event,
bool should_route_event);
void ScheduleMouseWheelEndDispatching(blink::WebMouseWheelEvent wheel_event,
bool should_route_event); bool should_route_event);
void ScheduleMouseWheelEndDispatching(bool should_route_event);
RenderWidgetHostImpl* const host_; RenderWidgetHostImpl* const host_;
RenderWidgetHostViewBase* const host_view_; RenderWidgetHostViewBase* const host_view_;
base::OneShotTimer mouse_wheel_end_dispatch_timer_; base::OneShotTimer mouse_wheel_end_dispatch_timer_;
blink::WebMouseWheelEvent last_mouse_wheel_event_;
ScrollPhaseState scroll_phase_state_;
DISALLOW_COPY_AND_ASSIGN(MouseWheelPhaseHandler); DISALLOW_COPY_AND_ASSIGN(MouseWheelPhaseHandler);
}; };
......
...@@ -1947,11 +1947,16 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest, ...@@ -1947,11 +1947,16 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
EXPECT_EQ(WebInputEvent::kGestureScrollEnd, gesture_event->GetType()); EXPECT_EQ(WebInputEvent::kGestureScrollEnd, gesture_event->GetType());
} }
// Tests that a gesture fling start with touchpad source stops the // Tests that a gesture fling start with touchpad source resets wheel phase
// RenderWidgetHostViewEventHandler::mouse_wheel_phase_timer_ and no synthetic // state.
// wheel event will be sent.
TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest, TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
TouchpadFlingStartStopsWheelPhaseTimer) { TouchpadFlingStartResetsWheelPhaseState) {
// When the user puts their fingers down a GFC is receieved.
ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 0, 0, 0, 2);
view_->OnScrollEvent(&fling_cancel);
// Scrolling starts.
ui::ScrollEvent scroll0(ui::ET_SCROLL, gfx::Point(2, 2), ui::ScrollEvent scroll0(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 5, 0, 5, 2); ui::EventTimeForNow(), 0, 0, 5, 0, 5, 2);
view_->OnScrollEvent(&scroll0); view_->OnScrollEvent(&scroll0);
...@@ -1980,6 +1985,36 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest, ...@@ -1980,6 +1985,36 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
EXPECT_EQ(5U, gesture_event->data.scroll_update.delta_y); EXPECT_EQ(5U, gesture_event->data.scroll_update.delta_y);
events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); events[1]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
// Wait for some time and resume scrolling. The second scroll will latch since
// the user hasn't lifted their fingers, yet.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(
2 * kDefaultMouseWheelLatchingTransactionMs));
run_loop.Run();
ui::ScrollEvent scroll1(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 15, 0, 15, 2);
view_->OnScrollEvent(&scroll1);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(1U, events.size());
wheel_event = static_cast<const WebMouseWheelEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, wheel_event->phase);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
events = GetAndResetDispatchedMessages();
EXPECT_EQ("GestureScrollUpdate", GetMessageNames(events));
gesture_event = static_cast<const WebGestureEvent*>(
events[0]->ToEvent()->Event()->web_event.get());
EXPECT_EQ(WebInputEvent::kGestureScrollUpdate, gesture_event->GetType());
EXPECT_EQ(0U, gesture_event->data.scroll_update.delta_x);
EXPECT_EQ(15U, gesture_event->data.scroll_update.delta_y);
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
// A GFS is received showing that the user has lifted their fingers. This will
// reset the scroll state of the wheel phase handler.
ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, gfx::Point(2, 2), ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 10, 0, 10, 2); ui::EventTimeForNow(), 0, 0, 10, 0, 10, 2);
view_->OnScrollEvent(&fling_start); view_->OnScrollEvent(&fling_start);
...@@ -1993,22 +2028,11 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest, ...@@ -1993,22 +2028,11 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED); events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
SendNotConsumedAcks(events); SendNotConsumedAcks(events);
// Let the MouseWheelPhaseHandler::mouse_wheel_end_dispatch_timer_ fire. No
// synthetic wheel event will be sent since the timer has stopped.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(),
base::TimeDelta::FromMilliseconds(
kDefaultMouseWheelLatchingTransactionMs));
base::RunLoop().Run();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(0U, events.size());
// Handling the next ui::ET_SCROLL event will send a fling cancellation and a // Handling the next ui::ET_SCROLL event will send a fling cancellation and a
// mouse wheel with kPhaseBegan. // mouse wheel with kPhaseBegan.
ui::ScrollEvent scroll1(ui::ET_SCROLL, gfx::Point(2, 2), ui::ScrollEvent scroll2(ui::ET_SCROLL, gfx::Point(2, 2),
ui::EventTimeForNow(), 0, 0, 15, 0, 15, 2); ui::EventTimeForNow(), 0, 0, 15, 0, 15, 2);
view_->OnScrollEvent(&scroll1); view_->OnScrollEvent(&scroll2);
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages(); events = GetAndResetDispatchedMessages();
EXPECT_EQ(2U, events.size()); EXPECT_EQ(2U, events.size());
......
...@@ -445,9 +445,12 @@ void RenderWidgetHostViewEventHandler::OnScrollEvent(ui::ScrollEvent* event) { ...@@ -445,9 +445,12 @@ void RenderWidgetHostViewEventHandler::OnScrollEvent(ui::ScrollEvent* event) {
} }
if (event->type() == ui::ET_SCROLL_FLING_START) { if (event->type() == ui::ET_SCROLL_FLING_START) {
RecordAction(base::UserMetricsAction("TrackpadScrollFling")); RecordAction(base::UserMetricsAction("TrackpadScrollFling"));
// Ignore the pending wheel end event to avoid sending a wheel event with // The user has lifted their fingers.
// kPhaseEnded before a GFS. mouse_wheel_phase_handler_.ResetScrollSequence();
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent(); } else if (event->type() == ui::ET_SCROLL_FLING_CANCEL) {
// The user has put their fingers down.
DCHECK_EQ(blink::kWebGestureDeviceTouchpad, gesture_event.source_device);
mouse_wheel_phase_handler_.ScrollingMayBegin();
} }
} }
...@@ -546,11 +549,13 @@ void RenderWidgetHostViewEventHandler::OnGestureEvent(ui::GestureEvent* event) { ...@@ -546,11 +549,13 @@ void RenderWidgetHostViewEventHandler::OnGestureEvent(ui::GestureEvent* event) {
// wheel based send a synthetic wheel event with kPhaseEnded to cancel // wheel based send a synthetic wheel event with kPhaseEnded to cancel
// the current scroll. // the current scroll.
mouse_wheel_phase_handler_.DispatchPendingWheelEndEvent(); mouse_wheel_phase_handler_.DispatchPendingWheelEndEvent();
mouse_wheel_phase_handler_.SendWheelEndIfNeeded();
} else if (event->type() == ui::ET_GESTURE_SCROLL_END) { } else if (event->type() == ui::ET_GESTURE_SCROLL_END) {
// Make sure that the next wheel event will have phase = |kPhaseBegan|. // Make sure that the next wheel event will have phase = |kPhaseBegan|.
// This is for maintaining the correct phase info when some of the wheel // This is for maintaining the correct phase info when some of the wheel
// events get ignored while a touchscreen scroll is going on. // events get ignored while a touchscreen scroll is going on.
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent(); mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent();
mouse_wheel_phase_handler_.ResetScrollSequence();
} else if (event->type() == ui::ET_SCROLL_FLING_START) { } else if (event->type() == ui::ET_SCROLL_FLING_START) {
RecordAction(base::UserMetricsAction("TouchscreenScrollFling")); RecordAction(base::UserMetricsAction("TouchscreenScrollFling"));
} }
......
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