blob: 4a48c8a2f3f87d7152964f592311d61dd9600fdb [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 "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),
scroll_phase_state_(SCROLL_STATE_UNKNOWN),
initial_wheel_modifiers_(0) {}
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;
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 (scroll_phase_state_) {
case 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)) {
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_modifiers_ = mouse_wheel_event.GetModifiers();
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 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();
}
}
last_mouse_wheel_event_ = mouse_wheel_event;
}
void MouseWheelPhaseHandler::DispatchPendingWheelEndEvent() {
if (!mouse_wheel_end_dispatch_timer_.IsRunning())
return;
base::Closure task = mouse_wheel_end_dispatch_timer_.user_task();
mouse_wheel_end_dispatch_timer_.Stop();
std::move(task).Run();
}
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) {
RenderWidgetHostImpl* widget_host = host_view_->host();
if (!widget_host) {
ResetScrollSequence();
return;
}
bool should_route_event = widget_host->delegate() &&
widget_host->delegate()->GetInputEventRouter();
SendSyntheticWheelEventWithPhaseEnded(should_route_event);
}
ResetScrollSequence();
}
void MouseWheelPhaseHandler::ScrollingMayBegin() {
scroll_phase_state_ = SCROLL_MAY_BEGIN;
}
void MouseWheelPhaseHandler::SendSyntheticWheelEventWithPhaseEnded(
bool should_route_event) {
DCHECK(host_view_->wheel_scroll_latching_enabled());
last_mouse_wheel_event_.SetTimeStampSeconds(
ui::EventTimeStampToSeconds(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) {
mouse_wheel_end_dispatch_timer_.Start(
FROM_HERE, timeout,
base::Bind(&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(scroll_phase_state_ == 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(scroll_phase_state_ == SCROLL_STATE_UNKNOWN);
DCHECK(scroll_phase_state_ == SCROLL_STATE_UNKNOWN);
return wheel_event.GetModifiers() != initial_wheel_modifiers_;
}
} // namespace content