| // 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/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 { |
| // 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::FromMicroseconds(33333); |
| |
| const char* kFlingTraceName = "FlingController::HandlingGestureFling"; |
| } // namespace |
| |
| namespace content { |
| |
| FlingController::Config::Config() {} |
| |
| FlingController::FlingController( |
| GestureEventQueue* gesture_event_queue, |
| FlingControllerEventSenderClient* event_sender_client, |
| FlingControllerSchedulerClient* scheduler_client, |
| const Config& config) |
| : gesture_event_queue_(gesture_event_queue), |
| 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), |
| weak_ptr_factory_(this) { |
| DCHECK(gesture_event_queue); |
| 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::kWebGestureDeviceTouchscreen) { |
| touchscreen_tap_suppression_controller_ |
| .GestureFlingCancelStoppedFling(); |
| } else if (gesture_event.event.SourceDevice() == |
| blink::kWebGestureDeviceTouchpad) { |
| 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::kWebGestureDeviceTouchscreen) { |
| 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::FilterGestureEvent( |
| const GestureEventWithLatencyInfo& gesture_event) { |
| return !ShouldForwardForGFCFiltering(gesture_event) || |
| !ShouldForwardForTapSuppression(gesture_event) || |
| FilterGestureEventForFlingBoosting(gesture_event); |
| } |
| |
| void FlingController::OnGestureEventAck( |
| const GestureEventWithLatencyInfo& acked_event, |
| InputEventAckState ack_result) { |
| bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result); |
| switch (acked_event.event.GetType()) { |
| case WebInputEvent::kGestureScrollUpdate: |
| if (acked_event.event.data.scroll_update.inertial_phase == |
| WebGestureEvent::kMomentumPhase && |
| fling_curve_ && !processed && |
| current_fling_parameters_.source_device != |
| blink::kWebGestureDeviceSyntheticAutoscroll) { |
| CancelCurrentFling(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| 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; |
| fling_in_progress_ = true; |
| fling_booster_ = std::make_unique<ui::FlingBooster>( |
| current_fling_parameters_.velocity, |
| current_fling_parameters_.source_device, |
| current_fling_parameters_.modifiers); |
| ScheduleFlingProgress(); |
| } |
| |
| 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, future or sufficiently stale start times, as there |
| // are no guarantees fling event and progress timestamps are compatible. |
| if (current_fling_parameters_.start_time.is_null() || |
| current_time <= current_fling_parameters_.start_time || |
| current_time >= current_fling_parameters_.start_time + |
| kMaxMicrosecondsFromFlingTimestampToFirstProgress) { |
| current_fling_parameters_.start_time = current_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 (delta_to_scroll != gfx::Vector2d()) { |
| GenerateAndSendFlingProgressEvents(delta_to_scroll); |
| has_fling_animation_started_ = true; |
| } |
| // 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::kWebGestureDeviceSyntheticAutoscroll) { |
| 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, |
| base::TimeTicks::Now(), 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, base::TimeTicks::Now(), |
| 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; |
| } |
| event_sender_client_->SendGeneratedGestureScrollEvents(synthetic_gesture); |
| } |
| |
| void FlingController::GenerateAndSendFlingProgressEvents( |
| const gfx::Vector2dF& delta) { |
| switch (current_fling_parameters_.source_device) { |
| case blink::kWebGestureDeviceTouchpad: { |
| blink::WebMouseWheelEvent::Phase phase = |
| has_fling_animation_started_ |
| ? blink::WebMouseWheelEvent::kPhaseChanged |
| : blink::WebMouseWheelEvent::kPhaseBegan; |
| GenerateAndSendWheelEvents(delta, phase); |
| break; |
| } |
| case blink::kWebGestureDeviceTouchscreen: |
| case blink::kWebGestureDeviceSyntheticAutoscroll: |
| GenerateAndSendGestureScrollEvents(WebInputEvent::kGestureScrollUpdate, |
| delta); |
| break; |
| default: |
| NOTREACHED() |
| << "Fling controller doesn't handle flings with source device:" |
| << current_fling_parameters_.source_device; |
| } |
| } |
| |
| void FlingController::GenerateAndSendFlingEndEvents() { |
| switch (current_fling_parameters_.source_device) { |
| case blink::kWebGestureDeviceTouchpad: |
| GenerateAndSendWheelEvents(gfx::Vector2d(), |
| blink::WebMouseWheelEvent::kPhaseEnded); |
| break; |
| case blink::kWebGestureDeviceTouchscreen: |
| case blink::kWebGestureDeviceSyntheticAutoscroll: |
| GenerateAndSendGestureScrollEvents(WebInputEvent::kGestureScrollEnd); |
| break; |
| default: |
| NOTREACHED() |
| << "Fling controller doesn't handle flings with source device:" |
| << current_fling_parameters_.source_device; |
| } |
| } |
| |
| void FlingController::CancelCurrentFling() { |
| bool had_active_fling = !!fling_curve_; |
| fling_curve_.reset(); |
| has_fling_animation_started_ = false; |
| fling_in_progress_ = false; |
| gesture_event_queue_->FlingHasBeenHalted(); |
| |
| // 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::kWebGestureDeviceTouchscreen && |
| (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(); |
| current_fling_parameters_.start_time = fling_start_event.TimeStamp(); |
| |
| if (velocity.IsZero() && fling_start_event.SourceDevice() != |
| blink::kWebGestureDeviceSyntheticAutoscroll) { |
| 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(); |
| } |
| |
| bool FlingController::TouchscreenFlingInProgress() const { |
| return fling_in_progress_ && current_fling_parameters_.source_device == |
| blink::kWebGestureDeviceTouchscreen; |
| } |
| |
| gfx::Vector2dF FlingController::CurrentFlingVelocity() const { |
| return current_fling_parameters_.velocity; |
| } |
| |
| TouchpadTapSuppressionController* |
| FlingController::GetTouchpadTapSuppressionController() { |
| return &touchpad_tap_suppression_controller_; |
| } |
| |
| } // namespace content |