blob: 545fac1c32d04a4effbead288907e8088f98cc6e [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/fling_controller.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/input/gesture_event_queue.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/fling_booster.h"
#include "ui/events/gestures/blink/web_gesture_curve_impl.h"
using blink::WebInputEvent;
using blink::WebGestureEvent;
namespace {
constexpr base::TimeDelta kFrameDelta =
base::TimeDelta::FromSecondsD(1.0 / 60.0);
// Maximum time between a fling event's timestamp and the first |Progress| call
// for the fling curve to use the fling timestamp as the initial animation time.
// Two frames allows a minor delay between event creation and the first
// progress.
constexpr base::TimeDelta kMaxMicrosecondsFromFlingTimestampToFirstProgress =
base::TimeDelta::FromSecondsD(2.0 / 60.0);
// Since the progress fling is called in ProcessGestureFlingStart right after
// processing the GFS, it is possible to have a very small delta for the first
// event. Don't send an event with deltas smaller than the
// |kMinInertialScrollDelta| since the renderer ignores it and the fling gets
// cancelled in FlingController::OnGestureEventAck due to an inertial GSU with
// ack ignored.
const float kMinInertialScrollDelta = 0.1f;
const char* kFlingTraceName = "FlingController::HandlingGestureFling";
} // namespace
namespace content {
FlingController::Config::Config() {}
FlingController::FlingController(
FlingControllerEventSenderClient* event_sender_client,
FlingControllerSchedulerClient* scheduler_client,
const Config& config)
: event_sender_client_(event_sender_client),
scheduler_client_(scheduler_client),
touchpad_tap_suppression_controller_(
config.touchpad_tap_suppression_config),
touchscreen_tap_suppression_controller_(
config.touchscreen_tap_suppression_config),
fling_in_progress_(false),
clock_(base::DefaultTickClock::GetInstance()),
weak_ptr_factory_(this) {
DCHECK(event_sender_client);
DCHECK(scheduler_client);
}
FlingController::~FlingController() = default;
bool FlingController::ShouldForwardForGFCFiltering(
const GestureEventWithLatencyInfo& gesture_event) const {
if (gesture_event.event.GetType() != WebInputEvent::kGestureFlingCancel)
return true;
if (fling_in_progress_)
return !fling_booster_->fling_cancellation_is_deferred();
return false;
}
bool FlingController::ShouldForwardForTapSuppression(
const GestureEventWithLatencyInfo& gesture_event) {
switch (gesture_event.event.GetType()) {
case WebInputEvent::kGestureFlingCancel:
if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchscreen) {
touchscreen_tap_suppression_controller_
.GestureFlingCancelStoppedFling();
} else if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchpad) {
touchpad_tap_suppression_controller_.GestureFlingCancelStoppedFling();
}
return true;
case WebInputEvent::kGestureTapDown:
case WebInputEvent::kGestureShowPress:
case WebInputEvent::kGestureTapUnconfirmed:
case WebInputEvent::kGestureTapCancel:
case WebInputEvent::kGestureTap:
case WebInputEvent::kGestureDoubleTap:
case WebInputEvent::kGestureLongPress:
case WebInputEvent::kGestureLongTap:
case WebInputEvent::kGestureTwoFingerTap:
if (gesture_event.event.SourceDevice() ==
blink::WebGestureDevice::kTouchscreen) {
return !touchscreen_tap_suppression_controller_.FilterTapEvent(
gesture_event);
}
return true;
default:
return true;
}
}
bool FlingController::FilterGestureEventForFlingBoosting(
const GestureEventWithLatencyInfo& gesture_event) {
if (!fling_booster_)
return false;
bool cancel_current_fling;
bool should_filter_event = fling_booster_->FilterGestureEventForFlingBoosting(
gesture_event.event, &cancel_current_fling);
if (cancel_current_fling) {
CancelCurrentFling();
}
if (should_filter_event) {
if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingStart) {
UpdateCurrentFlingState(gesture_event.event,
fling_booster_->current_fling_velocity());
TRACE_EVENT_INSTANT2("input",
fling_booster_->fling_boosted()
? "FlingController::FlingBoosted"
: "FlingController::FlingReplaced",
TRACE_EVENT_SCOPE_THREAD, "vx",
fling_booster_->current_fling_velocity().x(), "vy",
fling_booster_->current_fling_velocity().y());
} else if (gesture_event.event.GetType() ==
WebInputEvent::kGestureFlingCancel) {
DCHECK(fling_booster_->fling_cancellation_is_deferred());
TRACE_EVENT_INSTANT0("input", "FlingController::FlingBoostStart",
TRACE_EVENT_SCOPE_THREAD);
} else if (gesture_event.event.GetType() ==
WebInputEvent::kGestureScrollBegin ||
gesture_event.event.GetType() ==
WebInputEvent::kGestureScrollUpdate) {
TRACE_EVENT_INSTANT0("input",
"FlingController::ExtendBoostedFlingTimeout",
TRACE_EVENT_SCOPE_THREAD);
}
}
return should_filter_event;
}
bool FlingController::ObserveAndMaybeConsumeGestureEvent(
const GestureEventWithLatencyInfo& gesture_event) {
if (!ShouldForwardForGFCFiltering(gesture_event) ||
!ShouldForwardForTapSuppression(gesture_event) ||
FilterGestureEventForFlingBoosting(gesture_event))
return true;
if (gesture_event.event.GetType() == WebInputEvent::kGestureScrollUpdate) {
last_seen_scroll_update_ = gesture_event.event.TimeStamp();
} else if (gesture_event.event.GetType() ==
WebInputEvent::kGestureScrollEnd ||
gesture_event.event.GetType() ==
WebInputEvent::kGestureScrollBegin) {
// TODO(bokan): We reset this on Begin as well as End since there appear to
// be cases where we see an invalid event sequence:
// https://crbug.com/928569.
last_seen_scroll_update_ = base::TimeTicks();
}
// fling_controller_ is in charge of handling GFS events and the events are
// not sent to the renderer, the controller processes the fling and generates
// fling progress events (wheel events for touchpad and GSU events for
// touchscreen and autoscroll) which are handled normally.
if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingStart) {
ProcessGestureFlingStart(gesture_event);
return true;
}
// If the GestureFlingStart event is processed by the fling_controller_, the
// GestureFlingCancel event should be the same.
if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingCancel) {
ProcessGestureFlingCancel(gesture_event);
return true;
}
return false;
}
void FlingController::ProcessGestureFlingStart(
const GestureEventWithLatencyInfo& gesture_event) {
const float vx = gesture_event.event.data.fling_start.velocity_x;
const float vy = gesture_event.event.data.fling_start.velocity_y;
if (!UpdateCurrentFlingState(gesture_event.event, gfx::Vector2dF(vx, vy)))
return;
TRACE_EVENT_ASYNC_BEGIN2("input", kFlingTraceName, this, "vx", vx, "vy", vy);
has_fling_animation_started_ = false;
last_progress_time_ = base::TimeTicks();
fling_in_progress_ = true;
fling_booster_ = std::make_unique<ui::FlingBooster>(
current_fling_parameters_.velocity,
current_fling_parameters_.source_device,
current_fling_parameters_.modifiers);
// Wait for BeginFrame to call ProgressFling when
// SetNeedsBeginFrameForFlingProgress is used to progress flings instead of
// compositor animation observer (happens on Android WebView).
if (scheduler_client_->NeedsBeginFrameForFlingProgress())
ScheduleFlingProgress();
else
ProgressFling(clock_->NowTicks());
}
void FlingController::ScheduleFlingProgress() {
scheduler_client_->ScheduleFlingProgress(weak_ptr_factory_.GetWeakPtr());
}
void FlingController::ProcessGestureFlingCancel(
const GestureEventWithLatencyInfo& gesture_event) {
fling_in_progress_ = false;
if (fling_curve_)
CancelCurrentFling();
}
void FlingController::ProgressFling(base::TimeTicks current_time) {
if (!fling_curve_)
return;
TRACE_EVENT_ASYNC_STEP_INTO0("input", kFlingTraceName, this, "ProgressFling");
DCHECK(fling_booster_);
fling_booster_->set_last_fling_animation_time(
(current_time - base::TimeTicks()).InSecondsF());
if (fling_booster_->MustCancelDeferredFling()) {
CancelCurrentFling();
return;
}
if (!has_fling_animation_started_) {
// Guard against invalid as there are no guarantees fling event and progress
// timestamps are compatible.
if (current_fling_parameters_.start_time.is_null()) {
current_fling_parameters_.start_time = current_time;
ScheduleFlingProgress();
return;
}
// If the first time that progressFling is called is more than two frames
// later than the fling start time, delay the fling start time to one frame
// prior to the current time. This makes sure that at least one progress
// event is sent while the fling is active even when the fling duration is
// short (small velocity) and the time delta between its timestamp and its
// processing time is big (e.g. When a GFS gets bubbled from an oopif).
if (current_time >= current_fling_parameters_.start_time +
kMaxMicrosecondsFromFlingTimestampToFirstProgress) {
current_fling_parameters_.start_time = current_time - kFrameDelta;
}
}
// ProgressFling is called inside FlingScheduler::OnAnimationStep. Sometimes
// the first OnAnimationStep call has the time of the last frame before
// AddAnimationObserver call rather than time of the first frame after
// AddAnimationObserver call. Do not advance the fling when current_time is
// less than last fling progress time or less than the GFS event timestamp.
if (current_time < last_progress_time_ ||
current_time <= current_fling_parameters_.start_time) {
ScheduleFlingProgress();
return;
}
gfx::Vector2dF delta_to_scroll;
bool fling_is_active = fling_curve_->Advance(
(current_time - current_fling_parameters_.start_time).InSecondsF(),
current_fling_parameters_.velocity, delta_to_scroll);
if (fling_is_active) {
if (std::abs(delta_to_scroll.x()) > kMinInertialScrollDelta ||
std::abs(delta_to_scroll.y()) > kMinInertialScrollDelta) {
GenerateAndSendFlingProgressEvents(delta_to_scroll);
has_fling_animation_started_ = true;
last_progress_time_ = current_time;
}
// As long as the fling curve is active, the fling progress must get
// scheduled even when the last delta to scroll was zero.
ScheduleFlingProgress();
return;
}
if (current_fling_parameters_.source_device !=
blink::WebGestureDevice::kSyntheticAutoscroll) {
CancelCurrentFling();
}
}
void FlingController::StopFling() {
if (!fling_curve_)
return;
CancelCurrentFling();
}
void FlingController::GenerateAndSendWheelEvents(
const gfx::Vector2dF& delta,
blink::WebMouseWheelEvent::Phase phase) {
MouseWheelEventWithLatencyInfo synthetic_wheel(
WebInputEvent::kMouseWheel, current_fling_parameters_.modifiers,
clock_->NowTicks(), ui::LatencyInfo(ui::SourceEventType::WHEEL));
synthetic_wheel.event.delta_x = delta.x();
synthetic_wheel.event.delta_y = delta.y();
synthetic_wheel.event.has_precise_scrolling_deltas = true;
synthetic_wheel.event.momentum_phase = phase;
synthetic_wheel.event.has_synthetic_phase = true;
synthetic_wheel.event.SetPositionInWidget(current_fling_parameters_.point);
synthetic_wheel.event.SetPositionInScreen(
current_fling_parameters_.global_point);
// Send wheel events nonblocking.
synthetic_wheel.event.dispatch_type = WebInputEvent::kEventNonBlocking;
event_sender_client_->SendGeneratedWheelEvent(synthetic_wheel);
}
void FlingController::GenerateAndSendGestureScrollEvents(
WebInputEvent::Type type,
const gfx::Vector2dF& delta /* = gfx::Vector2dF() */) {
GestureEventWithLatencyInfo synthetic_gesture(
type, current_fling_parameters_.modifiers, clock_->NowTicks(),
ui::LatencyInfo(ui::SourceEventType::INERTIAL));
synthetic_gesture.event.SetPositionInWidget(current_fling_parameters_.point);
synthetic_gesture.event.SetPositionInScreen(
current_fling_parameters_.global_point);
synthetic_gesture.event.primary_pointer_type =
blink::WebPointerProperties::PointerType::kTouch;
synthetic_gesture.event.SetSourceDevice(
current_fling_parameters_.source_device);
if (type == WebInputEvent::kGestureScrollUpdate) {
synthetic_gesture.event.data.scroll_update.delta_x = delta.x();
synthetic_gesture.event.data.scroll_update.delta_y = delta.y();
synthetic_gesture.event.data.scroll_update.inertial_phase =
WebGestureEvent::kMomentumPhase;
} else {
DCHECK_EQ(WebInputEvent::kGestureScrollEnd, type);
synthetic_gesture.event.data.scroll_end.inertial_phase =
WebGestureEvent::kMomentumPhase;
synthetic_gesture.event.data.scroll_end.generated_by_fling_controller =
true;
}
event_sender_client_->SendGeneratedGestureScrollEvents(synthetic_gesture);
}
void FlingController::GenerateAndSendFlingProgressEvents(
const gfx::Vector2dF& delta) {
switch (current_fling_parameters_.source_device) {
case blink::WebGestureDevice::kTouchpad: {
blink::WebMouseWheelEvent::Phase phase =
has_fling_animation_started_
? blink::WebMouseWheelEvent::kPhaseChanged
: blink::WebMouseWheelEvent::kPhaseBegan;
GenerateAndSendWheelEvents(delta, phase);
break;
}
case blink::WebGestureDevice::kTouchscreen:
case blink::WebGestureDevice::kSyntheticAutoscroll:
GenerateAndSendGestureScrollEvents(WebInputEvent::kGestureScrollUpdate,
delta);
break;
case blink::WebGestureDevice::kUninitialized:
case blink::WebGestureDevice::kScrollbar:
NOTREACHED()
<< "Fling controller doesn't handle flings with source device:"
<< static_cast<int>(current_fling_parameters_.source_device);
}
}
void FlingController::GenerateAndSendFlingEndEvents() {
switch (current_fling_parameters_.source_device) {
case blink::WebGestureDevice::kTouchpad:
GenerateAndSendWheelEvents(gfx::Vector2d(),
blink::WebMouseWheelEvent::kPhaseEnded);
break;
case blink::WebGestureDevice::kTouchscreen:
case blink::WebGestureDevice::kSyntheticAutoscroll:
GenerateAndSendGestureScrollEvents(WebInputEvent::kGestureScrollEnd);
break;
case blink::WebGestureDevice::kUninitialized:
case blink::WebGestureDevice::kScrollbar:
NOTREACHED()
<< "Fling controller doesn't handle flings with source device:"
<< static_cast<int>(current_fling_parameters_.source_device);
}
}
void FlingController::CancelCurrentFling() {
bool had_active_fling = !!fling_curve_;
fling_curve_.reset();
has_fling_animation_started_ = false;
last_progress_time_ = base::TimeTicks();
fling_in_progress_ = false;
// Extract the last event filtered by the fling booster if it exists.
bool fling_cancellation_is_deferred =
fling_booster_ && fling_booster_->fling_cancellation_is_deferred();
WebGestureEvent last_fling_boost_event;
if (fling_cancellation_is_deferred)
last_fling_boost_event = fling_booster_->last_boost_event();
// Reset the state of the fling.
fling_booster_.reset();
GenerateAndSendFlingEndEvents();
current_fling_parameters_ = ActiveFlingParameters();
// Synthesize a GestureScrollBegin, as the original event was suppressed. It
// is important to send the GSB after resetting the fling_booster_ otherwise
// it will get filtered by the booster again. This is necessary for
// touchscreen fling cancelation only, since autoscroll fling cancelation
// doesn't get deferred and when the touchpad fling cancelation gets deferred,
// the first wheel event after the cancelation will cause a GSB generation.
if (fling_cancellation_is_deferred &&
last_fling_boost_event.SourceDevice() ==
blink::WebGestureDevice::kTouchscreen &&
(last_fling_boost_event.GetType() == WebInputEvent::kGestureScrollBegin ||
last_fling_boost_event.GetType() ==
WebInputEvent::kGestureScrollUpdate)) {
WebGestureEvent scroll_begin_event = last_fling_boost_event;
scroll_begin_event.SetType(WebInputEvent::kGestureScrollBegin);
bool is_update =
last_fling_boost_event.GetType() == WebInputEvent::kGestureScrollUpdate;
float delta_x_hint =
is_update ? last_fling_boost_event.data.scroll_update.delta_x
: last_fling_boost_event.data.scroll_begin.delta_x_hint;
float delta_y_hint =
is_update ? last_fling_boost_event.data.scroll_update.delta_y
: last_fling_boost_event.data.scroll_begin.delta_y_hint;
scroll_begin_event.data.scroll_begin.delta_x_hint = delta_x_hint;
scroll_begin_event.data.scroll_begin.delta_y_hint = delta_y_hint;
event_sender_client_->SendGeneratedGestureScrollEvents(
GestureEventWithLatencyInfo(
scroll_begin_event,
ui::LatencyInfo(ui::SourceEventType::INERTIAL)));
}
if (had_active_fling) {
scheduler_client_->DidStopFlingingOnBrowser(weak_ptr_factory_.GetWeakPtr());
TRACE_EVENT_ASYNC_END0("input", kFlingTraceName, this);
}
}
bool FlingController::UpdateCurrentFlingState(
const WebGestureEvent& fling_start_event,
const gfx::Vector2dF& velocity) {
DCHECK_EQ(WebInputEvent::kGestureFlingStart, fling_start_event.GetType());
current_fling_parameters_.velocity = velocity;
current_fling_parameters_.point = fling_start_event.PositionInWidget();
current_fling_parameters_.global_point = fling_start_event.PositionInScreen();
current_fling_parameters_.modifiers = fling_start_event.GetModifiers();
current_fling_parameters_.source_device = fling_start_event.SourceDevice();
if (fling_start_event.SourceDevice() ==
blink::WebGestureDevice::kSyntheticAutoscroll ||
last_seen_scroll_update_.is_null()) {
current_fling_parameters_.start_time = fling_start_event.TimeStamp();
} else {
// To maintain a smooth, continuous transition from a drag scroll to a fling
// scroll, the animation should begin at the time of the last update.
current_fling_parameters_.start_time = last_seen_scroll_update_;
}
if (velocity.IsZero() && fling_start_event.SourceDevice() !=
blink::WebGestureDevice::kSyntheticAutoscroll) {
CancelCurrentFling();
return false;
}
fling_curve_ = std::unique_ptr<blink::WebGestureCurve>(
ui::WebGestureCurveImpl::CreateFromDefaultPlatformCurve(
current_fling_parameters_.source_device,
current_fling_parameters_.velocity,
gfx::Vector2dF() /*initial_offset*/, false /*on_main_thread*/,
GetContentClient()->browser()->ShouldUseMobileFlingCurve()));
return true;
}
bool FlingController::FlingCancellationIsDeferred() const {
return fling_booster_ && fling_booster_->fling_cancellation_is_deferred();
}
gfx::Vector2dF FlingController::CurrentFlingVelocity() const {
return current_fling_parameters_.velocity;
}
TouchpadTapSuppressionController*
FlingController::GetTouchpadTapSuppressionController() {
return &touchpad_tap_suppression_controller_;
}
} // namespace content