blob: 15b4f7dce2e814fb79cccfded39a0972564484a9 [file] [log] [blame]
// Copyright 2013 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 "third_party/blink/public/platform/input/input_handler_proxy.h"
#include <stddef.h>
#include <algorithm>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/profiler/sample_metadata.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "cc/base/features.h"
#include "cc/input/main_thread_scrolling_reason.h"
#include "cc/metrics/event_metrics.h"
#include "services/tracing/public/cpp/perfetto/flow_event_utils.h"
#include "services/tracing/public/cpp/perfetto/macros.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "third_party/blink/public/common/input/web_input_event_attribution.h"
#include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
#include "third_party/blink/public/common/input/web_pointer_event.h"
#include "third_party/blink/public/common/input/web_touch_event.h"
#include "third_party/blink/public/platform/input/input_handler_proxy_client.h"
#include "third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.h"
#include "third_party/blink/renderer/platform/widget/input/cursor_control_handler.h"
#include "third_party/blink/renderer/platform/widget/input/elastic_overscroll_controller.h"
#include "third_party/blink/renderer/platform/widget/input/event_with_callback.h"
#include "third_party/blink/renderer/platform/widget/input/input_metrics.h"
#include "third_party/blink/renderer/platform/widget/input/momentum_scroll_jank_tracker.h"
#include "third_party/blink/renderer/platform/widget/input/scroll_predictor.h"
#include "ui/events/types/scroll_input_type.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/latency/latency_info.h"
using perfetto::protos::pbzero::ChromeLatencyInfo;
using perfetto::protos::pbzero::TrackEvent;
using ScrollThread = cc::InputHandler::ScrollThread;
namespace blink {
namespace {
cc::ScrollState CreateScrollStateForGesture(const WebGestureEvent& event) {
cc::ScrollStateData scroll_state_data;
switch (event.GetType()) {
case WebInputEvent::Type::kGestureScrollBegin:
scroll_state_data.position_x = event.PositionInWidget().x();
scroll_state_data.position_y = event.PositionInWidget().y();
scroll_state_data.delta_x_hint = -event.data.scroll_begin.delta_x_hint;
scroll_state_data.delta_y_hint = -event.data.scroll_begin.delta_y_hint;
scroll_state_data.is_beginning = true;
// On Mac, a GestureScrollBegin in the inertial phase indicates a fling
// start.
scroll_state_data.is_in_inertial_phase =
(event.data.scroll_begin.inertial_phase ==
WebGestureEvent::InertialPhaseState::kMomentum);
scroll_state_data.delta_granularity =
event.data.scroll_begin.delta_hint_units;
if (cc::ElementId::IsValid(
event.data.scroll_begin.scrollable_area_element_id)) {
cc::ElementId target_scroller(
event.data.scroll_begin.scrollable_area_element_id);
scroll_state_data.set_current_native_scrolling_element(target_scroller);
// If the target scroller comes from a main thread hit test, we're in
// scroll unification.
scroll_state_data.is_main_thread_hit_tested =
event.data.scroll_begin.main_thread_hit_tested;
DCHECK(!event.data.scroll_begin.main_thread_hit_tested ||
base::FeatureList::IsEnabled(::features::kScrollUnification));
} else {
// If a main thread hit test didn't yield a target we should have
// discarded this event before this point.
DCHECK(!event.data.scroll_begin.main_thread_hit_tested);
}
break;
case WebInputEvent::Type::kGestureScrollUpdate:
scroll_state_data.delta_x = -event.data.scroll_update.delta_x;
scroll_state_data.delta_y = -event.data.scroll_update.delta_y;
scroll_state_data.velocity_x = event.data.scroll_update.velocity_x;
scroll_state_data.velocity_y = event.data.scroll_update.velocity_y;
scroll_state_data.is_in_inertial_phase =
event.data.scroll_update.inertial_phase ==
WebGestureEvent::InertialPhaseState::kMomentum;
scroll_state_data.delta_granularity =
event.data.scroll_update.delta_units;
break;
case WebInputEvent::Type::kGestureScrollEnd:
scroll_state_data.is_ending = true;
break;
default:
NOTREACHED();
break;
}
scroll_state_data.is_direct_manipulation =
event.SourceDevice() == WebGestureDevice::kTouchscreen;
return cc::ScrollState(scroll_state_data);
}
cc::ScrollState CreateScrollStateForInertialUpdate(
const gfx::Vector2dF& delta) {
cc::ScrollStateData scroll_state_data;
scroll_state_data.delta_x = delta.x();
scroll_state_data.delta_y = delta.y();
scroll_state_data.is_in_inertial_phase = true;
return cc::ScrollState(scroll_state_data);
}
ui::ScrollInputType GestureScrollInputType(WebGestureDevice device) {
switch (device) {
case WebGestureDevice::kTouchpad:
return ui::ScrollInputType::kWheel;
case WebGestureDevice::kTouchscreen:
return ui::ScrollInputType::kTouchscreen;
case WebGestureDevice::kSyntheticAutoscroll:
return ui::ScrollInputType::kAutoscroll;
case WebGestureDevice::kScrollbar:
return ui::ScrollInputType::kScrollbar;
case WebGestureDevice::kUninitialized:
NOTREACHED();
return ui::ScrollInputType::kMaxValue;
}
}
cc::SnapFlingController::GestureScrollType GestureScrollEventType(
WebInputEvent::Type web_event_type) {
switch (web_event_type) {
case WebInputEvent::Type::kGestureScrollBegin:
return cc::SnapFlingController::GestureScrollType::kBegin;
case WebInputEvent::Type::kGestureScrollUpdate:
return cc::SnapFlingController::GestureScrollType::kUpdate;
case WebInputEvent::Type::kGestureScrollEnd:
return cc::SnapFlingController::GestureScrollType::kEnd;
default:
NOTREACHED();
return cc::SnapFlingController::GestureScrollType::kBegin;
}
}
cc::SnapFlingController::GestureScrollUpdateInfo GetGestureScrollUpdateInfo(
const WebGestureEvent& event) {
cc::SnapFlingController::GestureScrollUpdateInfo info;
info.delta = gfx::Vector2dF(-event.data.scroll_update.delta_x,
-event.data.scroll_update.delta_y);
info.is_in_inertial_phase = event.data.scroll_update.inertial_phase ==
WebGestureEvent::InertialPhaseState::kMomentum;
info.event_time = event.TimeStamp();
return info;
}
cc::ScrollBeginThreadState RecordScrollingThread(
bool scrolling_on_compositor_thread,
bool blocked_on_main_thread_event_handler,
WebGestureDevice device) {
const char* kWheelHistogramName = "Renderer4.ScrollingThread.Wheel";
const char* kTouchHistogramName = "Renderer4.ScrollingThread.Touch";
auto status = cc::ScrollBeginThreadState::kScrollingOnMain;
if (scrolling_on_compositor_thread) {
status =
blocked_on_main_thread_event_handler
? cc::ScrollBeginThreadState::kScrollingOnCompositorBlockedOnMain
: cc::ScrollBeginThreadState::kScrollingOnCompositor;
}
if (device == WebGestureDevice::kTouchscreen) {
UMA_HISTOGRAM_ENUMERATION(kTouchHistogramName, status);
} else if (device == WebGestureDevice::kTouchpad) {
UMA_HISTOGRAM_ENUMERATION(kWheelHistogramName, status);
} else if (device == WebGestureDevice::kScrollbar) {
// TODO(crbug.com/1101502): Add support for
// Renderer4.ScrollingThread.Scrollbar
} else {
NOTREACHED();
}
return status;
}
bool IsGestureScrollOrPinch(WebInputEvent::Type type) {
switch (type) {
case WebGestureEvent::Type::kGestureScrollBegin:
case WebGestureEvent::Type::kGestureScrollUpdate:
case WebGestureEvent::Type::kGestureScrollEnd:
case WebGestureEvent::Type::kGesturePinchBegin:
case WebGestureEvent::Type::kGesturePinchUpdate:
case WebGestureEvent::Type::kGesturePinchEnd:
return true;
default:
return false;
}
}
} // namespace
InputHandlerProxy::InputHandlerProxy(cc::InputHandler& input_handler,
InputHandlerProxyClient* client)
: client_(client),
input_handler_(&input_handler),
synchronous_input_handler_(nullptr),
handling_gesture_on_impl_thread_(false),
scroll_sequence_ignored_(false),
current_overscroll_params_(nullptr),
has_seen_first_gesture_scroll_update_after_begin_(false),
last_injected_gesture_was_begin_(false),
tick_clock_(base::DefaultTickClock::GetInstance()),
snap_fling_controller_(std::make_unique<cc::SnapFlingController>(this)),
cursor_control_handler_(std::make_unique<CursorControlHandler>()) {
DCHECK(client);
input_handler_->BindToClient(this);
cc::ScrollElasticityHelper* scroll_elasticity_helper =
input_handler_->CreateScrollElasticityHelper();
if (scroll_elasticity_helper) {
elastic_overscroll_controller_ =
ElasticOverscrollController::Create(scroll_elasticity_helper);
}
compositor_event_queue_ = std::make_unique<CompositorThreadEventQueue>();
scroll_predictor_ =
base::FeatureList::IsEnabled(blink::features::kResamplingScrollEvents)
? std::make_unique<ScrollPredictor>()
: nullptr;
if (base::FeatureList::IsEnabled(blink::features::kSkipTouchEventFilter) &&
GetFieldTrialParamValueByFeature(
blink::features::kSkipTouchEventFilter,
blink::features::kSkipTouchEventFilterFilteringProcessParamName) ==
blink::features::
kSkipTouchEventFilterFilteringProcessParamValueBrowserAndRenderer) {
// Skipping filtering for touch events on renderer process is enabled.
// Always skip filtering discrete events.
skip_touch_filter_discrete_ = true;
if (GetFieldTrialParamValueByFeature(
blink::features::kSkipTouchEventFilter,
blink::features::kSkipTouchEventFilterTypeParamName) ==
blink::features::kSkipTouchEventFilterTypeParamValueAll) {
// The experiment config also specifies to skip touchmove events.
skip_touch_filter_all_ = true;
}
}
}
InputHandlerProxy::~InputHandlerProxy() {}
void InputHandlerProxy::WillShutdown() {
elastic_overscroll_controller_.reset();
input_handler_ = nullptr;
client_->WillShutdown();
}
void InputHandlerProxy::HandleInputEventWithLatencyInfo(
std::unique_ptr<blink::WebCoalescedInputEvent> event,
std::unique_ptr<cc::EventMetrics> metrics,
EventDispositionCallback callback) {
DCHECK(input_handler_);
input_handler_->NotifyInputEvent();
int64_t trace_id = event->latency_info().trace_id();
TRACE_EVENT("input,benchmark", "LatencyInfo.Flow",
[trace_id](perfetto::EventContext ctx) {
ChromeLatencyInfo* info =
ctx.event()->set_chrome_latency_info();
info->set_trace_id(trace_id);
info->set_step(ChromeLatencyInfo::STEP_HANDLE_INPUT_EVENT_IMPL);
tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT,
trace_id);
});
auto event_with_callback = std::make_unique<EventWithCallback>(
std::move(event), tick_clock_->NowTicks(), std::move(callback),
std::move(metrics));
enum {
NO_SCROLL_PINCH = 0,
ONGOING_SCROLL_PINCH = 1,
SCROLL_PINCH = 2,
};
// Note: Other input can race ahead of gesture input as they don't have to go
// through the queue, but we believe it's OK to do so.
if (!IsGestureScrollOrPinch(event_with_callback->event().GetType())) {
base::ScopedSampleMetadata metadata("Input.GestureScrollOrPinch",
NO_SCROLL_PINCH);
DispatchSingleInputEvent(std::move(event_with_callback),
tick_clock_->NowTicks());
return;
}
base::ScopedSampleMetadata metadata(
"Input.GestureScrollOrPinch", currently_active_gesture_device_.has_value()
? ONGOING_SCROLL_PINCH
: SCROLL_PINCH);
const auto& gesture_event =
static_cast<const WebGestureEvent&>(event_with_callback->event());
const bool is_first_gesture_scroll_update =
!has_seen_first_gesture_scroll_update_after_begin_ &&
gesture_event.GetType() == WebGestureEvent::Type::kGestureScrollUpdate;
if (gesture_event.GetType() == WebGestureEvent::Type::kGestureScrollBegin) {
has_seen_first_gesture_scroll_update_after_begin_ = false;
} else if (gesture_event.GetType() ==
WebGestureEvent::Type::kGestureScrollUpdate) {
has_seen_first_gesture_scroll_update_after_begin_ = true;
}
if (currently_active_gesture_device_.has_value()) {
// Scroll updates should typically be queued and wait until a
// BeginImplFrame to dispatch. However, the first scroll update to be
// generated from a *blocking* touch sequence will have waited for the
// touch event to be ACK'ed by the renderer as unconsumed. Queueing here
// again until BeginImplFrame means we'll likely add a whole frame of
// latency to so we flush the queue immediately. This happens only for the
// first scroll update because once a scroll starts touch events are
// dispatched non-blocking so scroll updates don't wait for a touch ACK.
// The |is_source_touch_event_set_blocking| bit is set based on the
// renderer's reply that a blocking touch stream should be made
// non-blocking. Note: unlike wheel events below, the first GSU in a touch
// may have come from a non-blocking touch sequence, e.g. if the earlier
// touchstart determined we're in a |touch-action: pan-y| region. Because
// of this, we can't simply look at the first GSU like wheels do.
bool is_from_blocking_touch =
gesture_event.SourceDevice() == WebGestureDevice::kTouchscreen &&
gesture_event.is_source_touch_event_set_blocking;
// TODO(bokan): This was added in https://crrev.com/c/557463 before async
// wheel events. It's not clear to me why flushing on a scroll end would
// help or why this is specific to wheel events but I suspect it's no
// longer needed now that wheel scrolling uses non-blocking events.
bool is_scroll_end_from_wheel =
gesture_event.SourceDevice() == WebGestureDevice::kTouchpad &&
gesture_event.GetType() == WebGestureEvent::Type::kGestureScrollEnd;
// Wheel events have the same issue as the blocking touch issue above.
// However, all wheel events are initially sent blocking and become non-
// blocking on the first unconsumed event. We can therefore simply look for
// the first scroll update in a wheel gesture.
bool is_first_wheel_scroll_update =
gesture_event.SourceDevice() == WebGestureDevice::kTouchpad &&
is_first_gesture_scroll_update;
// |synchronous_input_handler_| is WebView only. WebView has different
// mechanisms and we want to forward all events immediately.
if (is_from_blocking_touch || is_scroll_end_from_wheel ||
is_first_wheel_scroll_update || synchronous_input_handler_) {
compositor_event_queue_->Queue(std::move(event_with_callback),
tick_clock_->NowTicks());
DispatchQueuedInputEvents();
return;
}
bool needs_animate_input = compositor_event_queue_->empty();
compositor_event_queue_->Queue(std::move(event_with_callback),
tick_clock_->NowTicks());
if (needs_animate_input)
input_handler_->SetNeedsAnimateInput();
return;
}
// We have to dispatch the event to know whether the gesture sequence will be
// handled by the compositor or not.
DispatchSingleInputEvent(std::move(event_with_callback),
tick_clock_->NowTicks());
}
void InputHandlerProxy::ContinueScrollBeginAfterMainThreadHitTest(
std::unique_ptr<blink::WebCoalescedInputEvent> event,
std::unique_ptr<cc::EventMetrics> metrics,
EventDispositionCallback callback,
cc::ElementIdType hit_test_result) {
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
DCHECK_EQ(event->Event().GetType(),
WebGestureEvent::Type::kGestureScrollBegin);
DCHECK(hit_testing_scroll_begin_on_main_thread_);
DCHECK(currently_active_gesture_device_);
DCHECK(input_handler_);
hit_testing_scroll_begin_on_main_thread_ = false;
// HandleGestureScrollBegin has logic to end an existing scroll when an
// unexpected scroll begin arrives. We currently think we're in a scroll
// because of the first ScrollBegin so clear this so we don't spurriously
// call ScrollEnd. It will be set again in HandleGestureScrollBegin.
currently_active_gesture_device_ = base::nullopt;
auto* gesture_event =
static_cast<blink::WebGestureEvent*>(event->EventPointer());
if (cc::ElementId::IsValid(hit_test_result)) {
gesture_event->data.scroll_begin.scrollable_area_element_id =
hit_test_result;
gesture_event->data.scroll_begin.main_thread_hit_tested = true;
if (metrics) {
// The event is going to be re-processed on the compositor thread; so,
// reset timstamps of following dispatch stages.
metrics->ResetToDispatchStage(
cc::EventMetrics::DispatchStage::kArrivedInRendererCompositor);
}
auto event_with_callback = std::make_unique<EventWithCallback>(
std::move(event), tick_clock_->NowTicks(), std::move(callback),
std::move(metrics));
DispatchSingleInputEvent(std::move(event_with_callback),
tick_clock_->NowTicks());
} else {
// TODO(bokan): This looks odd but is actually what happens in the
// non-unified path. If a scroll is DROP_EVENT'ed, we still call
// RecordMainThreadScrollingReasons and then LTHI::RecordScrollEnd when we
// DROP the ScrollEnd. We call this to ensure symmetry between
// RecordScrollBegin and RecordScrollEnd but we should probably be avoiding
// this if the scroll never starts. https://crbug.com/1082601.
RecordMainThreadScrollingReasons(gesture_event->SourceDevice(), 0);
// If the main thread failed to return a scroller for whatever reason,
// consider the ScrollBegin to be dropped.
scroll_sequence_ignored_ = true;
WebInputEventAttribution attribution =
PerformEventAttribution(event->Event());
std::move(callback).Run(DROP_EVENT, std::move(event),
/*overscroll_params=*/nullptr, attribution,
std::move(metrics));
}
// We blocked the compositor gesture event queue while the hit test was
// pending so scroll updates may be waiting in the queue. Now that we've
// finished the hit test and performed the scroll begin, flush the queue.
DispatchQueuedInputEvents();
}
void InputHandlerProxy::DispatchSingleInputEvent(
std::unique_ptr<EventWithCallback> event_with_callback,
const base::TimeTicks now) {
ui::LatencyInfo monitored_latency_info = event_with_callback->latency_info();
std::unique_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
input_handler_->CreateLatencyInfoSwapPromiseMonitor(
&monitored_latency_info);
current_overscroll_params_.reset();
WebInputEventAttribution attribution =
PerformEventAttribution(event_with_callback->event());
InputHandlerProxy::EventDisposition disposition =
RouteToTypeSpecificHandler(event_with_callback.get(), attribution);
const WebInputEvent& event = event_with_callback->event();
const WebGestureEvent::Type type = event.GetType();
switch (type) {
case WebGestureEvent::Type::kGestureScrollBegin:
case WebGestureEvent::Type::kGesturePinchBegin:
if (disposition == DID_HANDLE ||
disposition == REQUIRES_MAIN_THREAD_HIT_TEST ||
(disposition == DROP_EVENT && handling_gesture_on_impl_thread_)) {
// REQUIRES_MAIN_THREAD_HIT_TEST means the scroll will be handled by
// the compositor but needs to block until a hit test is performed by
// Blink. We need to set this to indicate we're in a scroll so that
// gestures are queued rather than dispatched immediately.
// TODO(bokan): It's a bit of an open question if we need to also set
// |handling_gesture_on_impl_thread_|. Ideally these two bits would be
// merged. The queueing behavior is currently just determined by having
// an active gesture device.
//
// DROP_EVENT and handling_gesture_on_impl_thread_ means that the
// gesture was handled but the scroll was not consumed.
currently_active_gesture_device_ =
static_cast<const WebGestureEvent&>(event).SourceDevice();
}
break;
case WebGestureEvent::Type::kGestureScrollEnd:
case WebGestureEvent::Type::kGesturePinchEnd:
if (!handling_gesture_on_impl_thread_)
currently_active_gesture_device_ = base::nullopt;
break;
default:
break;
}
// Handle jank tracking during the momentum phase of a scroll gesture. The
// class filters non-momentum events internally.
switch (type) {
case WebGestureEvent::Type::kGestureScrollBegin:
momentum_scroll_jank_tracker_ =
std::make_unique<MomentumScrollJankTracker>();
break;
case WebGestureEvent::Type::kGestureScrollUpdate:
// It's possible to get a scroll update without a begin. Ignore these
// cases.
if (momentum_scroll_jank_tracker_) {
momentum_scroll_jank_tracker_->OnDispatchedInputEvent(
event_with_callback.get(), now);
}
break;
case WebGestureEvent::Type::kGestureScrollEnd:
momentum_scroll_jank_tracker_.reset();
break;
default:
break;
}
// Will run callback for every original events.
event_with_callback->RunCallbacks(disposition, monitored_latency_info,
std::move(current_overscroll_params_),
attribution);
}
void InputHandlerProxy::DispatchQueuedInputEvents() {
// Block flushing the compositor gesture event queue while there's an async
// scroll begin hit test outstanding. We'll flush the queue when the hit test
// responds.
if (hit_testing_scroll_begin_on_main_thread_) {
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
return;
}
// Calling |NowTicks()| is expensive so we only want to do it once.
base::TimeTicks now = tick_clock_->NowTicks();
while (!compositor_event_queue_->empty())
DispatchSingleInputEvent(compositor_event_queue_->Pop(), now);
}
void InputHandlerProxy::InjectScrollbarGestureScroll(
const WebInputEvent::Type type,
const gfx::PointF& position_in_widget,
const cc::InputHandlerPointerResult& pointer_result,
const ui::LatencyInfo& latency_info,
const base::TimeTicks original_timestamp,
const cc::EventMetrics* original_metrics) {
gfx::Vector2dF scroll_delta(pointer_result.scroll_offset.x(),
pointer_result.scroll_offset.y());
std::unique_ptr<WebGestureEvent> synthetic_gesture_event =
WebGestureEvent::GenerateInjectedScrollGesture(
type, original_timestamp, WebGestureDevice::kScrollbar,
position_in_widget, scroll_delta, pointer_result.scroll_units);
// This will avoid hit testing and directly scroll the scroller with the
// provided element_id.
if (type == WebInputEvent::Type::kGestureScrollBegin) {
synthetic_gesture_event->data.scroll_begin.scrollable_area_element_id =
pointer_result.target_scroller.GetStableId();
}
// Send in a LatencyInfo with SCROLLBAR type so that the end to end latency
// is calculated specifically for scrollbars.
ui::LatencyInfo scrollbar_latency_info(latency_info);
scrollbar_latency_info.set_source_event_type(ui::SourceEventType::SCROLLBAR);
// This latency_info should not have already been scheduled for rendering -
// i.e. it should be the original latency_info that was associated with the
// input event that caused this scroll injection. If it has already been
// scheduled it won't get queued to be shipped off with the CompositorFrame
// when the gesture is handled.
DCHECK(!scrollbar_latency_info.FindLatency(
ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, nullptr));
base::Optional<cc::EventMetrics::ScrollUpdateType> scroll_update_type;
if (type == WebInputEvent::Type::kGestureScrollBegin) {
last_injected_gesture_was_begin_ = true;
} else {
if (type == WebInputEvent::Type::kGestureScrollUpdate) {
// For injected GSUs, add a scroll update component to the latency info
// so that it is properly classified as a scroll. If the last injected
// gesture was a GSB, then this GSU is the first scroll update - mark
// the LatencyInfo as such.
scrollbar_latency_info.AddLatencyNumberWithTimestamp(
last_injected_gesture_was_begin_
? ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT
: ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
original_timestamp);
scroll_update_type = last_injected_gesture_was_begin_
? cc::EventMetrics::ScrollUpdateType::kStarted
: cc::EventMetrics::ScrollUpdateType::kContinued;
}
last_injected_gesture_was_begin_ = false;
}
std::unique_ptr<cc::EventMetrics> metrics =
cc::EventMetrics::CreateFromExisting(
synthetic_gesture_event->GetTypeAsUiEventType(), scroll_update_type,
synthetic_gesture_event->GetScrollInputType(),
cc::EventMetrics::DispatchStage::kArrivedInRendererCompositor,
original_metrics);
auto gesture_event_with_callback_update = std::make_unique<EventWithCallback>(
std::make_unique<WebCoalescedInputEvent>(
std::move(synthetic_gesture_event), scrollbar_latency_info),
original_timestamp, base::DoNothing(), std::move(metrics));
bool needs_animate_input = compositor_event_queue_->empty();
compositor_event_queue_->Queue(std::move(gesture_event_with_callback_update),
original_timestamp);
if (needs_animate_input)
input_handler_->SetNeedsAnimateInput();
}
bool HasScrollbarJumpKeyModifier(const WebInputEvent& event) {
#if defined(OS_MAC)
// Mac uses the "Option" key (which is mapped to the enum "kAltKey").
return event.GetModifiers() & WebInputEvent::kAltKey;
#else
return event.GetModifiers() & WebInputEvent::kShiftKey;
#endif
}
InputHandlerProxy::EventDisposition
InputHandlerProxy::RouteToTypeSpecificHandler(
EventWithCallback* event_with_callback,
const WebInputEventAttribution& original_attribution) {
DCHECK(input_handler_);
cc::EventsMetricsManager::ScopedMonitor::DoneCallback done_callback;
if (event_with_callback->metrics()) {
event_with_callback->WillStartProcessingForMetrics();
done_callback = base::BindOnce(
[](EventWithCallback* event, bool handled) {
event->DidCompleteProcessingForMetrics();
std::unique_ptr<cc::EventMetrics> result =
handled ? event->TakeMetrics() : nullptr;
return result;
},
event_with_callback);
}
auto scoped_event_monitor =
input_handler_->GetScopedEventMetricsMonitor(std::move(done_callback));
const WebInputEvent& event = event_with_callback->event();
if (event.IsGestureScroll() &&
(snap_fling_controller_->FilterEventForSnap(
GestureScrollEventType(event.GetType())))) {
return DROP_EVENT;
}
if (base::Optional<InputHandlerProxy::EventDisposition> handled =
cursor_control_handler_->ObserveInputEvent(event))
return *handled;
switch (event.GetType()) {
case WebInputEvent::Type::kMouseWheel:
return HandleMouseWheel(static_cast<const WebMouseWheelEvent&>(event));
case WebInputEvent::Type::kGestureScrollBegin:
return HandleGestureScrollBegin(
static_cast<const WebGestureEvent&>(event));
case WebInputEvent::Type::kGestureScrollUpdate:
return HandleGestureScrollUpdate(
static_cast<const WebGestureEvent&>(event), original_attribution,
event_with_callback->metrics());
case WebInputEvent::Type::kGestureScrollEnd:
return HandleGestureScrollEnd(static_cast<const WebGestureEvent&>(event));
case WebInputEvent::Type::kGesturePinchBegin: {
DCHECK(!gesture_pinch_in_progress_);
input_handler_->PinchGestureBegin();
gesture_pinch_in_progress_ = true;
return DID_HANDLE;
}
case WebInputEvent::Type::kGesturePinchEnd: {
DCHECK(gesture_pinch_in_progress_);
gesture_pinch_in_progress_ = false;
const WebGestureEvent& gesture_event =
static_cast<const WebGestureEvent&>(event);
input_handler_->PinchGestureEnd(
gfx::ToFlooredPoint(gesture_event.PositionInWidget()),
gesture_event.SourceDevice() == WebGestureDevice::kTouchpad);
return DID_HANDLE;
}
case WebInputEvent::Type::kGesturePinchUpdate: {
DCHECK(gesture_pinch_in_progress_);
const WebGestureEvent& gesture_event =
static_cast<const WebGestureEvent&>(event);
input_handler_->PinchGestureUpdate(
gesture_event.data.pinch_update.scale,
gfx::ToFlooredPoint(gesture_event.PositionInWidget()));
return DID_HANDLE;
}
case WebInputEvent::Type::kTouchStart:
return HandleTouchStart(event_with_callback);
case WebInputEvent::Type::kTouchMove:
return HandleTouchMove(event_with_callback);
case WebInputEvent::Type::kTouchEnd:
return HandleTouchEnd(event_with_callback);
case WebInputEvent::Type::kMouseDown: {
// Only for check scrollbar captured
const WebMouseEvent& mouse_event =
static_cast<const WebMouseEvent&>(event);
if (mouse_event.button == WebMouseEvent::Button::kLeft) {
CHECK(input_handler_);
// TODO(arakeri): Pass in the modifier instead of a bool once the
// refactor (crbug.com/1022097) is done. For details, see
// crbug.com/1016955.
HandlePointerDown(event_with_callback, mouse_event.PositionInWidget());
}
return DID_NOT_HANDLE;
}
case WebInputEvent::Type::kMouseUp: {
// Only for release scrollbar captured
const WebMouseEvent& mouse_event =
static_cast<const WebMouseEvent&>(event);
CHECK(input_handler_);
if (mouse_event.button == WebMouseEvent::Button::kLeft)
HandlePointerUp(event_with_callback, mouse_event.PositionInWidget());
return DID_NOT_HANDLE;
}
case WebInputEvent::Type::kMouseMove: {
const WebMouseEvent& mouse_event =
static_cast<const WebMouseEvent&>(event);
// TODO(davemoore): This should never happen, but bug #326635 showed some
// surprising crashes.
CHECK(input_handler_);
HandlePointerMove(event_with_callback, mouse_event.PositionInWidget());
return DID_NOT_HANDLE;
}
case WebInputEvent::Type::kMouseLeave: {
CHECK(input_handler_);
input_handler_->MouseLeave();
return DID_NOT_HANDLE;
}
// Fling gestures are handled only in the browser process and not sent to
// the renderer.
case WebInputEvent::Type::kGestureFlingStart:
case WebInputEvent::Type::kGestureFlingCancel:
NOTREACHED();
break;
default:
break;
}
return DID_NOT_HANDLE;
}
WebInputEventAttribution InputHandlerProxy::PerformEventAttribution(
const WebInputEvent& event) {
if (!event_attribution_enabled_) {
return WebInputEventAttribution(WebInputEventAttribution::kUnknown);
}
if (WebInputEvent::IsKeyboardEventType(event.GetType())) {
// Keyboard events should be dispatched to the focused frame.
return WebInputEventAttribution(WebInputEventAttribution::kFocusedFrame);
} else if (WebInputEvent::IsMouseEventType(event.GetType()) ||
event.GetType() == WebInputEvent::Type::kMouseWheel) {
// Mouse events are dispatched based on their location in the DOM tree.
// Perform frame attribution via cc.
// TODO(acomminos): handle pointer locks, or provide a hint to the renderer
// to check pointer lock state
gfx::PointF point =
static_cast<const WebMouseEvent&>(event).PositionInWidget();
return WebInputEventAttribution(
WebInputEventAttribution::kTargetedFrame,
input_handler_->FindFrameElementIdAtPoint(point));
} else if (WebInputEvent::IsGestureEventType(event.GetType())) {
gfx::PointF point =
static_cast<const WebGestureEvent&>(event).PositionInWidget();
return WebInputEventAttribution(
WebInputEventAttribution::kTargetedFrame,
input_handler_->FindFrameElementIdAtPoint(point));
} else if (WebInputEvent::IsTouchEventType(event.GetType())) {
const auto& touch_event = static_cast<const WebTouchEvent&>(event);
if (touch_event.touches_length == 0) {
return WebInputEventAttribution(WebInputEventAttribution::kTargetedFrame,
cc::ElementId());
}
// Use the first touch location to perform frame attribution, similar to
// how the renderer host performs touch event dispatch.
// https://cs.chromium.org/chromium/src/content/browser/renderer_host/render_widget_host_input_event_router.cc?l=808&rcl=10fe9d0a725d4ed7b69266a5936c525f0a5b26d3
gfx::PointF point = touch_event.touches[0].PositionInWidget();
const cc::ElementId targeted_element =
input_handler_->FindFrameElementIdAtPoint(point);
return WebInputEventAttribution(WebInputEventAttribution::kTargetedFrame,
targeted_element);
} else {
return WebInputEventAttribution(WebInputEventAttribution::kUnknown);
}
}
void InputHandlerProxy::RecordMainThreadScrollingReasons(
WebGestureDevice device,
uint32_t reasons) {
if (device != WebGestureDevice::kTouchpad &&
device != WebGestureDevice::kScrollbar &&
device != WebGestureDevice::kTouchscreen) {
return;
}
// NonCompositedScrollReasons should only be set on the main thread.
DCHECK(
!cc::MainThreadScrollingReason::HasNonCompositedScrollReasons(reasons));
// This records whether a scroll is handled on the main or compositor
// threads. Note: scrolls handled on the compositor but blocked on main due
// to event handlers are still considered compositor scrolls.
const bool is_compositor_scroll =
reasons == cc::MainThreadScrollingReason::kNotScrollingOnMain;
base::Optional<EventDisposition> disposition =
(device == WebGestureDevice::kTouchpad ? mouse_wheel_result_
: touch_result_);
// Scrolling can be handled on the compositor thread but it might be blocked
// on the main thread waiting for non-passive event handlers to process the
// wheel/touch events (i.e. were they preventDefaulted?).
bool blocked_on_main_thread_handler =
disposition.has_value() && disposition == DID_NOT_HANDLE;
auto scroll_start_state = RecordScrollingThread(
is_compositor_scroll, blocked_on_main_thread_handler, device);
input_handler_->RecordScrollBegin(GestureScrollInputType(device),
scroll_start_state);
if (blocked_on_main_thread_handler) {
// We should also collect main thread scrolling reasons if a scroll event
// scrolls on impl thread but is blocked by main thread event handlers.
reasons |= (device == WebGestureDevice::kTouchpad
? cc::MainThreadScrollingReason::kWheelEventHandlerRegion
: cc::MainThreadScrollingReason::kTouchEventHandlerRegion);
}
RecordScrollReasonsMetric(device, reasons);
}
InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel(
const WebMouseWheelEvent& wheel_event) {
InputHandlerProxy::EventDisposition result = DROP_EVENT;
if (wheel_event.dispatch_type ==
WebInputEvent::DispatchType::kEventNonBlocking) {
// The first wheel event in the sequence should be cancellable.
DCHECK(wheel_event.phase != WebMouseWheelEvent::kPhaseBegan);
// Noncancellable wheel events should have phase info.
DCHECK(wheel_event.phase != WebMouseWheelEvent::kPhaseNone ||
wheel_event.momentum_phase != WebMouseWheelEvent::kPhaseNone);
// TODO(bokan): This should never happen but after changing
// mouse_event_result_ to a base::Optional, crashes indicate that it does
// so |if| maintains prior behavior. https://crbug.com/1069760.
if (mouse_wheel_result_.has_value()) {
result = mouse_wheel_result_.value();
if (wheel_event.phase == WebMouseWheelEvent::kPhaseEnded ||
wheel_event.phase == WebMouseWheelEvent::kPhaseCancelled ||
wheel_event.momentum_phase == WebMouseWheelEvent::kPhaseEnded ||
wheel_event.momentum_phase == WebMouseWheelEvent::kPhaseCancelled) {
mouse_wheel_result_.reset();
} else {
return result;
}
}
}
gfx::PointF position_in_widget = wheel_event.PositionInWidget();
if (input_handler_->HasBlockingWheelEventHandlerAt(
gfx::Point(position_in_widget.x(), position_in_widget.y()))) {
result = DID_NOT_HANDLE;
} else {
cc::EventListenerProperties properties =
input_handler_->GetEventListenerProperties(
cc::EventListenerClass::kMouseWheel);
switch (properties) {
case cc::EventListenerProperties::kBlockingAndPassive:
case cc::EventListenerProperties::kPassive:
result = DID_NOT_HANDLE_NON_BLOCKING;
break;
case cc::EventListenerProperties::kNone:
result = DROP_EVENT;
break;
default:
// If properties is kBlocking, and the event falls outside wheel event
// handler region, we should handle it the same as kNone.
result = DROP_EVENT;
}
}
mouse_wheel_result_ = result;
return result;
}
InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin(
const WebGestureEvent& gesture_event) {
TRACE_EVENT0("input", "InputHandlerProxy::HandleGestureScrollBegin");
if (scroll_predictor_)
scroll_predictor_->ResetOnGestureScrollBegin(gesture_event);
// When a GSB is being handled, end any pre-existing gesture scrolls that are
// in progress.
if (currently_active_gesture_device_.has_value() &&
handling_gesture_on_impl_thread_) {
// TODO(arakeri): Once crbug.com/1074209 is fixed, delete calls to
// RecordScrollEnd.
input_handler_->RecordScrollEnd(
GestureScrollInputType(*currently_active_gesture_device_));
InputHandlerScrollEnd();
}
cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event);
cc::InputHandler::ScrollStatus scroll_status;
if (gesture_event.data.scroll_begin.target_viewport) {
scroll_status = input_handler_->RootScrollBegin(
&scroll_state, GestureScrollInputType(gesture_event.SourceDevice()));
} else {
scroll_status = input_handler_->ScrollBegin(
&scroll_state, GestureScrollInputType(gesture_event.SourceDevice()));
}
// If we need a hit test from the main thread, we'll reinject this scroll
// begin event once the hit test is complete so avoid everything below for
// now, it'll be run on the second iteration.
if (scroll_status.needs_main_thread_hit_test) {
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
hit_testing_scroll_begin_on_main_thread_ = true;
return REQUIRES_MAIN_THREAD_HIT_TEST;
}
RecordMainThreadScrollingReasons(gesture_event.SourceDevice(),
scroll_status.main_thread_scrolling_reasons);
InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE;
scroll_sequence_ignored_ = false;
in_inertial_scrolling_ = false;
switch (scroll_status.thread) {
case ScrollThread::SCROLL_ON_IMPL_THREAD:
TRACE_EVENT_INSTANT0("input", "Handle On Impl", TRACE_EVENT_SCOPE_THREAD);
handling_gesture_on_impl_thread_ = true;
if (input_handler_->IsCurrentlyScrollingViewport())
client_->DidStartScrollingViewport();
// if the viewport cannot scroll, the scroll cannot be consumed so we
// drop the event
if (scroll_status.viewport_cannot_scroll)
result = DROP_EVENT;
else
result = DID_HANDLE;
break;
case ScrollThread::SCROLL_ON_MAIN_THREAD:
TRACE_EVENT_INSTANT0("input", "Handle On Main", TRACE_EVENT_SCOPE_THREAD);
result = DID_NOT_HANDLE;
break;
case ScrollThread::SCROLL_IGNORED:
TRACE_EVENT_INSTANT0("input", "Ignore Scroll", TRACE_EVENT_SCOPE_THREAD);
scroll_sequence_ignored_ = true;
result = DROP_EVENT;
break;
default:
NOTREACHED();
break;
}
// TODO(bokan): Should we really be calling this in cases like DROP_EVENT and
// DID_NOT_HANDLE_NON_BLOCKING_DUE_TO_FLING? I think probably not.
if (elastic_overscroll_controller_ && result != DID_NOT_HANDLE) {
HandleScrollElasticityOverscroll(gesture_event,
cc::InputHandlerScrollResult());
}
return result;
}
InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollUpdate(
const WebGestureEvent& gesture_event,
const WebInputEventAttribution& original_attribution,
const cc::EventMetrics* original_metrics) {
TRACE_EVENT2("input", "InputHandlerProxy::HandleGestureScrollUpdate", "dx",
-gesture_event.data.scroll_update.delta_x, "dy",
-gesture_event.data.scroll_update.delta_y);
if (scroll_sequence_ignored_) {
TRACE_EVENT_INSTANT0("input", "Scroll Sequence Ignored",
TRACE_EVENT_SCOPE_THREAD);
return DROP_EVENT;
}
if (!handling_gesture_on_impl_thread_ && !gesture_pinch_in_progress_)
return DID_NOT_HANDLE;
cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event);
in_inertial_scrolling_ = scroll_state.is_in_inertial_phase();
TRACE_EVENT_INSTANT1(
"input", "DeltaUnits", TRACE_EVENT_SCOPE_THREAD, "unit",
static_cast<int>(gesture_event.data.scroll_update.delta_units));
if (snap_fling_controller_->HandleGestureScrollUpdate(
GetGestureScrollUpdateInfo(gesture_event))) {
handling_gesture_on_impl_thread_ = false;
return DROP_EVENT;
}
if (!base::FeatureList::IsEnabled(::features::kScrollUnification) &&
input_handler_->ScrollingShouldSwitchtoMainThread()) {
TRACE_EVENT_INSTANT0("input", "Move Scroll To Main Thread",
TRACE_EVENT_SCOPE_THREAD);
handling_gesture_on_impl_thread_ = false;
currently_active_gesture_device_ = base::nullopt;
client_->GenerateScrollBeginAndSendToMainThread(
gesture_event, original_attribution, original_metrics);
// TODO(bokan): |!gesture_pinch_in_progress_| was put here by
// https://crrev.com/2720903005 but it's not clear to me how this is
// supposed to work - we already generated and sent a GSB to the main
// thread above so it's odd to continue handling on the compositor thread
// if a pinch was in progress. It probably makes more sense to bake this
// condition into ScrollingShouldSwitchToMainThread().
if (!gesture_pinch_in_progress_)
return DID_NOT_HANDLE;
}
base::TimeTicks event_time = gesture_event.TimeStamp();
base::TimeDelta delay = base::TimeTicks::Now() - event_time;
cc::InputHandlerScrollResult scroll_result =
input_handler_->ScrollUpdate(&scroll_state, delay);
HandleOverscroll(gesture_event.PositionInWidget(), scroll_result);
if (elastic_overscroll_controller_)
HandleScrollElasticityOverscroll(gesture_event, scroll_result);
return scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT;
}
// TODO(arakeri): Ensure that redudant GSE(s) in the CompositorThreadEventQueue
// are handled gracefully. (i.e currently, when an ongoing scroll needs to end,
// we call RecordScrollEnd and InputHandlerScrollEnd synchronously. Ideally, we
// should end the scroll when the GSB is being handled).
InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollEnd(
const WebGestureEvent& gesture_event) {
TRACE_EVENT0("input", "InputHandlerProxy::HandleGestureScrollEnd");
// TODO(bokan): It seems odd that we'd record a ScrollEnd for a scroll
// secuence that was ignored (i.e. the ScrollBegin was dropped). However,
// RecordScrollBegin does get called in that case so this needs to be this
// way for now. This makes life rather awkward for the unified scrolling path
// so perhaps we should only record a scrolling thread if a scroll actually
// started? https://crbug.com/1082601.
input_handler_->RecordScrollEnd(
GestureScrollInputType(gesture_event.SourceDevice()));
if (scroll_sequence_ignored_) {
DCHECK(!currently_active_gesture_device_.has_value());
return DROP_EVENT;
}
if (!handling_gesture_on_impl_thread_) {
DCHECK(!currently_active_gesture_device_.has_value());
return DID_NOT_HANDLE;
}
if (!currently_active_gesture_device_.has_value() ||
(currently_active_gesture_device_.value() !=
gesture_event.SourceDevice()))
return DROP_EVENT;
InputHandlerScrollEnd();
if (elastic_overscroll_controller_) {
HandleScrollElasticityOverscroll(gesture_event,
cc::InputHandlerScrollResult());
}
return DID_HANDLE;
}
void InputHandlerProxy::InputHandlerScrollEnd() {
input_handler_->ScrollEnd(/*should_snap=*/true);
handling_gesture_on_impl_thread_ = false;
DCHECK(!gesture_pinch_in_progress_);
currently_active_gesture_device_ = base::nullopt;
}
InputHandlerProxy::EventDisposition InputHandlerProxy::HitTestTouchEvent(
const WebTouchEvent& touch_event,
bool* is_touching_scrolling_layer,
cc::TouchAction* allowed_touch_action) {
TRACE_EVENT1("input", "InputHandlerProxy::HitTestTouchEvent",
"Needs allowed TouchAction",
static_cast<bool>(allowed_touch_action));
*is_touching_scrolling_layer = false;
EventDisposition result = DROP_EVENT;
for (size_t i = 0; i < touch_event.touches_length; ++i) {
if (touch_event.touch_start_or_first_touch_move)
DCHECK(allowed_touch_action);
else
DCHECK(!allowed_touch_action);
if (touch_event.GetType() == WebInputEvent::Type::kTouchStart &&
touch_event.touches[i].state != WebTouchPoint::State::kStatePressed) {
continue;
}
cc::TouchAction touch_action = cc::TouchAction::kAuto;
cc::InputHandler::TouchStartOrMoveEventListenerType event_listener_type =
input_handler_->EventListenerTypeForTouchStartOrMoveAt(
gfx::Point(touch_event.touches[i].PositionInWidget().x(),
touch_event.touches[i].PositionInWidget().y()),
&touch_action);
if (allowed_touch_action && touch_action != cc::TouchAction::kAuto) {
TRACE_EVENT_INSTANT1("input", "Adding TouchAction",
TRACE_EVENT_SCOPE_THREAD, "TouchAction",
cc::TouchActionToString(touch_action));
*allowed_touch_action &= touch_action;
}
if (event_listener_type !=
cc::InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER) {
TRACE_EVENT_INSTANT1("input", "HaveHandler", TRACE_EVENT_SCOPE_THREAD,
"Type", event_listener_type);
*is_touching_scrolling_layer =
event_listener_type ==
cc::InputHandler::TouchStartOrMoveEventListenerType::
HANDLER_ON_SCROLLING_LAYER;
// A non-passive touch start / move will always set the allowed touch
// action to TouchAction::kNone, and in that case we do not ack the event
// from the compositor.
if (allowed_touch_action &&
*allowed_touch_action != cc::TouchAction::kNone) {
TRACE_EVENT_INSTANT0("input", "NonBlocking due to allowed touchaction",
TRACE_EVENT_SCOPE_THREAD);
result = DID_NOT_HANDLE_NON_BLOCKING;
} else {
TRACE_EVENT_INSTANT0("input", "DidNotHandle due to no touchaction",
TRACE_EVENT_SCOPE_THREAD);
result = DID_NOT_HANDLE;
}
break;
}
}
// If |result| is DROP_EVENT it wasn't processed above.
if (result == DROP_EVENT) {
auto event_listener_class = input_handler_->GetEventListenerProperties(
cc::EventListenerClass::kTouchStartOrMove);
TRACE_EVENT_INSTANT1("input", "DropEvent", TRACE_EVENT_SCOPE_THREAD,
"listener", event_listener_class);
switch (event_listener_class) {
case cc::EventListenerProperties::kPassive:
result = DID_NOT_HANDLE_NON_BLOCKING;
break;
case cc::EventListenerProperties::kBlocking:
// The touch area rects above already have checked whether it hits
// a blocking region. Since it does not the event can be dropped.
result = DROP_EVENT;
break;
case cc::EventListenerProperties::kBlockingAndPassive:
// There is at least one passive listener that needs to possibly
// be notified so it can't be dropped.
result = DID_NOT_HANDLE_NON_BLOCKING;
break;
case cc::EventListenerProperties::kNone:
result = DROP_EVENT;
break;
default:
NOTREACHED();
result = DROP_EVENT;
break;
}
}
// Depending on which arm of the SkipTouchEventFilter experiment we're on, we
// may need to simulate a passive listener instead of dropping touch events.
if (result == DROP_EVENT &&
(skip_touch_filter_all_ ||
(skip_touch_filter_discrete_ &&
touch_event.GetType() == WebInputEvent::Type::kTouchStart))) {
TRACE_EVENT_INSTANT0("input", "Non blocking due to skip filter",
TRACE_EVENT_SCOPE_THREAD);
result = DID_NOT_HANDLE_NON_BLOCKING;
}
// Merge |touch_result_| and |result| so the result has the highest
// priority value according to the sequence; (DROP_EVENT,
// DID_NOT_HANDLE_NON_BLOCKING, DID_NOT_HANDLE).
if (!touch_result_.has_value() || touch_result_ == DROP_EVENT ||
result == DID_NOT_HANDLE) {
TRACE_EVENT_INSTANT2(
"input", "Update touch_result_", TRACE_EVENT_SCOPE_THREAD, "old",
(touch_result_ ? touch_result_.value() : -1), "new", result);
touch_result_ = result;
}
return result;
}
InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart(
EventWithCallback* event_with_callback) {
TRACE_EVENT0("input", "InputHandlerProxy::HandleTouchStart");
const auto& touch_event =
static_cast<const WebTouchEvent&>(event_with_callback->event());
bool is_touching_scrolling_layer;
cc::TouchAction allowed_touch_action = cc::TouchAction::kAuto;
EventDisposition result = HitTestTouchEvent(
touch_event, &is_touching_scrolling_layer, &allowed_touch_action);
TRACE_EVENT_INSTANT1("input", "HitTest", TRACE_EVENT_SCOPE_THREAD,
"disposition", result);
if (allowed_touch_action != cc::TouchAction::kNone &&
touch_event.touches_length == 1) {
DCHECK(touch_event.touches[0].state == WebTouchPoint::State::kStatePressed);
cc::InputHandlerPointerResult pointer_result = HandlePointerDown(
event_with_callback, touch_event.touches[0].PositionInWidget());
if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) {
client_->SetAllowedTouchAction(
allowed_touch_action, touch_event.unique_touch_event_id, DID_HANDLE);
return DID_HANDLE;
}
}
// If |result| is still DROP_EVENT look at the touch end handler as we may
// not want to discard the entire touch sequence. Note this code is
// explicitly after the assignment of the |touch_result_| in
// HitTestTouchEvent so the touch moves are not sent to the main thread
// un-necessarily.
if (result == DROP_EVENT && input_handler_->GetEventListenerProperties(
cc::EventListenerClass::kTouchEndOrCancel) !=
cc::EventListenerProperties::kNone) {
TRACE_EVENT_INSTANT0("input", "NonBlocking due to TouchEnd handler",
TRACE_EVENT_SCOPE_THREAD);
result = DID_NOT_HANDLE_NON_BLOCKING;
}
bool is_in_inertial_scrolling_on_impl =
in_inertial_scrolling_ && handling_gesture_on_impl_thread_;
if (is_in_inertial_scrolling_on_impl && is_touching_scrolling_layer) {
// If the touchstart occurs during a fling, it will be ACK'd immediately
// and it and its following touch moves will be dispatched as non-blocking.
// Due to tap suppression on the browser side, this will reset the
// browser-side touch action (see comment in
// TouchActionFilter::FilterGestureEvent for GestureScrollBegin). Ensure we
// send back an allowed_touch_action that matches this non-blocking behavior
// rather than treating it as if it'll block.
TRACE_EVENT_INSTANT0("input", "NonBlocking due to fling",
TRACE_EVENT_SCOPE_THREAD);
allowed_touch_action = cc::TouchAction::kAuto;
result = DID_NOT_HANDLE_NON_BLOCKING_DUE_TO_FLING;
}
TRACE_EVENT_INSTANT2(
"input", "Allowed TouchAction", TRACE_EVENT_SCOPE_THREAD, "TouchAction",
cc::TouchActionToString(allowed_touch_action), "disposition", result);
client_->SetAllowedTouchAction(allowed_touch_action,
touch_event.unique_touch_event_id, result);
return result;
}
InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchMove(
EventWithCallback* event_with_callback) {
const auto& touch_event =
static_cast<const WebTouchEvent&>(event_with_callback->event());
TRACE_EVENT2("input", "InputHandlerProxy::HandleTouchMove", "touch_result",
touch_result_.has_value() ? touch_result_.value() : -1,
"is_start_or_first",
touch_event.touch_start_or_first_touch_move);
if (touch_event.touches_length == 1) {
cc::InputHandlerPointerResult pointer_result = HandlePointerMove(
event_with_callback, touch_event.touches[0].PositionInWidget());
if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) {
return DID_HANDLE;
}
}
// Hit test if this is the first touch move or we don't have any results
// from a previous hit test.
if (!touch_result_.has_value() ||
touch_event.touch_start_or_first_touch_move) {
bool is_touching_scrolling_layer;
cc::TouchAction allowed_touch_action = cc::TouchAction::kAuto;
EventDisposition result = HitTestTouchEvent(
touch_event, &is_touching_scrolling_layer, &allowed_touch_action);
TRACE_EVENT_INSTANT2(
"input", "Allowed TouchAction", TRACE_EVENT_SCOPE_THREAD, "TouchAction",
cc::TouchActionToString(allowed_touch_action), "disposition", result);
client_->SetAllowedTouchAction(allowed_touch_action,
touch_event.unique_touch_event_id, result);
return result;
}
return touch_result_.value();
}
InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchEnd(
EventWithCallback* event_with_callback) {
const auto& touch_event =
static_cast<const WebTouchEvent&>(event_with_callback->event());
TRACE_EVENT1("input", "InputHandlerProxy::HandleTouchEnd", "num_touches",
touch_event.touches_length);
if (touch_event.touches_length == 1) {
cc::InputHandlerPointerResult pointer_result = HandlePointerUp(
event_with_callback, touch_event.touches[0].PositionInWidget());
if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) {
return DID_HANDLE;
}
}
if (touch_event.touches_length == 1)
touch_result_.reset();
return DID_NOT_HANDLE;
}
void InputHandlerProxy::Animate(base::TimeTicks time) {
if (elastic_overscroll_controller_)
elastic_overscroll_controller_->Animate(time);
snap_fling_controller_->Animate(time);
// These animations can change the root scroll offset, so inform the
// synchronous input handler.
if (synchronous_input_handler_)
input_handler_->RequestUpdateForSynchronousInputHandler();
}
void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() {
if (elastic_overscroll_controller_)
elastic_overscroll_controller_->ReconcileStretchAndScroll();
}
void InputHandlerProxy::UpdateRootLayerStateForSynchronousInputHandler(
const gfx::ScrollOffset& total_scroll_offset,
const gfx::ScrollOffset& max_scroll_offset,
const gfx::SizeF& scrollable_size,
float page_scale_factor,
float min_page_scale_factor,
float max_page_scale_factor) {
if (synchronous_input_handler_) {
synchronous_input_handler_->UpdateRootLayerState(
total_scroll_offset, max_scroll_offset, scrollable_size,
page_scale_factor, min_page_scale_factor, max_page_scale_factor);
}
}
void InputHandlerProxy::DeliverInputForBeginFrame(
const viz::BeginFrameArgs& args) {
// Block flushing the compositor gesture event queue while there's an async
// scroll begin hit test outstanding. We'll flush the queue when the hit test
// responds.
if (hit_testing_scroll_begin_on_main_thread_) {
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
return;
}
if (!scroll_predictor_)
DispatchQueuedInputEvents();
// Resampling GSUs and dispatch queued input events.
while (!compositor_event_queue_->empty()) {
std::unique_ptr<EventWithCallback> event_with_callback =
scroll_predictor_->ResampleScrollEvents(compositor_event_queue_->Pop(),
args.frame_time, args.interval);
DispatchSingleInputEvent(std::move(event_with_callback), args.frame_time);
}
}
void InputHandlerProxy::DeliverInputForHighLatencyMode() {
// When prediction enabled, do not handle input after commit complete.
if (!scroll_predictor_)
DispatchQueuedInputEvents();
}
void InputHandlerProxy::SetSynchronousInputHandler(
SynchronousInputHandler* synchronous_input_handler) {
synchronous_input_handler_ = synchronous_input_handler;
if (synchronous_input_handler_)
input_handler_->RequestUpdateForSynchronousInputHandler();
}
void InputHandlerProxy::SynchronouslySetRootScrollOffset(
const gfx::ScrollOffset& root_offset) {
DCHECK(synchronous_input_handler_);
input_handler_->SetSynchronousInputHandlerRootScrollOffset(root_offset);
}
void InputHandlerProxy::SynchronouslyZoomBy(float magnify_delta,
const gfx::Point& anchor) {
DCHECK(synchronous_input_handler_);
input_handler_->PinchGestureBegin();
input_handler_->PinchGestureUpdate(magnify_delta, anchor);
input_handler_->PinchGestureEnd(anchor, false);
}
bool InputHandlerProxy::GetSnapFlingInfoAndSetAnimatingSnapTarget(
const gfx::Vector2dF& natural_displacement,
gfx::Vector2dF* initial_offset,
gfx::Vector2dF* target_offset) const {
return input_handler_->GetSnapFlingInfoAndSetAnimatingSnapTarget(
natural_displacement, initial_offset, target_offset);
}
gfx::Vector2dF InputHandlerProxy::ScrollByForSnapFling(
const gfx::Vector2dF& delta) {
cc::ScrollState scroll_state = CreateScrollStateForInertialUpdate(delta);
cc::InputHandlerScrollResult scroll_result =
input_handler_->ScrollUpdate(&scroll_state, base::TimeDelta());
return scroll_result.current_visual_offset;
}
void InputHandlerProxy::ScrollEndForSnapFling(bool did_finish) {
input_handler_->ScrollEndForSnapFling(did_finish);
}
void InputHandlerProxy::RequestAnimationForSnapFling() {
RequestAnimation();
}
void InputHandlerProxy::HandleOverscroll(
const gfx::PointF& causal_event_viewport_point,
const cc::InputHandlerScrollResult& scroll_result) {
DCHECK(client_);
if (!scroll_result.did_overscroll_root)
return;
TRACE_EVENT2("input", "InputHandlerProxy::DidOverscroll", "dx",
scroll_result.unused_scroll_delta.x(), "dy",
scroll_result.unused_scroll_delta.y());
// Bundle overscroll message with triggering event response, saving an IPC.
current_overscroll_params_ = std::make_unique<DidOverscrollParams>();
current_overscroll_params_->accumulated_overscroll =
scroll_result.accumulated_root_overscroll;
current_overscroll_params_->latest_overscroll_delta =
scroll_result.unused_scroll_delta;
current_overscroll_params_->causal_event_viewport_point =
causal_event_viewport_point;
current_overscroll_params_->overscroll_behavior =
scroll_result.overscroll_behavior;
return;
}
void InputHandlerProxy::RequestAnimation() {
input_handler_->SetNeedsAnimateInput();
}
void InputHandlerProxy::HandleScrollElasticityOverscroll(
const WebGestureEvent& gesture_event,
const cc::InputHandlerScrollResult& scroll_result) {
DCHECK(elastic_overscroll_controller_);
elastic_overscroll_controller_->ObserveGestureEventAndResult(gesture_event,
scroll_result);
}
void InputHandlerProxy::SetTickClockForTesting(
const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
const cc::InputHandlerPointerResult InputHandlerProxy::HandlePointerDown(
EventWithCallback* event_with_callback,
const gfx::PointF& position) {
CHECK(input_handler_);
if (input_handler_->HitTest(position) !=
cc::PointerResultType::kScrollbarScroll)
return cc::InputHandlerPointerResult();
// Since a kScrollbarScroll is about to commence, ensure that any existing
// ongoing scroll is ended.
if (currently_active_gesture_device_.has_value()) {
DCHECK_NE(*currently_active_gesture_device_,
WebGestureDevice::kUninitialized);
if (gesture_pinch_in_progress_) {
input_handler_->PinchGestureEnd(gfx::ToFlooredPoint(position), true);
}
if (handling_gesture_on_impl_thread_) {
input_handler_->RecordScrollEnd(
GestureScrollInputType(*currently_active_gesture_device_));
InputHandlerScrollEnd();
}
}
// Generate GSB and GSU events and add them to the CompositorThreadEventQueue.
// Note that the latency info passed in to InjectScrollbarGestureScroll is the
// original LatencyInfo, not the one that may be currently monitored. The
// currently monitored one may be modified by the call to
// InjectScrollbarGestureScroll, as it will SetNeedsAnimateInput if the
// CompositorThreadEventQueue is currently empty.
// TODO(arakeri): Pass in the modifier instead of a bool once the refactor
// (crbug.com/1022097) is done. For details, see crbug.com/1016955.
const cc::InputHandlerPointerResult pointer_result =
input_handler_->MouseDown(
position, HasScrollbarJumpKeyModifier(event_with_callback->event()));
InjectScrollbarGestureScroll(
WebInputEvent::Type::kGestureScrollBegin, position, pointer_result,
event_with_callback->latency_info(),
event_with_callback->event().TimeStamp(), event_with_callback->metrics());
// Don't need to inject GSU if the scroll offset is zero (this can be the case
// where mouse down occurs on the thumb).
if (!pointer_result.scroll_offset.IsZero()) {
InjectScrollbarGestureScroll(WebInputEvent::Type::kGestureScrollUpdate,
position, pointer_result,
event_with_callback->latency_info(),
event_with_callback->event().TimeStamp(),
event_with_callback->metrics());
}
if (event_with_callback) {
event_with_callback->SetScrollbarManipulationHandledOnCompositorThread();
}
return pointer_result;
}
const cc::InputHandlerPointerResult InputHandlerProxy::HandlePointerMove(
EventWithCallback* event_with_callback,
const gfx::PointF& position) {
cc::InputHandlerPointerResult pointer_result =
input_handler_->MouseMoveAt(gfx::Point(position.x(), position.y()));
if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) {
// Generate a GSU event and add it to the CompositorThreadEventQueue if
// delta is non zero.
if (!pointer_result.scroll_offset.IsZero()) {
InjectScrollbarGestureScroll(WebInputEvent::Type::kGestureScrollUpdate,
position, pointer_result,
event_with_callback->latency_info(),
event_with_callback->event().TimeStamp(),
event_with_callback->metrics());
}
if (event_with_callback) {
event_with_callback->SetScrollbarManipulationHandledOnCompositorThread();
}
}
return pointer_result;
}
const cc::InputHandlerPointerResult InputHandlerProxy::HandlePointerUp(
EventWithCallback* event_with_callback,
const gfx::PointF& position) {
cc::InputHandlerPointerResult pointer_result =
input_handler_->MouseUp(position);
if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) {
// Generate a GSE and add it to the CompositorThreadEventQueue.
InjectScrollbarGestureScroll(WebInputEvent::Type::kGestureScrollEnd,
position, pointer_result,
event_with_callback->latency_info(),
event_with_callback->event().TimeStamp(),
event_with_callback->metrics());
if (event_with_callback) {
event_with_callback->SetScrollbarManipulationHandledOnCompositorThread();
}
}
return pointer_result;
}
} // namespace blink