blob: 12fe3c1241f18ca98b57585a62b5eae7e9a65937 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/input/mouse_wheel_phase_handler.h"
#include "base/bind.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "ui/events/base_event_utils.h"
namespace content {
MouseWheelPhaseHandler::MouseWheelPhaseHandler(
RenderWidgetHostViewBase* const host_view)
: host_view_(host_view),
mouse_wheel_end_dispatch_timeout_(kDefaultMouseWheelLatchingTransaction),
touchpad_scroll_phase_state_(TOUCHPAD_SCROLL_STATE_UNKNOWN) {}
void MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent(
blink::WebMouseWheelEvent& mouse_wheel_event,
bool should_route_event) {
bool has_phase =
mouse_wheel_event.phase != blink::WebMouseWheelEvent::kPhaseNone ||
mouse_wheel_event.momentum_phase != blink::WebMouseWheelEvent::kPhaseNone;
TRACE_EVENT2("input",
"MouseWheelPhaseHandler::AddPhaseIfNeededAndScheduleEndEvent",
"has_phase", has_phase, "touchpad_scroll_phase_state",
touchpad_scroll_phase_state_);
if (has_phase) {
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(
should_route_event,
kMaximumTimeBetweenPhaseEndedAndMomentumPhaseBegan);
} else if (mouse_wheel_event.phase ==
blink::WebMouseWheelEvent::kPhaseBegan) {
// A new scrolling sequence has started, send the pending wheel end
// event to end the previous scrolling sequence.
DispatchPendingWheelEndEvent();
} else if (mouse_wheel_event.momentum_phase ==
blink::WebMouseWheelEvent::kPhaseBegan) {
// Momentum phase has started, drop the pending wheel end event to make
// sure that no wheel end event will be sent during the momentum phase
// of scrolling.
IgnorePendingWheelEndEvent();
}
} else { // !has_phase
switch (touchpad_scroll_phase_state_) {
case TOUCHPAD_SCROLL_STATE_UNKNOWN: {
mouse_wheel_event.has_synthetic_phase = true;
// Break the latching when the location difference between the current
// and the initial wheel event positions exceeds the maximum allowed
// threshold or when the event modifiers have changed.
if (!IsWithinSlopRegion(mouse_wheel_event) ||
HasDifferentModifiers(mouse_wheel_event) ||
ShouldBreakLatchingDueToDirectionChange(mouse_wheel_event)) {
DispatchPendingWheelEndEvent();
}
if (!mouse_wheel_end_dispatch_timer_.IsRunning()) {
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
first_wheel_location_ =
gfx::Vector2dF(mouse_wheel_event.PositionInWidget().x,
mouse_wheel_event.PositionInWidget().y);
initial_wheel_event_ = mouse_wheel_event;
first_scroll_update_ack_state_ =
FirstScrollUpdateAckState::kNotArrived;
ScheduleMouseWheelEndDispatching(should_route_event,
mouse_wheel_end_dispatch_timeout_);
} 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 TOUCHPAD_SCROLL_MAY_BEGIN:
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseBegan;
touchpad_scroll_phase_state_ = TOUCHPAD_SCROLL_IN_PROGRESS;
break;
case TOUCHPAD_SCROLL_IN_PROGRESS:
mouse_wheel_event.phase = blink::WebMouseWheelEvent::kPhaseChanged;
break;
default:
NOTREACHED();
}
}
last_mouse_wheel_event_ = mouse_wheel_event;
}
void MouseWheelPhaseHandler::DispatchPendingWheelEndEvent() {
if (!mouse_wheel_end_dispatch_timer_.IsRunning())
return;
TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler Dispatched",
TRACE_EVENT_SCOPE_THREAD);
mouse_wheel_end_dispatch_timer_.FireNow();
}
void MouseWheelPhaseHandler::IgnorePendingWheelEndEvent() {
if (!mouse_wheel_end_dispatch_timer_.IsRunning())
return;
TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler Ignored",
TRACE_EVENT_SCOPE_THREAD);
mouse_wheel_end_dispatch_timer_.Stop();
}
void MouseWheelPhaseHandler::ResetTouchpadScrollSequence() {
TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler Reset",
TRACE_EVENT_SCOPE_THREAD);
touchpad_scroll_phase_state_ = TOUCHPAD_SCROLL_STATE_UNKNOWN;
}
void MouseWheelPhaseHandler::SendWheelEndForTouchpadScrollingIfNeeded(
bool should_route_event) {
if (touchpad_scroll_phase_state_ == TOUCHPAD_SCROLL_IN_PROGRESS) {
RenderWidgetHostImpl* widget_host = host_view_->host();
if (!widget_host) {
ResetTouchpadScrollSequence();
return;
}
TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler Sent touchpad end",
TRACE_EVENT_SCOPE_THREAD);
SendSyntheticWheelEventWithPhaseEnded(should_route_event);
}
ResetTouchpadScrollSequence();
}
void MouseWheelPhaseHandler::TouchpadScrollingMayBegin() {
// End the timer-based wheel scroll sequence before starting a touchpad scroll
// sequence.
if (mouse_wheel_end_dispatch_timer_.IsRunning()) {
DCHECK_EQ(TOUCHPAD_SCROLL_STATE_UNKNOWN, touchpad_scroll_phase_state_);
DispatchPendingWheelEndEvent();
}
TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler May Begin",
TRACE_EVENT_SCOPE_THREAD);
touchpad_scroll_phase_state_ = TOUCHPAD_SCROLL_MAY_BEGIN;
}
void MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded(
bool should_route_event) {
TRACE_EVENT0("input",
"MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded");
last_mouse_wheel_event_.SetTimeStamp(ui::EventTimeForNow());
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) {
RenderWidgetHostImpl* widget_host = host_view_->host();
if (!widget_host || !widget_host->delegate() ||
!widget_host->delegate()->GetInputEventRouter())
return;
widget_host->delegate()->GetInputEventRouter()->RouteMouseWheelEvent(
host_view_, &last_mouse_wheel_event_,
ui::LatencyInfo(ui::SourceEventType::WHEEL));
} else {
host_view_->ProcessMouseWheelEvent(
last_mouse_wheel_event_, ui::LatencyInfo(ui::SourceEventType::WHEEL));
}
}
void MouseWheelPhaseHandler::ScheduleMouseWheelEndDispatching(
bool should_route_event,
const base::TimeDelta timeout) {
TRACE_EVENT_INSTANT0("input", "MouseWheelPhaseHandler timer started",
TRACE_EVENT_SCOPE_THREAD);
mouse_wheel_end_dispatch_timer_.Start(
FROM_HERE, timeout,
base::BindOnce(
&MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded,
base::Unretained(this), should_route_event));
}
bool MouseWheelPhaseHandler::IsWithinSlopRegion(
const blink::WebMouseWheelEvent& wheel_event) const {
// This function is called to check if breaking timer-based wheel scroll
// latching sequence is needed or not, and timer-based wheel scroll latching
// happens only when scroll state is unknown.
DCHECK(touchpad_scroll_phase_state_ == TOUCHPAD_SCROLL_STATE_UNKNOWN);
gfx::Vector2dF current_wheel_location(wheel_event.PositionInWidget().x,
wheel_event.PositionInWidget().y);
return (current_wheel_location - first_wheel_location_).LengthSquared() <
kWheelLatchingSlopRegion * kWheelLatchingSlopRegion;
}
bool MouseWheelPhaseHandler::HasDifferentModifiers(
const blink::WebMouseWheelEvent& wheel_event) const {
// This function is called to check if breaking timer-based wheel scroll
// latching sequence is needed or not, and timer-based wheel scroll latching
// happens only when scroll state is unknown.
DCHECK(touchpad_scroll_phase_state_ == TOUCHPAD_SCROLL_STATE_UNKNOWN);
return wheel_event.GetModifiers() != initial_wheel_event_.GetModifiers();
}
bool MouseWheelPhaseHandler::ShouldBreakLatchingDueToDirectionChange(
const blink::WebMouseWheelEvent& wheel_event) const {
// This function is called to check if breaking timer-based wheel scroll
// latching sequence is needed or not, and timer-based wheel scroll latching
// happens only when scroll state is unknown.
DCHECK(touchpad_scroll_phase_state_ == TOUCHPAD_SCROLL_STATE_UNKNOWN);
if (first_scroll_update_ack_state_ != FirstScrollUpdateAckState::kNotConsumed)
return false;
bool consistent_x_direction =
(wheel_event.delta_x == 0 && initial_wheel_event_.delta_x == 0) ||
wheel_event.delta_x * initial_wheel_event_.delta_x > 0;
bool consistent_y_direction =
(wheel_event.delta_y == 0 && initial_wheel_event_.delta_y == 0) ||
wheel_event.delta_y * initial_wheel_event_.delta_y > 0;
return !consistent_x_direction || !consistent_y_direction;
}
void MouseWheelPhaseHandler::GestureEventAck(
const blink::WebGestureEvent& event,
InputEventAckState ack_result) {
if (event.GetType() != blink::WebInputEvent::kGestureScrollUpdate ||
first_scroll_update_ack_state_ !=
FirstScrollUpdateAckState::kNotArrived) {
return;
}
if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED)
first_scroll_update_ack_state_ = FirstScrollUpdateAckState::kConsumed;
else
first_scroll_update_ack_state_ = FirstScrollUpdateAckState::kNotConsumed;
}
} // namespace content