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 {
MouseWheelPhaseHandler::MouseWheelPhaseHandler(
RenderWidgetHostImpl* const host,
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(
blink::WebMouseWheelEvent& mouse_wheel_event,
bool should_route_event) {
last_mouse_wheel_event_ = mouse_wheel_event;
bool has_phase =
mouse_wheel_event.phase != blink::WebMouseWheelEvent::kPhaseNone ||
mouse_wheel_event.momentum_phase != blink::WebMouseWheelEvent::kPhaseNone;
......@@ -25,7 +29,7 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
if (mouse_wheel_event.phase == blink::WebMouseWheelEvent::kPhaseEnded) {
// Don't send the wheel end event immediately, start a timer instead to
// 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 ==
blink::WebMouseWheelEvent::kPhaseBegan) {
// A new scrolling sequence has started, send the pending wheel end
......@@ -39,16 +43,30 @@ void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
IgnorePendingWheelEndEvent();
}
} else { // !has_phase
if (!mouse_wheel_end_dispatch_timer_.IsRunning()) {
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
ScheduleMouseWheelEndDispatching(mouse_wheel_event, should_route_event);
} else { // mouse_wheel_end_dispatch_timer_.IsRunning()
bool non_zero_delta =
mouse_wheel_event.delta_x || mouse_wheel_event.delta_y;
mouse_wheel_event.phase =
non_zero_delta ? blink::WebMouseWheelEvent::kPhaseChanged
: blink::WebMouseWheelEvent::kPhaseStationary;
mouse_wheel_end_dispatch_timer_.Reset();
switch (scroll_phase_state_) {
case SCROLL_STATE_UNKNOWN: {
if (!mouse_wheel_end_dispatch_timer_.IsRunning()) {
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
ScheduleMouseWheelEndDispatching(should_route_event);
} else { // mouse_wheel_end_dispatch_timer_.IsRunning()
bool non_zero_delta =
mouse_wheel_event.delta_x || mouse_wheel_event.delta_y;
mouse_wheel_event.phase =
non_zero_delta ? blink::WebMouseWheelEvent::kPhaseChanged
: blink::WebMouseWheelEvent::kPhaseStationary;
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() {
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(
blink::WebMouseWheelEvent last_mouse_wheel_event,
bool should_route_event) {
DCHECK(host_view_->wheel_scroll_latching_enabled());
blink::WebMouseWheelEvent mouse_wheel_event = last_mouse_wheel_event;
mouse_wheel_event.SetTimeStampSeconds(
last_mouse_wheel_event_.SetTimeStampSeconds(
ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
mouse_wheel_event.delta_x = 0;
mouse_wheel_event.delta_y = 0;
mouse_wheel_event.wheel_ticks_x = 0;
mouse_wheel_event.wheel_ticks_y = 0;
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseEnded;
mouse_wheel_event.dispatch_type =
last_mouse_wheel_event_.delta_x = 0;
last_mouse_wheel_event_.delta_y = 0;
last_mouse_wheel_event_.wheel_ticks_x = 0;
last_mouse_wheel_event_.wheel_ticks_y = 0;
last_mouse_wheel_event_.phase = blink::WebMouseWheelEvent::kPhaseEnded;
last_mouse_wheel_event_.dispatch_type =
blink::WebInputEvent::DispatchType::kEventNonBlocking;
if (should_route_event) {
host_->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
host_view_, &mouse_wheel_event,
host_view_, &last_mouse_wheel_event_,
ui::LatencyInfo(ui::SourceEventType::WHEEL));
} else {
host_view_->ProcessMouseWheelEvent(
mouse_wheel_event, ui::LatencyInfo(ui::SourceEventType::WHEEL));
last_mouse_wheel_event_, ui::LatencyInfo(ui::SourceEventType::WHEEL));
}
}
void MouseWheelPhaseHandler::ScheduleMouseWheelEndDispatching(
blink::WebMouseWheelEvent wheel_event,
bool should_route_event) {
mouse_wheel_end_dispatch_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(
kDefaultMouseWheelLatchingTransactionMs),
base::Bind(&MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded,
base::Unretained(this), wheel_event, should_route_event));
base::Unretained(this), should_route_event));
}
} // namespace content
......@@ -7,6 +7,7 @@
#include "base/timer/timer.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "third_party/WebKit/public/platform/WebMouseWheelEvent.h"
namespace content {
class RenderWidgetHostImpl;
......@@ -16,6 +17,18 @@ class RenderWidgetHostViewBase;
// phase = |kPhaseEnded| will be sent after the last wheel event.
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 {
public:
MouseWheelPhaseHandler(RenderWidgetHostImpl* const host,
......@@ -27,20 +40,24 @@ class MouseWheelPhaseHandler {
bool should_route_event);
void DispatchPendingWheelEndEvent();
void IgnorePendingWheelEndEvent();
void ResetScrollSequence();
void SendWheelEndIfNeeded();
void ScrollingMayBegin();
bool HasPendingWheelEndEvent() const {
return mouse_wheel_end_dispatch_timer_.IsRunning();
}
private:
void SendSyntheticWheelEventWithPhaseEnded(
blink::WebMouseWheelEvent last_mouse_wheel_event,
bool should_route_event);
void ScheduleMouseWheelEndDispatching(blink::WebMouseWheelEvent wheel_event,
bool should_route_event);
void ScheduleMouseWheelEndDispatching(bool should_route_event);
RenderWidgetHostImpl* const host_;
RenderWidgetHostViewBase* const host_view_;
base::OneShotTimer mouse_wheel_end_dispatch_timer_;
blink::WebMouseWheelEvent last_mouse_wheel_event_;
ScrollPhaseState scroll_phase_state_;
DISALLOW_COPY_AND_ASSIGN(MouseWheelPhaseHandler);
};
......
......@@ -1947,11 +1947,16 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
EXPECT_EQ(WebInputEvent::kGestureScrollEnd, gesture_event->GetType());
}
// Tests that a gesture fling start with touchpad source stops the
// RenderWidgetHostViewEventHandler::mouse_wheel_phase_timer_ and no synthetic
// wheel event will be sent.
// Tests that a gesture fling start with touchpad source resets wheel phase
// state.
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::EventTimeForNow(), 0, 0, 5, 0, 5, 2);
view_->OnScrollEvent(&scroll0);
......@@ -1980,6 +1985,36 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
EXPECT_EQ(5U, gesture_event->data.scroll_update.delta_y);
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::EventTimeForNow(), 0, 0, 10, 0, 10, 2);
view_->OnScrollEvent(&fling_start);
......@@ -1993,22 +2028,11 @@ TEST_F(RenderWidgetHostViewAuraWheelScrollLatchingEnabledTest,
events[0]->ToEvent()->CallCallback(INPUT_EVENT_ACK_STATE_CONSUMED);
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
// 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);
view_->OnScrollEvent(&scroll1);
view_->OnScrollEvent(&scroll2);
base::RunLoop().RunUntilIdle();
events = GetAndResetDispatchedMessages();
EXPECT_EQ(2U, events.size());
......
......@@ -445,9 +445,12 @@ void RenderWidgetHostViewEventHandler::OnScrollEvent(ui::ScrollEvent* event) {
}
if (event->type() == ui::ET_SCROLL_FLING_START) {
RecordAction(base::UserMetricsAction("TrackpadScrollFling"));
// Ignore the pending wheel end event to avoid sending a wheel event with
// kPhaseEnded before a GFS.
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent();
// The user has lifted their fingers.
mouse_wheel_phase_handler_.ResetScrollSequence();
} 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) {
// wheel based send a synthetic wheel event with kPhaseEnded to cancel
// the current scroll.
mouse_wheel_phase_handler_.DispatchPendingWheelEndEvent();
mouse_wheel_phase_handler_.SendWheelEndIfNeeded();
} else if (event->type() == ui::ET_GESTURE_SCROLL_END) {
// Make sure that the next wheel event will have phase = |kPhaseBegan|.
// This is for maintaining the correct phase info when some of the wheel
// events get ignored while a touchscreen scroll is going on.
mouse_wheel_phase_handler_.IgnorePendingWheelEndEvent();
mouse_wheel_phase_handler_.ResetScrollSequence();
} else if (event->type() == ui::ET_SCROLL_FLING_START) {
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