diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/widget/input')
16 files changed, 2184 insertions, 269 deletions
diff --git a/chromium/third_party/blink/renderer/platform/widget/input/DEPS b/chromium/third_party/blink/renderer/platform/widget/input/DEPS index 97b795dad74..6ad1673e51e 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/DEPS +++ b/chromium/third_party/blink/renderer/platform/widget/input/DEPS @@ -3,6 +3,7 @@ include_rules = [ "+base/numerics/math_constants.h", "+base/profiler/sample_metadata.h", "+base/strings/string_number_conversions.h", + "+cc/base/features.h", "+cc/input/input_handler.h", "+cc/input/scroll_behavior.h", "+cc/input/scroll_elasticity_helper.h", @@ -17,4 +18,4 @@ include_rules = [ "+ui/base/ui_base_features.h", "+ui/events/types/scroll_types.h", "+ui/latency/latency_info.h", -]
\ No newline at end of file +] diff --git a/chromium/third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.cc b/chromium/third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.cc index fba0dda603d..43850976709 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.cc @@ -173,14 +173,16 @@ void CompositorThreadEventQueue::Queue( std::unique_ptr<EventWithCallback> scroll_event = std::make_unique<EventWithCallback>( - coalesced_events.first.Clone(), scroll_latency, + std::make_unique<WebCoalescedInputEvent>( + coalesced_events.first.Clone(), scroll_latency), oldest_creation_timestamp, timestamp_now, std::move(scroll_original_events)); scroll_event->set_coalesced_scroll_and_pinch(); std::unique_ptr<EventWithCallback> pinch_event = std::make_unique<EventWithCallback>( - coalesced_events.second.Clone(), pinch_latency, + std::make_unique<WebCoalescedInputEvent>( + coalesced_events.second.Clone(), pinch_latency), oldest_creation_timestamp, timestamp_now, std::move(pinch_original_events)); pinch_event->set_coalesced_scroll_and_pinch(); diff --git a/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.cc b/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.cc index d79d40d3d1e..b9842eff91e 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.cc @@ -11,25 +11,21 @@ namespace blink { EventWithCallback::EventWithCallback( - WebScopedInputEvent event, - const ui::LatencyInfo& latency, + std::unique_ptr<WebCoalescedInputEvent> event, base::TimeTicks timestamp_now, InputHandlerProxy::EventDispositionCallback callback) - : event_(event->Clone()), - latency_(latency), + : event_(std::make_unique<WebCoalescedInputEvent>(*event)), creation_timestamp_(timestamp_now), last_coalesced_timestamp_(timestamp_now) { - original_events_.emplace_back(std::move(event), latency, std::move(callback)); + original_events_.emplace_back(std::move(event), std::move(callback)); } EventWithCallback::EventWithCallback( - WebScopedInputEvent event, - const ui::LatencyInfo& latency, + std::unique_ptr<WebCoalescedInputEvent> event, base::TimeTicks creation_timestamp, base::TimeTicks last_coalesced_timestamp, std::unique_ptr<OriginalEventList> original_events) : event_(std::move(event)), - latency_(latency), creation_timestamp_(creation_timestamp), last_coalesced_timestamp_(last_coalesced_timestamp) { if (original_events) @@ -43,33 +39,15 @@ bool EventWithCallback::CanCoalesceWith(const EventWithCallback& other) const { } void EventWithCallback::SetScrollbarManipulationHandledOnCompositorThread() { - for (auto& original_event : original_events_) - original_event.event_->SetScrollbarManipulationHandledOnCompositorThread(); + for (auto& original_event : original_events_) { + original_event.event_->EventPointer() + ->SetScrollbarManipulationHandledOnCompositorThread(); + } } void EventWithCallback::CoalesceWith(EventWithCallback* other, base::TimeTicks timestamp_now) { - TRACE_EVENT2("input", "EventWithCallback::CoalesceWith", "traceId", - latency_.trace_id(), "coalescedTraceId", - other->latency_.trace_id()); - // |other| should be a newer event than |this|. - if (other->latency_.trace_id() >= 0 && latency_.trace_id() >= 0) - DCHECK_GT(other->latency_.trace_id(), latency_.trace_id()); - - // New events get coalesced into older events, and the newer timestamp - // should always be preserved. - const base::TimeTicks time_stamp = other->event().TimeStamp(); - event_->Coalesce(other->event()); - event_->SetTimeStamp(time_stamp); - - // When coalescing two input events, we keep the oldest LatencyInfo - // since it will represent the longest latency. If it's a GestureScrollUpdate - // event, also update the old event's last timestamp and scroll delta using - // the newer event's latency info. - if (event_->GetType() == WebInputEvent::Type::kGestureScrollUpdate) - latency_.CoalesceScrollUpdateWith(other->latency_); - other->latency_ = latency_; - other->latency_.set_coalesced(); + event_->CoalesceWith(*other->event_); // Move original events. original_events_.splice(original_events_.end(), other->original_events_); @@ -96,8 +74,9 @@ void EventWithCallback::RunCallbacks( return; // Ack the oldest event with original latency. + original_events_.front().event_->latency_info() = latency; std::move(original_events_.front().callback_) - .Run(disposition, std::move(original_events_.front().event_), latency, + .Run(disposition, std::move(original_events_.front().event_), did_overscroll_params ? std::make_unique<InputHandlerProxy::DidOverscrollParams>( *did_overscroll_params) @@ -114,14 +93,14 @@ void EventWithCallback::RunCallbacks( bool handled = HandledOnCompositorThread(disposition); for (auto& coalesced_event : original_events_) { if (handled) { - int64_t original_trace_id = coalesced_event.latency_.trace_id(); - coalesced_event.latency_ = latency; - coalesced_event.latency_.set_trace_id(original_trace_id); - coalesced_event.latency_.set_coalesced(); + int64_t original_trace_id = + coalesced_event.event_->latency_info().trace_id(); + coalesced_event.event_->latency_info() = latency; + coalesced_event.event_->latency_info().set_trace_id(original_trace_id); + coalesced_event.event_->latency_info().set_coalesced(); } std::move(coalesced_event.callback_) .Run(disposition, std::move(coalesced_event.event_), - coalesced_event.latency_, did_overscroll_params ? std::make_unique<InputHandlerProxy::DidOverscrollParams>( *did_overscroll_params) @@ -131,12 +110,9 @@ void EventWithCallback::RunCallbacks( } EventWithCallback::OriginalEventWithCallback::OriginalEventWithCallback( - WebScopedInputEvent event, - const ui::LatencyInfo& latency, + std::unique_ptr<WebCoalescedInputEvent> event, InputHandlerProxy::EventDispositionCallback callback) - : event_(std::move(event)), - latency_(latency), - callback_(std::move(callback)) {} + : event_(std::move(event)), callback_(std::move(callback)) {} EventWithCallback::OriginalEventWithCallback::~OriginalEventWithCallback() {} diff --git a/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.h b/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.h index cca19851de8..ad0e56dfcec 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.h +++ b/chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.h @@ -7,6 +7,7 @@ #include <list> +#include "third_party/blink/public/common/input/web_coalesced_input_event.h" #include "third_party/blink/public/platform/input/input_handler_proxy.h" #include "third_party/blink/renderer/platform/platform_export.h" #include "ui/latency/latency_info.h" @@ -19,26 +20,20 @@ class InputHandlerProxyEventQueueTest; class PLATFORM_EXPORT EventWithCallback { public: - using WebScopedInputEvent = std::unique_ptr<WebInputEvent>; - struct PLATFORM_EXPORT OriginalEventWithCallback { OriginalEventWithCallback( - WebScopedInputEvent event, - const ui::LatencyInfo& latency, + std::unique_ptr<WebCoalescedInputEvent> event, InputHandlerProxy::EventDispositionCallback callback); ~OriginalEventWithCallback(); - WebScopedInputEvent event_; - ui::LatencyInfo latency_; + std::unique_ptr<WebCoalescedInputEvent> event_; InputHandlerProxy::EventDispositionCallback callback_; }; using OriginalEventList = std::list<OriginalEventWithCallback>; - EventWithCallback(WebScopedInputEvent event, - const ui::LatencyInfo& latency, + EventWithCallback(std::unique_ptr<WebCoalescedInputEvent> event, base::TimeTicks timestamp_now, InputHandlerProxy::EventDispositionCallback callback); - EventWithCallback(WebScopedInputEvent event, - const ui::LatencyInfo& latency, + EventWithCallback(std::unique_ptr<WebCoalescedInputEvent> event, base::TimeTicks creation_timestamp, base::TimeTicks last_coalesced_timestamp, std::unique_ptr<OriginalEventList> original_events); @@ -52,10 +47,10 @@ class PLATFORM_EXPORT EventWithCallback { std::unique_ptr<InputHandlerProxy::DidOverscrollParams>, const WebInputEventAttribution&); - const WebInputEvent& event() const { return *event_; } - WebInputEvent* event_pointer() { return event_.get(); } - const ui::LatencyInfo& latency_info() const { return latency_; } - ui::LatencyInfo* mutable_latency_info() { return &latency_; } + const WebInputEvent& event() const { return event_->Event(); } + WebInputEvent* event_pointer() { return event_->EventPointer(); } + const ui::LatencyInfo& latency_info() const { return event_->latency_info(); } + ui::LatencyInfo& latency_info() { return event_->latency_info(); } base::TimeTicks creation_timestamp() const { return creation_timestamp_; } base::TimeTicks last_coalesced_timestamp() const { return last_coalesced_timestamp_; @@ -68,8 +63,9 @@ class PLATFORM_EXPORT EventWithCallback { OriginalEventList& original_events() { return original_events_; } // |first_original_event()| is used as ID for tracing. WebInputEvent* first_original_event() { - return original_events_.empty() ? nullptr - : original_events_.front().event_.get(); + return original_events_.empty() + ? nullptr + : original_events_.front().event_->EventPointer(); } void SetScrollbarManipulationHandledOnCompositorThread(); @@ -78,8 +74,7 @@ class PLATFORM_EXPORT EventWithCallback { void SetTickClockForTesting(std::unique_ptr<base::TickClock> tick_clock); - WebScopedInputEvent event_; - ui::LatencyInfo latency_; + std::unique_ptr<WebCoalescedInputEvent> event_; OriginalEventList original_events_; bool coalesced_scroll_and_pinch_ = false; diff --git a/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc b/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc index 9da77d69fb0..e366ca1ec13 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc @@ -22,6 +22,7 @@ #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" @@ -63,6 +64,25 @@ cc::ScrollState CreateScrollStateForGesture(const WebGestureEvent& event) { 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; @@ -235,27 +255,26 @@ void InputHandlerProxy::WillShutdown() { } void InputHandlerProxy::HandleInputEventWithLatencyInfo( - WebScopedInputEvent event, - const ui::LatencyInfo& latency_info, + std::unique_ptr<blink::WebCoalescedInputEvent> event, EventDispositionCallback callback) { DCHECK(input_handler_); input_handler_->NotifyInputEvent(); + int64_t trace_id = event->latency_info().trace_id(); TRACE_EVENT("input,benchmark", "LatencyInfo.Flow", - [&latency_info](perfetto::EventContext ctx) { + [trace_id](perfetto::EventContext ctx) { ChromeLatencyInfo* info = ctx.event()->set_chrome_latency_info(); - info->set_trace_id(latency_info.trace_id()); + info->set_trace_id(trace_id); info->set_step(ChromeLatencyInfo::STEP_HANDLE_INPUT_EVENT_IMPL); tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT, - latency_info.trace_id()); + trace_id); }); std::unique_ptr<EventWithCallback> event_with_callback = - std::make_unique<EventWithCallback>(std::move(event), latency_info, - tick_clock_->NowTicks(), - std::move(callback)); + std::make_unique<EventWithCallback>( + std::move(event), tick_clock_->NowTicks(), std::move(callback)); enum { NO_SCROLL_PINCH = 0, @@ -348,6 +367,62 @@ void InputHandlerProxy::HandleInputEventWithLatencyInfo( tick_clock_->NowTicks()); } +void InputHandlerProxy::ContinueScrollBeginAfterMainThreadHitTest( + std::unique_ptr<blink::WebCoalescedInputEvent> event, + 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; + + std::unique_ptr<EventWithCallback> event_with_callback = + std::make_unique<EventWithCallback>( + std::move(event), tick_clock_->NowTicks(), std::move(callback)); + + 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); + } + + // 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) { @@ -376,7 +451,16 @@ void InputHandlerProxy::DispatchSingleInputEvent( case WebGestureEvent::Type::kGestureScrollBegin: case WebGestureEvent::Type::kGesturePinchBegin: if (disposition == DID_HANDLE || - disposition == DID_HANDLE_SHOULD_BUBBLE) { + disposition == DID_HANDLE_SHOULD_BUBBLE || + disposition == REQUIRES_MAIN_THREAD_HIT_TEST) { + // 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. currently_active_gesture_device_ = static_cast<const WebGestureEvent&>(event).SourceDevice(); } @@ -420,6 +504,14 @@ void InputHandlerProxy::DispatchSingleInputEvent( } 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()) @@ -486,7 +578,8 @@ void InputHandlerProxy::InjectScrollbarGestureScroll( std::unique_ptr<EventWithCallback> gesture_event_with_callback_update = std::make_unique<EventWithCallback>( - std::move(web_scoped_gesture_event), scrollbar_latency_info, + std::make_unique<WebCoalescedInputEvent>( + std::move(web_scoped_gesture_event), scrollbar_latency_info), original_timestamp, original_timestamp, nullptr); bool needs_animate_input = compositor_event_queue_->empty(); @@ -497,7 +590,7 @@ void InputHandlerProxy::InjectScrollbarGestureScroll( input_handler_->SetNeedsAnimateInput(); } -bool HasModifier(const WebInputEvent& event) { +bool HasScrollbarJumpKeyModifier(const WebInputEvent& event) { #if defined(OS_MACOSX) // Mac uses the "Option" key (which is mapped to the enum "kAltKey"). return event.GetModifiers() & WebInputEvent::kAltKey; @@ -588,7 +681,7 @@ InputHandlerProxy::RouteToTypeSpecificHandler( cc::InputHandlerPointerResult pointer_result = input_handler_->MouseDown( gfx::PointF(mouse_event.PositionInWidget()), - HasModifier(event)); + HasScrollbarJumpKeyModifier(event)); if (pointer_result.type == cc::PointerResultType::kScrollbarScroll) { // Since a kScrollbarScroll is about to commence, ensure that any // existing ongoing scroll is ended. @@ -903,24 +996,23 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin( cc::ScrollState scroll_state = CreateScrollStateForGesture(gesture_event); cc::InputHandler::ScrollStatus scroll_status; - cc::ElementIdType element_id_type = - gesture_event.data.scroll_begin.scrollable_area_element_id; - if (element_id_type) { - scroll_state.data()->set_current_native_scrolling_element( - cc::ElementId(element_id_type)); - } - if (gesture_event.data.scroll_begin.delta_hint_units == - ui::ScrollGranularity::kScrollByPage) { - scroll_status.thread = cc::InputHandler::SCROLL_ON_MAIN_THREAD; - scroll_status.main_thread_scrolling_reasons = - cc::MainThreadScrollingReason::kContinuingMainThreadScroll; - } else if (gesture_event.data.scroll_begin.target_viewport) { + 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); @@ -950,6 +1042,9 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin( result = DROP_EVENT; 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()); @@ -988,7 +1083,8 @@ InputHandlerProxy::HandleGestureScrollUpdate( return DROP_EVENT; } - if (input_handler_->ScrollingShouldSwitchtoMainThread()) { + 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; @@ -1020,9 +1116,20 @@ InputHandlerProxy::HandleGestureScrollUpdate( 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())); @@ -1053,22 +1160,25 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollEnd( 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* white_listed_touch_action) { + cc::TouchAction* allowed_touch_action) { TRACE_EVENT1("input", "InputHandlerProxy::HitTestTouchEvent", - "Needs whitelisted TouchAction", - static_cast<bool>(white_listed_touch_action)); + "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(white_listed_touch_action); + DCHECK(allowed_touch_action); else - DCHECK(!white_listed_touch_action); + DCHECK(!allowed_touch_action); if (touch_event.GetType() == WebInputEvent::Type::kTouchStart && touch_event.touches[i].state != WebTouchPoint::State::kStatePressed) { @@ -1081,11 +1191,11 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HitTestTouchEvent( gfx::Point(touch_event.touches[i].PositionInWidget().x(), touch_event.touches[i].PositionInWidget().y()), &touch_action); - if (white_listed_touch_action && touch_action != cc::TouchAction::kAuto) { + if (allowed_touch_action && touch_action != cc::TouchAction::kAuto) { TRACE_EVENT_INSTANT1("input", "Adding TouchAction", TRACE_EVENT_SCOPE_THREAD, "TouchAction", cc::TouchActionToString(touch_action)); - *white_listed_touch_action &= touch_action; + *allowed_touch_action &= touch_action; } if (event_listener_type != @@ -1098,13 +1208,12 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HitTestTouchEvent( cc::InputHandler::TouchStartOrMoveEventListenerType:: HANDLER_ON_SCROLLING_LAYER; - // A non-passive touch start / move will always set the whitelisted touch + // 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 (white_listed_touch_action && - *white_listed_touch_action != cc::TouchAction::kNone) { - TRACE_EVENT_INSTANT0("input", - "NonBlocking due to whitelisted touchaction", + 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_HANDLE_NON_BLOCKING; } else { @@ -1176,9 +1285,9 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart( TRACE_EVENT0("input", "InputHandlerProxy::HandleTouchStart"); bool is_touching_scrolling_layer; - cc::TouchAction white_listed_touch_action = cc::TouchAction::kAuto; + cc::TouchAction allowed_touch_action = cc::TouchAction::kAuto; EventDisposition result = HitTestTouchEvent( - touch_event, &is_touching_scrolling_layer, &white_listed_touch_action); + touch_event, &is_touching_scrolling_layer, &allowed_touch_action); TRACE_EVENT_INSTANT1("input", "HitTest", TRACE_EVENT_SCOPE_THREAD, "disposition", result); @@ -1203,20 +1312,19 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart( // 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 a white_listed_touch_action that matches this non-blocking - // behavior rather than treating it as if it'll block. + // 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); - white_listed_touch_action = cc::TouchAction::kAuto; + allowed_touch_action = cc::TouchAction::kAuto; result = DID_NOT_HANDLE_NON_BLOCKING_DUE_TO_FLING; } - TRACE_EVENT_INSTANT2("input", "Whitelisted TouchAction", - TRACE_EVENT_SCOPE_THREAD, "TouchAction", - cc::TouchActionToString(white_listed_touch_action), - "disposition", result); - client_->SetWhiteListedTouchAction(white_listed_touch_action, - touch_event.unique_touch_event_id, result); + 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; } @@ -1232,15 +1340,14 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchMove( if (!touch_result_.has_value() || touch_event.touch_start_or_first_touch_move) { bool is_touching_scrolling_layer; - cc::TouchAction white_listed_touch_action = cc::TouchAction::kAuto; + cc::TouchAction allowed_touch_action = cc::TouchAction::kAuto; EventDisposition result = HitTestTouchEvent( - touch_event, &is_touching_scrolling_layer, &white_listed_touch_action); - TRACE_EVENT_INSTANT2("input", "Whitelisted TouchAction", - TRACE_EVENT_SCOPE_THREAD, "TouchAction", - cc::TouchActionToString(white_listed_touch_action), - "disposition", result); - client_->SetWhiteListedTouchAction( - white_listed_touch_action, touch_event.unique_touch_event_id, result); + 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(); @@ -1283,6 +1390,14 @@ void InputHandlerProxy::UpdateRootLayerStateForSynchronousInputHandler( 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(); diff --git a/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc b/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc index d6a6cdaa05c..b1a59a5035e 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc @@ -17,6 +17,7 @@ #include "base/test/task_environment.h" #include "base/test/trace_event_analyzer.h" #include "build/build_config.h" +#include "cc/base/features.h" #include "cc/input/main_thread_scrolling_reason.h" #include "cc/trees/swap_promise_monitor.h" #include "testing/gmock/include/gmock/gmock.h" @@ -42,30 +43,22 @@ using cc::InputHandler; using cc::ScrollBeginThreadState; using cc::TouchAction; using testing::_; +using testing::AllOf; using testing::DoAll; +using testing::Eq; using testing::Field; using testing::Mock; +using testing::NiceMock; +using testing::Property; using testing::Return; using testing::SetArgPointee; +using testing::StrictMock; namespace blink { namespace test { namespace { -enum InputHandlerProxyTestType { - ROOT_SCROLL_NORMAL_HANDLER, - ROOT_SCROLL_SYNCHRONOUS_HANDLER, - CHILD_SCROLL_NORMAL_HANDLER, - CHILD_SCROLL_SYNCHRONOUS_HANDLER, -}; -static const InputHandlerProxyTestType test_types[] = { - ROOT_SCROLL_NORMAL_HANDLER, - ROOT_SCROLL_SYNCHRONOUS_HANDLER, - CHILD_SCROLL_NORMAL_HANDLER, - CHILD_SCROLL_SYNCHRONOUS_HANDLER, -}; - MATCHER_P(WheelEventsMatch, expected, "") { return WheelEventsMatch(arg, expected); } @@ -221,11 +214,10 @@ class MockInputHandlerProxyClient : public InputHandlerProxyClient { const WebInputEventAttribution&)); void DispatchNonBlockingEventToMainThread( - std::unique_ptr<WebInputEvent> event, - const ui::LatencyInfo& latency_info, + std::unique_ptr<WebCoalescedInputEvent> event, const WebInputEventAttribution&) override { CHECK(event.get()); - DispatchNonBlockingEventToMainThread_(*event.get()); + DispatchNonBlockingEventToMainThread_(event->Event()); } MOCK_METHOD5(DidOverscroll, @@ -236,7 +228,7 @@ class MockInputHandlerProxyClient : public InputHandlerProxyClient { const cc::OverscrollBehavior& overscroll_behavior)); void DidAnimateForInput() override {} void DidStartScrollingViewport() override {} - MOCK_METHOD3(SetWhiteListedTouchAction, + MOCK_METHOD3(SetAllowedTouchAction, void(cc::TouchAction touch_action, uint32_t unique_touch_event_id, InputHandlerProxy::EventDisposition event_disposition)); @@ -271,6 +263,11 @@ const cc::InputHandler::ScrollStatus kImplThreadScrollState( cc::InputHandler::SCROLL_ON_IMPL_THREAD, cc::MainThreadScrollingReason::kNotScrollingOnMain); +const cc::InputHandler::ScrollStatus kRequiresMainThreadHitTestState( + cc::InputHandler::SCROLL_ON_IMPL_THREAD, + cc::MainThreadScrollingReason::kNotScrollingOnMain, + /*needs_main_thread_hit_test=*/true); + const cc::InputHandler::ScrollStatus kMainThreadScrollState( cc::InputHandler::SCROLL_ON_MAIN_THREAD, cc::MainThreadScrollingReason::kHandlingScrollFromMainThread); @@ -297,9 +294,9 @@ class TestInputHandlerProxy : public InputHandlerProxy { EventDisposition HitTestTouchEventForTest( const WebTouchEvent& touch_event, bool* is_touching_scrolling_layer, - cc::TouchAction* white_listed_touch_action) { + cc::TouchAction* allowed_touch_action) { return HitTestTouchEvent(touch_event, is_touching_scrolling_layer, - white_listed_touch_action); + allowed_touch_action); } EventDisposition HandleMouseWheelForTest( @@ -312,23 +309,41 @@ class TestInputHandlerProxy : public InputHandlerProxy { void DispatchQueuedInputEventsHelper() { DispatchQueuedInputEvents(); } }; +// Whether or not the input handler says that the viewport is scrolling the +// root scroller or a child. +enum class ScrollerType { kRoot, kChild }; + +// Whether or not to setup a synchronous input handler. This simulates the mode +// that WebView runs in. +enum class HandlerType { kNormal, kSynchronous }; + +// Run tests with unification both on and off. +enum class ScrollUnification { kEnabled, kDisabled }; + class InputHandlerProxyTest : public testing::Test, - public testing::WithParamInterface<InputHandlerProxyTestType> { + public testing::WithParamInterface< + std::tuple<ScrollerType, HandlerType, ScrollUnification>> { + ScrollerType GetScrollerType() { return std::get<0>(GetParam()); } + HandlerType GetHandlerType() { return std::get<1>(GetParam()); } + ScrollUnification GetScrollUnificationState() { + return std::get<2>(GetParam()); + } + public: - InputHandlerProxyTest() - : synchronous_root_scroll_(GetParam() == ROOT_SCROLL_SYNCHRONOUS_HANDLER), - install_synchronous_handler_( - GetParam() == ROOT_SCROLL_SYNCHRONOUS_HANDLER || - GetParam() == CHILD_SCROLL_SYNCHRONOUS_HANDLER), - expected_disposition_(InputHandlerProxy::DID_HANDLE) { + InputHandlerProxyTest() { + if (GetScrollUnificationState() == ScrollUnification::kEnabled) + scoped_feature_list_.InitAndEnableFeature(features::kScrollUnification); + else + scoped_feature_list_.InitAndDisableFeature(features::kScrollUnification); + input_handler_ = std::make_unique<TestInputHandlerProxy>( &mock_input_handler_, &mock_client_, /*force_input_to_main_thread=*/false); scroll_result_did_scroll_.did_scroll = true; scroll_result_did_not_scroll_.did_scroll = false; - if (install_synchronous_handler_) { + if (GetHandlerType() == HandlerType::kSynchronous) { EXPECT_CALL(mock_input_handler_, RequestUpdateForSynchronousInputHandler()) .Times(1); @@ -336,7 +351,9 @@ class InputHandlerProxyTest &mock_synchronous_input_handler_); } - mock_input_handler_.set_is_scrolling_root(synchronous_root_scroll_); + mock_input_handler_.set_is_scrolling_root( + GetHandlerType() == HandlerType::kSynchronous && + GetScrollerType() == ScrollerType::kRoot); // Set a default device so tests don't always have to set this. gesture_.SetSourceDevice(WebGestureDevice::kTouchpad); @@ -373,18 +390,18 @@ class InputHandlerProxyTest void GestureScrollIgnored(); void FlingAndSnap(); - const bool synchronous_root_scroll_; - const bool install_synchronous_handler_; testing::StrictMock<MockInputHandler> mock_input_handler_; testing::StrictMock<MockSynchronousInputHandler> mock_synchronous_input_handler_; std::unique_ptr<TestInputHandlerProxy> input_handler_; testing::StrictMock<MockInputHandlerProxyClient> mock_client_; WebGestureEvent gesture_; - InputHandlerProxy::EventDisposition expected_disposition_; + InputHandlerProxy::EventDisposition expected_disposition_ = + InputHandlerProxy::DID_HANDLE; base::HistogramTester histogram_tester_; cc::InputHandlerScrollResult scroll_result_did_scroll_; cc::InputHandlerScrollResult scroll_result_did_not_scroll_; + base::test::ScopedFeatureList scoped_feature_list_; }; // The helper basically returns the EventDisposition that is returned by @@ -395,16 +412,17 @@ class InputHandlerProxyTest InputHandlerProxy::EventDisposition HandleInputEventWithLatencyInfo( TestInputHandlerProxy* input_handler, const WebInputEvent& event) { - std::unique_ptr<WebInputEvent> scoped_input_event(event.Clone()); + std::unique_ptr<WebCoalescedInputEvent> scoped_input_event = + std::make_unique<WebCoalescedInputEvent>(event.Clone(), + ui::LatencyInfo()); InputHandlerProxy::EventDisposition event_disposition = InputHandlerProxy::DID_NOT_HANDLE; input_handler->HandleInputEventWithLatencyInfo( - std::move(scoped_input_event), ui::LatencyInfo(), + std::move(scoped_input_event), base::BindLambdaForTesting( [&event_disposition]( InputHandlerProxy::EventDisposition disposition, - std::unique_ptr<WebInputEvent> event, - const ui::LatencyInfo& latency_info, + std::unique_ptr<blink::WebCoalescedInputEvent> event, std::unique_ptr<InputHandlerProxy::DidOverscrollParams> callback, const WebInputEventAttribution& attribution) { event_disposition = disposition; @@ -419,16 +437,17 @@ InputHandlerProxy::EventDisposition HandleInputEventAndFlushEventQueue( const WebInputEvent& event) { EXPECT_CALL(mock_input_handler, SetNeedsAnimateInput()) .Times(testing::AnyNumber()); - std::unique_ptr<WebInputEvent> scoped_input_event(event.Clone()); + std::unique_ptr<WebCoalescedInputEvent> scoped_input_event = + std::make_unique<WebCoalescedInputEvent>(event.Clone(), + ui::LatencyInfo()); InputHandlerProxy::EventDisposition event_disposition = InputHandlerProxy::DID_NOT_HANDLE; input_handler->HandleInputEventWithLatencyInfo( - std::move(scoped_input_event), ui::LatencyInfo(), + std::move(scoped_input_event), base::BindLambdaForTesting( [&event_disposition]( InputHandlerProxy::EventDisposition disposition, - std::unique_ptr<WebInputEvent> event, - const ui::LatencyInfo& latency_info, + std::unique_ptr<blink::WebCoalescedInputEvent> event, std::unique_ptr<InputHandlerProxy::DidOverscrollParams> callback, const WebInputEventAttribution& attribution) { event_disposition = disposition; @@ -444,10 +463,6 @@ class InputHandlerProxyEventQueueTest : public testing::Test { : input_handler_proxy_(&mock_input_handler_, &mock_client_, /*force_input_to_main_thread=*/false) { - if (input_handler_proxy_.compositor_event_queue_) { - input_handler_proxy_.compositor_event_queue_ = - std::make_unique<CompositorThreadEventQueue>(); - } SetScrollPredictionEnabled(true); } @@ -472,9 +487,9 @@ class InputHandlerProxyEventQueueTest : public testing::Test { } void InjectInputEvent(std::unique_ptr<WebInputEvent> event) { - ui::LatencyInfo latency; input_handler_proxy_.HandleInputEventWithLatencyInfo( - std::move(event), latency, + std::make_unique<WebCoalescedInputEvent>(std::move(event), + ui::LatencyInfo()), base::BindOnce( &InputHandlerProxyEventQueueTest::DidHandleInputEventAndOverscroll, weak_ptr_factory_.GetWeakPtr())); @@ -491,12 +506,11 @@ class InputHandlerProxyEventQueueTest : public testing::Test { void DidHandleInputEventAndOverscroll( InputHandlerProxy::EventDisposition event_disposition, - std::unique_ptr<WebInputEvent> input_event, - const ui::LatencyInfo& latency_info, + std::unique_ptr<WebCoalescedInputEvent> input_event, std::unique_ptr<InputHandlerProxy::DidOverscrollParams> overscroll_params, const WebInputEventAttribution& attribution) { event_disposition_recorder_.push_back(event_disposition); - latency_info_recorder_.push_back(latency_info); + latency_info_recorder_.push_back(input_event->latency_info()); } base::circular_deque<std::unique_ptr<EventWithCallback>>& event_queue() { @@ -554,7 +568,7 @@ class InputHandlerProxyEventQueueTest : public testing::Test { // Tests that changing source devices mid gesture scroll is handled gracefully. // For example, when a touch scroll is in progress and the user initiates a // scrollbar scroll before the touch scroll has had a chance to dispatch a GSE. -TEST_P(InputHandlerProxyTest, NestedGestureBasedScrolls) { +TEST_P(InputHandlerProxyTest, NestedGestureBasedScrollsDifferentSourceDevice) { // Touchpad initiates a scroll. EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) .WillOnce(testing::Return(kImplThreadScrollState)); @@ -587,8 +601,10 @@ TEST_P(InputHandlerProxyTest, NestedGestureBasedScrolls) { cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(0); EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } WebMouseEvent mouse_event(WebInputEvent::Type::kMouseDown, WebInputEvent::kNoModifiers, WebInputEvent::GetStaticTimeStampForTests()); @@ -747,8 +763,10 @@ TEST_P(InputHandlerProxyTest, ScrollbarScrollEndOnDeviceChange) { cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(0); EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } WebMouseEvent mouse_event(WebInputEvent::Type::kMouseDown, WebInputEvent::kNoModifiers, WebInputEvent::GetStaticTimeStampForTests()); @@ -841,8 +859,10 @@ void InputHandlerProxyTest::GestureScrollStarted() { ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), _)) .WillOnce(testing::Return(scroll_result_did_not_scroll_)); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_EQ(expected_disposition_, HandleInputEventAndFlushEventQueue(mock_input_handler_, input_handler_.get(), gesture_)); @@ -851,8 +871,10 @@ void InputHandlerProxyTest::GestureScrollStarted() { expected_disposition_ = InputHandlerProxy::DID_HANDLE; VERIFY_AND_RESET_MOCKS(); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } gesture_.SetType(WebInputEvent::Type::kGestureScrollUpdate); gesture_.data.scroll_update.delta_y = -40; // -Y means scroll down - i.e. in the +Y direction. @@ -949,22 +971,31 @@ TEST_P(InputHandlerProxyTest, GestureScrollIgnored) { } TEST_P(InputHandlerProxyTest, GestureScrollByPage) { - // We should send all events to the widget for this gesture. - expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; + expected_disposition_ = InputHandlerProxy::DID_HANDLE; VERIFY_AND_RESET_MOCKS(); + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(testing::Return(kImplThreadScrollState)); + gesture_.SetType(WebInputEvent::Type::kGestureScrollBegin); gesture_.data.scroll_begin.delta_hint_units = ui::ScrollGranularity::kScrollByPage; EXPECT_CALL( mock_input_handler_, - RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnMain)) + RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); EXPECT_EQ(expected_disposition_, HandleInputEventWithLatencyInfo(input_handler_.get(), gesture_)); VERIFY_AND_RESET_MOCKS(); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)) + .WillOnce(testing::Return(scroll_result_did_scroll_)); + gesture_.SetType(WebInputEvent::Type::kGestureScrollUpdate); gesture_.data.scroll_update.delta_y = 1; gesture_.data.scroll_update.delta_units = @@ -974,6 +1005,7 @@ TEST_P(InputHandlerProxyTest, GestureScrollByPage) { VERIFY_AND_RESET_MOCKS(); + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(1); gesture_.SetType(WebInputEvent::Type::kGestureScrollEnd); gesture_.data.scroll_update.delta_y = 0; EXPECT_CALL(mock_input_handler_, RecordScrollEnd(_)).Times(1); @@ -1110,6 +1142,12 @@ TEST_P(InputHandlerProxyTest, GesturePinch) { } TEST_P(InputHandlerProxyTest, GesturePinchAfterScrollOnMainThread) { + // This situation is no longer possible under scroll unification as all + // scrolling now happens on the compositor thread. This test can be removed + // when the feature ships. + if (base::FeatureList::IsEnabled(features::kScrollUnification)) + return; + // Scrolls will start by being sent to the main thread. expected_disposition_ = InputHandlerProxy::DID_NOT_HANDLE; VERIFY_AND_RESET_MOCKS(); @@ -1157,8 +1195,10 @@ TEST_P(InputHandlerProxyTest, GesturePinchAfterScrollOnMainThread) { VERIFY_AND_RESET_MOCKS(); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } gesture_.SetType(WebInputEvent::Type::kGestureScrollUpdate); gesture_.data.scroll_update.delta_y = -40; // -Y means scroll down - i.e. in the +Y direction. @@ -1262,10 +1302,22 @@ void InputHandlerProxyTest::ScrollHandlingSwitchedToMainThread() { VERIFY_AND_RESET_MOCKS(); } TEST_P(InputHandlerProxyTest, WheelScrollHandlingSwitchedToMainThread) { + // This situation is no longer possible under scroll unification as all + // scrolling now happens on the compositor thread. This test can be removed + // when the feature ships. + if (base::FeatureList::IsEnabled(features::kScrollUnification)) + return; + gesture_.SetSourceDevice(WebGestureDevice::kTouchpad); ScrollHandlingSwitchedToMainThread(); } TEST_P(InputHandlerProxyTest, TouchScrollHandlingSwitchedToMainThread) { + // This situation is no longer possible under scroll unification as all + // scrolling now happens on the compositor thread. This test can be removed + // when the feature ships. + if (base::FeatureList::IsEnabled(features::kScrollUnification)) + return; + gesture_.SetSourceDevice(WebGestureDevice::kTouchscreen); ScrollHandlingSwitchedToMainThread(); } @@ -1372,17 +1424,92 @@ TEST_P(InputHandlerProxyTest, HitTestTouchEventNonNullTouchAction) { CreateWebTouchPoint(WebTouchPoint::State::kStatePressed, -10, 10); bool is_touching_scrolling_layer; - cc::TouchAction white_listed_touch_action = cc::TouchAction::kAuto; - EXPECT_EQ(expected_disposition_, input_handler_->HitTestTouchEventForTest( - touch, &is_touching_scrolling_layer, - &white_listed_touch_action)); + cc::TouchAction allowed_touch_action = cc::TouchAction::kAuto; + EXPECT_EQ(expected_disposition_, + input_handler_->HitTestTouchEventForTest( + touch, &is_touching_scrolling_layer, &allowed_touch_action)); EXPECT_TRUE(is_touching_scrolling_layer); - EXPECT_EQ(white_listed_touch_action, cc::TouchAction::kPanUp); + EXPECT_EQ(allowed_touch_action, cc::TouchAction::kPanUp); VERIFY_AND_RESET_MOCKS(); } -// Tests that the whitelisted touch action is correctly set when a touch is -// made non blocking due to an ongoing fling. https://crbug.com/1048098. +// Tests that multiple mousedown(s) on scrollbar are handled gracefully and +// don't fail any DCHECK(s). +TEST_F(InputHandlerProxyEventQueueTest, + NestedGestureBasedScrollsSameSourceDevice) { + // Start with mousedown. Expect CompositorThreadEventQueue to contain [GSB, + // GSU]. + EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()); + HandleMouseEvent(WebInputEvent::Type::kMouseDown); + EXPECT_EQ(2ul, event_queue().size()); + EXPECT_EQ(event_queue()[0]->event().GetType(), + WebInputEvent::Type::kGestureScrollBegin); + EXPECT_EQ(event_queue()[1]->event().GetType(), + WebInputEvent::Type::kGestureScrollUpdate); + + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kImplThreadScrollState)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(Return(false)); + } + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(1); + + DeliverInputForBeginFrame(); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // A mouseup adds a GSE to the CompositorThreadEventQueue. + EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()); + HandleMouseEvent(WebInputEvent::Type::kMouseUp); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + + EXPECT_EQ(1ul, event_queue().size()); + EXPECT_EQ(event_queue()[0]->event().GetType(), + WebInputEvent::Type::kGestureScrollEnd); + + // Called when a mousedown is being handled as it tries to end the ongoing + // scroll. + EXPECT_CALL(mock_input_handler_, RecordScrollEnd(_)).Times(1); + EXPECT_CALL(mock_input_handler_, ScrollEnd(true)).Times(1); + + // A mousedown occurs on the scrollbar *before* the GSE is dispatched. + HandleMouseEvent(WebInputEvent::Type::kMouseDown); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + + EXPECT_EQ(3ul, event_queue().size()); + EXPECT_EQ(event_queue()[1]->event().GetType(), + WebInputEvent::Type::kGestureScrollBegin); + EXPECT_EQ(event_queue()[2]->event().GetType(), + WebInputEvent::Type::kGestureScrollUpdate); + + // Called when the GSE is being handled. (Note that ScrollEnd isn't called + // when the GSE is being handled as the GSE gets dropped in + // HandleGestureScrollEnd because handling_gesture_on_impl_thread_ is false) + EXPECT_CALL(mock_input_handler_, RecordScrollEnd(_)).Times(1); + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kImplThreadScrollState)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(Return(false)); + } + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(1); + + DeliverInputForBeginFrame(); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + + // Finally, a mouseup ends the scroll. + EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()); + HandleMouseEvent(WebInputEvent::Type::kMouseUp); + + EXPECT_CALL(mock_input_handler_, RecordScrollEnd(_)).Times(1); + EXPECT_CALL(mock_input_handler_, ScrollEnd(true)).Times(1); + + DeliverInputForBeginFrame(); + Mock::VerifyAndClearExpectations(&mock_input_handler_); +} + +// Tests that the allowed touch action is correctly set when a touch is made +// non-blocking due to an ongoing fling. https://crbug.com/1048098. TEST_F(InputHandlerProxyEventQueueTest, AckTouchActionNonBlockingForFling) { // Simulate starting a compositor scroll and then flinging. This is setup for // the real checks below. @@ -1405,8 +1532,10 @@ TEST_F(InputHandlerProxyEventQueueTest, AckTouchActionNonBlockingForFling) { // ScrollUpdate { EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(Return(false)); + } EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(1); HandleGestureEvent(WebInputEvent::Type::kGestureScrollUpdate, delta); @@ -1421,8 +1550,10 @@ TEST_F(InputHandlerProxyEventQueueTest, AckTouchActionNonBlockingForFling) { EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)) .WillOnce(Return(scroll_result_did_scroll)); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(Return(false)); + } EXPECT_CALL(mock_input_handler_, GetSnapFlingInfoAndSetAnimatingSnapTarget(_, _, _)) .WillOnce(Return(false)); @@ -1443,9 +1574,9 @@ TEST_F(InputHandlerProxyEventQueueTest, AckTouchActionNonBlockingForFling) { // the screen. If this touch hits a blocking region (e.g. touch-action or a // non-passive touchstart listener), we won't actually treat it as blocking; // because of the ongoing fling it will be treated as non blocking. However, - // we also have to ensure that the whitelisted_touch_action reported is also - // kAuto so that the browser knows that it shouldn't wait for an ACK with an - // allowed touch-action before dispatching more scrolls. + // we also have to ensure that the allowed_touch_action reported is also kAuto + // so that the browser knows that it shouldn't wait for an ACK with an allowed + // touch-action before dispatching more scrolls. { // Simulate hitting a blocking region on the scrolling layer, as if there // was a non-passive touchstart handler. @@ -1465,12 +1596,11 @@ TEST_F(InputHandlerProxyEventQueueTest, AckTouchActionNonBlockingForFling) { CreateWebTouchPoint(WebTouchPoint::State::kStatePressed, 10, 10); // This is the call this test is checking: we expect that the client will - // report the touch as non-blocking and also that the whitelisted touch - // action matches the non blocking expectatithe whitelisted touch action + // report the touch as non-blocking and also that the allowed touch action // matches the non blocking expectation (i.e. all touches are allowed). EXPECT_CALL( mock_client_, - SetWhiteListedTouchAction( + SetAllowedTouchAction( TouchAction::kAuto, touch_start->unique_touch_event_id, InputHandlerProxy::DID_NOT_HANDLE_NON_BLOCKING_DUE_TO_FLING)) .WillOnce(Return()); @@ -1513,12 +1643,12 @@ TEST_P(InputHandlerProxyTest, HitTestTouchEventNullTouchAction) { CreateWebTouchPoint(WebTouchPoint::State::kStatePressed, -10, 10); bool is_touching_scrolling_layer; - cc::TouchAction* white_listed_touch_action = nullptr; - EXPECT_EQ(expected_disposition_, input_handler_->HitTestTouchEventForTest( - touch, &is_touching_scrolling_layer, - white_listed_touch_action)); + cc::TouchAction* allowed_touch_action = nullptr; + EXPECT_EQ(expected_disposition_, + input_handler_->HitTestTouchEventForTest( + touch, &is_touching_scrolling_layer, allowed_touch_action)); EXPECT_TRUE(is_touching_scrolling_layer); - EXPECT_TRUE(!white_listed_touch_action); + EXPECT_TRUE(!allowed_touch_action); VERIFY_AND_RESET_MOCKS(); } @@ -1544,8 +1674,8 @@ TEST_P(InputHandlerProxyTest, MultiTouchPointHitTestNegative) { return cc::InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER; })); EXPECT_CALL(mock_client_, - SetWhiteListedTouchAction(cc::TouchAction::kPanUp, 1, - InputHandlerProxy::DROP_EVENT)) + SetAllowedTouchAction(cc::TouchAction::kPanUp, 1, + InputHandlerProxy::DROP_EVENT)) .WillOnce(testing::Return()); WebTouchEvent touch(WebInputEvent::Type::kTouchStart, @@ -1590,8 +1720,8 @@ TEST_P(InputHandlerProxyTest, MultiTouchPointHitTestPositive) { return cc::InputHandler::TouchStartOrMoveEventListenerType:: HANDLER_ON_SCROLLING_LAYER; })); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(cc::TouchAction::kPanY, 1, - expected_disposition_)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(cc::TouchAction::kPanY, 1, + expected_disposition_)) .WillOnce(testing::Return()); // Since the second touch point hits a touch-region, there should be no // hit-testing for the third touch point. @@ -1637,9 +1767,9 @@ TEST_P(InputHandlerProxyTest, MultiTouchPointHitTestPassivePositive) { *touch_action = cc::TouchAction::kPanX; return cc::InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER; })); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction( - cc::TouchAction::kPanRight, 1, - InputHandlerProxy::DID_HANDLE_NON_BLOCKING)) + EXPECT_CALL(mock_client_, + SetAllowedTouchAction(cc::TouchAction::kPanRight, 1, + InputHandlerProxy::DID_HANDLE_NON_BLOCKING)) .WillOnce(testing::Return()); WebTouchEvent touch(WebInputEvent::Type::kTouchStart, @@ -1682,9 +1812,9 @@ TEST_P(InputHandlerProxyTest, TouchStartPassiveAndTouchEndBlocking) { *touch_action = cc::TouchAction::kNone; return cc::InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER; })); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction( - cc::TouchAction::kNone, 1, - InputHandlerProxy::DID_HANDLE_NON_BLOCKING)) + EXPECT_CALL(mock_client_, + SetAllowedTouchAction(cc::TouchAction::kNone, 1, + InputHandlerProxy::DID_HANDLE_NON_BLOCKING)) .WillOnce(testing::Return()); WebTouchEvent touch(WebInputEvent::Type::kTouchStart, @@ -1721,7 +1851,7 @@ TEST_P(InputHandlerProxyTest, TouchMoveBlockingAddedAfterPassiveTouchStart) { EXPECT_CALL(mock_input_handler_, EventListenerTypeForTouchStartOrMoveAt(_, _)) .WillOnce(testing::Return( cc::InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER)); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(_, _, _)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(_, _, _)) .WillOnce(testing::Return()); WebTouchEvent touch(WebInputEvent::Type::kTouchStart, @@ -1737,7 +1867,7 @@ TEST_P(InputHandlerProxyTest, TouchMoveBlockingAddedAfterPassiveTouchStart) { EXPECT_CALL(mock_input_handler_, EventListenerTypeForTouchStartOrMoveAt(_, _)) .WillOnce(testing::Return( cc::InputHandler::TouchStartOrMoveEventListenerType::HANDLER)); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(_, _, _)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(_, _, _)) .WillOnce(testing::Return()); touch.SetType(WebInputEvent::Type::kTouchMove); @@ -1750,6 +1880,487 @@ TEST_P(InputHandlerProxyTest, TouchMoveBlockingAddedAfterPassiveTouchStart) { VERIFY_AND_RESET_MOCKS(); } +class UnifiedScrollingInputHandlerProxyTest : public testing::Test { + public: + using ElementId = cc::ElementId; + using ElementIdType = cc::ElementIdType; + using EventDisposition = InputHandlerProxy::EventDisposition; + using EventDispositionCallback = InputHandlerProxy::EventDispositionCallback; + using LatencyInfo = ui::LatencyInfo; + using ScrollGranularity = ui::ScrollGranularity; + using ScrollState = cc::ScrollState; + using ReturnedDisposition = base::Optional<EventDisposition>; + + UnifiedScrollingInputHandlerProxyTest() + : input_handler_proxy_(&mock_input_handler_, + &mock_client_, + /*force_input_to_main_thread=*/false) {} + + void SetUp() override { + scoped_feature_list_.InitAndEnableFeature(features::kScrollUnification); + } + + std::unique_ptr<WebCoalescedInputEvent> ScrollBegin() { + auto gsb = std::make_unique<WebGestureEvent>( + WebInputEvent::Type::kGestureScrollBegin, WebInputEvent::kNoModifiers, + TimeForInputEvents(), WebGestureDevice::kTouchpad); + gsb->data.scroll_begin.scrollable_area_element_id = 0; + gsb->data.scroll_begin.main_thread_hit_tested = false; + ; + gsb->data.scroll_begin.delta_x_hint = 0; + gsb->data.scroll_begin.delta_y_hint = 10; + gsb->data.scroll_begin.pointer_count = 0; + + LatencyInfo unused; + return std::make_unique<WebCoalescedInputEvent>(std::move(gsb), unused); + } + + std::unique_ptr<WebCoalescedInputEvent> ScrollUpdate() { + auto gsu = std::make_unique<WebGestureEvent>( + WebInputEvent::Type::kGestureScrollUpdate, WebInputEvent::kNoModifiers, + TimeForInputEvents(), WebGestureDevice::kTouchpad); + gsu->data.scroll_update.delta_x = 0; + gsu->data.scroll_update.delta_y = 10; + + LatencyInfo unused; + return std::make_unique<WebCoalescedInputEvent>(std::move(gsu), unused); + } + + std::unique_ptr<WebCoalescedInputEvent> ScrollEnd() { + auto gse = std::make_unique<WebGestureEvent>( + WebInputEvent::Type::kGestureScrollEnd, WebInputEvent::kNoModifiers, + TimeForInputEvents(), WebGestureDevice::kTouchpad); + + LatencyInfo unused; + return std::make_unique<WebCoalescedInputEvent>(std::move(gse), unused); + } + + void DispatchEvent(std::unique_ptr<blink::WebCoalescedInputEvent> event, + ReturnedDisposition* out_disposition = nullptr) { + input_handler_proxy_.HandleInputEventWithLatencyInfo( + std::move(event), BindEventHandledCallback(out_disposition)); + } + + void ContinueScrollBeginAfterMainThreadHitTest( + std::unique_ptr<WebCoalescedInputEvent> event, + cc::ElementIdType hit_test_result, + ReturnedDisposition* out_disposition = nullptr) { + input_handler_proxy_.ContinueScrollBeginAfterMainThreadHitTest( + std::move(event), BindEventHandledCallback(out_disposition), + hit_test_result); + } + + bool MainThreadHitTestInProgress() const { + return input_handler_proxy_.hit_testing_scroll_begin_on_main_thread_; + } + + void BeginFrame() { + constexpr base::TimeDelta interval = base::TimeDelta::FromMilliseconds(16); + base::TimeTicks frame_time = + TimeForInputEvents() + + (next_begin_frame_number_ - viz::BeginFrameArgs::kStartingFrameNumber) * + interval; + input_handler_proxy_.DeliverInputForBeginFrame(viz::BeginFrameArgs::Create( + BEGINFRAME_FROM_HERE, 0, next_begin_frame_number_++, frame_time, + frame_time + interval, interval, viz::BeginFrameArgs::NORMAL)); + } + + cc::InputHandlerScrollResult DidScrollResult() const { + cc::InputHandlerScrollResult result; + result.did_scroll = true; + return result; + } + + protected: + NiceMock<MockInputHandler> mock_input_handler_; + NiceMock<MockInputHandlerProxyClient> mock_client_; + + private: + void EventHandledCallback( + ReturnedDisposition* out_disposition, + EventDisposition event_disposition, + std::unique_ptr<WebCoalescedInputEvent> input_event, + std::unique_ptr<InputHandlerProxy::DidOverscrollParams> overscroll_params, + const WebInputEventAttribution& attribution) { + if (out_disposition) + *out_disposition = event_disposition; + } + + EventDispositionCallback BindEventHandledCallback( + ReturnedDisposition* out_disposition = nullptr) { + return base::BindOnce( + &UnifiedScrollingInputHandlerProxyTest::EventHandledCallback, + weak_ptr_factory_.GetWeakPtr(), out_disposition); + } + + base::TimeTicks TimeForInputEvents() const { + return WebInputEvent::GetStaticTimeStampForTests(); + } + + InputHandlerProxy input_handler_proxy_; + base::test::ScopedFeatureList scoped_feature_list_; + base::SimpleTestTickClock tick_clock_; + uint64_t next_begin_frame_number_ = viz::BeginFrameArgs::kStartingFrameNumber; + base::WeakPtrFactory<UnifiedScrollingInputHandlerProxyTest> weak_ptr_factory_{ + this}; +}; + +// Test that when a main thread hit test is requested, the InputHandlerProxy +// starts queueing incoming gesture event and the compositor queue is blocked +// until the hit test is satisfied. +TEST_F(UnifiedScrollingInputHandlerProxyTest, MainThreadHitTestRequired) { + // The hit testing state shouldn't be entered until one is actually requested. + EXPECT_FALSE(MainThreadHitTestInProgress()); + + // Inject a GSB that returns RequiresMainThreadHitTest. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kRequiresMainThreadHitTestState)); + + ReturnedDisposition disposition; + DispatchEvent(ScrollBegin(), &disposition); + + EXPECT_TRUE(MainThreadHitTestInProgress()); + EXPECT_EQ(InputHandlerProxy::REQUIRES_MAIN_THREAD_HIT_TEST, *disposition); + + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + ReturnedDisposition gsu1_disposition; + ReturnedDisposition gsu2_disposition; + + // Now inject a GSU. This should be queued. + { + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(0); + + DispatchEvent(ScrollUpdate(), &gsu1_disposition); + EXPECT_FALSE(gsu1_disposition); + + // Ensure the queue is blocked; a BeginFrame doesn't cause event dispatch. + BeginFrame(); + EXPECT_FALSE(gsu1_disposition); + + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // Inject a second GSU; it should be coalesced and also queued. + { + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(0); + + DispatchEvent(ScrollUpdate(), &gsu2_disposition); + EXPECT_FALSE(gsu2_disposition); + + // Ensure the queue is blocked. + BeginFrame(); + EXPECT_FALSE(gsu2_disposition); + + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + EXPECT_TRUE(MainThreadHitTestInProgress()); + + // The hit test reply arrives. Ensure we call ScrollBegin and unblock the + // queue. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kImplThreadScrollState)); + + // Additionally, the queue should be flushed by + // ContinueScrollBeginAfterMainThreadHitTest so that the GSUs dispatched + // earlier will now handled. + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)) + .WillOnce(Return(DidScrollResult())); + + // Ensure we don't spurriously call ScrollEnd (because we think we're + // already in a scroll from the first GSB). + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(0); + + ReturnedDisposition disposition; + const ElementIdType kHitTestResult = 12345; + ContinueScrollBeginAfterMainThreadHitTest(ScrollBegin(), kHitTestResult, + &disposition); + + // The ScrollBegin should have been immediately re-injected and queue + // flushed. + EXPECT_FALSE(MainThreadHitTestInProgress()); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gsu1_disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gsu2_disposition); + + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // Injecting a new GSU should cause queueing and dispatching as usual. + { + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)) + .WillOnce(Return(DidScrollResult())); + + ReturnedDisposition disposition; + DispatchEvent(ScrollUpdate(), &disposition); + EXPECT_FALSE(disposition); + + BeginFrame(); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *disposition); + + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // Finish the scroll. + { + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(1); + ReturnedDisposition disposition; + DispatchEvent(ScrollEnd(), &disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *disposition); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + EXPECT_FALSE(MainThreadHitTestInProgress()); +} + +// Test to ensure that a main thread hit test sets the correct flags on the +// re-injected GestureScrollBegin. +TEST_F(UnifiedScrollingInputHandlerProxyTest, MainThreadHitTestEvent) { + // Inject a GSB that returns RequiresMainThreadHitTest. + { + // Ensure that by default we don't set a target. The + // |is_main_thread_hit_tested| property should default to false. + EXPECT_CALL( + mock_input_handler_, + ScrollBegin( + AllOf(Property(&ScrollState::target_element_id, Eq(ElementId())), + Property(&ScrollState::is_main_thread_hit_tested, Eq(false))), + _)) + .WillOnce(Return(kRequiresMainThreadHitTestState)); + DispatchEvent(ScrollBegin()); + ASSERT_TRUE(MainThreadHitTestInProgress()); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // The hit test reply arrives. Ensure we call ScrollBegin with the ElementId + // from the hit test and the main_thread + { + const ElementId kHitTestResult(12345); + + EXPECT_CALL( + mock_input_handler_, + ScrollBegin( + AllOf(Property(&ScrollState::target_element_id, Eq(kHitTestResult)), + Property(&ScrollState::is_main_thread_hit_tested, Eq(true))), + _)) + .Times(1); + + ContinueScrollBeginAfterMainThreadHitTest(ScrollBegin(), + kHitTestResult.GetStableId()); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } +} + +// Test to ensure that a main thread hit test counts the correct number of +// scrolls for metrics. +TEST_F(UnifiedScrollingInputHandlerProxyTest, MainThreadHitTestMetrics) { + // Inject a GSB that returns RequiresMainThreadHitTest followed by a GSU and + // a GSE. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kRequiresMainThreadHitTestState)) + .WillOnce(Return(kImplThreadScrollState)); + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(1); + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(1); + + // The record begin/end should be called exactly once. + EXPECT_CALL(mock_input_handler_, RecordScrollBegin(_, _)).Times(1); + EXPECT_CALL(mock_input_handler_, RecordScrollEnd(_)).Times(1); + + DispatchEvent(ScrollBegin()); + EXPECT_TRUE(MainThreadHitTestInProgress()); + DispatchEvent(ScrollUpdate()); + DispatchEvent(ScrollEnd()); + + // Hit test reply. + const ElementIdType kHitTestResult = 12345; + ContinueScrollBeginAfterMainThreadHitTest(ScrollBegin(), kHitTestResult); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // Ensure we don't record either a begin or an end if the hit test fails. + // TODO(bokan): Though it looks odd, it appears that today we do record the + // scrolling thread if the scroll is dropped. We should fix that but in the + // mean-time we add a test for the unified path in this case. + // https://crbug.com/1082601. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kRequiresMainThreadHitTestState)); + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(0); + + EXPECT_CALL(mock_input_handler_, RecordScrollBegin(_, _)).Times(1); + EXPECT_CALL(mock_input_handler_, RecordScrollEnd(_)).Times(1); + + DispatchEvent(ScrollBegin()); + EXPECT_TRUE(MainThreadHitTestInProgress()); + DispatchEvent(ScrollUpdate()); + DispatchEvent(ScrollEnd()); + + // Hit test reply failed. + const ElementIdType kHitTestResult = 0; + ASSERT_FALSE(ElementId::IsValid(kHitTestResult)); + + ContinueScrollBeginAfterMainThreadHitTest(ScrollBegin(), kHitTestResult); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } +} + +// Test the case where a main thread hit test is in progress on the main thread +// and a GSE and new GSB arrive. +TEST_F(UnifiedScrollingInputHandlerProxyTest, + ScrollEndAndBeginsDuringMainThreadHitTest) { + ReturnedDisposition gsb1_disposition; + ReturnedDisposition gsu1_disposition; + ReturnedDisposition gse1_disposition; + ReturnedDisposition gsb2_disposition; + ReturnedDisposition gsu2_disposition; + ReturnedDisposition gse2_disposition; + + // Inject a GSB that returns RequiresMainThreadHitTest followed by a GSU and + // GSE that get queued. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kRequiresMainThreadHitTestState)); + DispatchEvent(ScrollBegin(), &gsb1_disposition); + ASSERT_TRUE(MainThreadHitTestInProgress()); + ASSERT_EQ(InputHandlerProxy::REQUIRES_MAIN_THREAD_HIT_TEST, + *gsb1_disposition); + + DispatchEvent(ScrollUpdate(), &gsu1_disposition); + DispatchEvent(ScrollEnd(), &gse1_disposition); + + // The queue is blocked so none of the events should be processed. + BeginFrame(); + + ASSERT_FALSE(gsu1_disposition); + ASSERT_FALSE(gse1_disposition); + } + + // Inject another group of GSB, GSU, GSE. They should all be queued. + { + DispatchEvent(ScrollBegin(), &gsb2_disposition); + DispatchEvent(ScrollUpdate(), &gsu2_disposition); + DispatchEvent(ScrollEnd(), &gse2_disposition); + + // The queue is blocked so none of the events should be processed. + BeginFrame(); + + EXPECT_FALSE(gsb2_disposition); + EXPECT_FALSE(gsu2_disposition); + EXPECT_FALSE(gse2_disposition); + } + + ASSERT_TRUE(MainThreadHitTestInProgress()); + + // The hit test reply arrives. Ensure we call ScrollBegin and unblock the + // queue. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .Times(2) + .WillRepeatedly(Return(kImplThreadScrollState)); + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)) + .Times(2) + .WillRepeatedly(Return(DidScrollResult())); + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(2); + + ReturnedDisposition disposition; + const ElementIdType kHitTestResult = 12345; + ContinueScrollBeginAfterMainThreadHitTest(ScrollBegin(), kHitTestResult, + &disposition); + + // The ScrollBegin should have been immediately re-injected and queue + // flushed. + EXPECT_FALSE(MainThreadHitTestInProgress()); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gsu1_disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gse1_disposition); + + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gsb2_disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gsu2_disposition); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *gse2_disposition); + + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } +} + +// Test the case where a main thread hit test returns a null element_id. In +// this case we should reset the state and unblock the queue. +TEST_F(UnifiedScrollingInputHandlerProxyTest, MainThreadHitTestFailed) { + ReturnedDisposition gsu1_disposition; + + // Inject a GSB that returns RequiresMainThreadHitTest. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kRequiresMainThreadHitTestState)); + DispatchEvent(ScrollBegin()); + DispatchEvent(ScrollUpdate(), &gsu1_disposition); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // The hit test reply arrives with an invalid ElementId. We shouldn't call + // ScrollBegin nor ScrollUpdate. Both should be dropped without reaching the + // input handler. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(0); + EXPECT_CALL(mock_input_handler_, ScrollEnd(_)).Times(0); + + const ElementIdType kHitTestResult = 0; + ASSERT_FALSE(ElementId::IsValid(kHitTestResult)); + + ReturnedDisposition gsb_disposition; + ContinueScrollBeginAfterMainThreadHitTest(ScrollBegin(), kHitTestResult, + &gsb_disposition); + + EXPECT_EQ(InputHandlerProxy::DROP_EVENT, *gsb_disposition); + EXPECT_EQ(InputHandlerProxy::DROP_EVENT, *gsu1_disposition); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // Send a new GSU, ensure it's dropped without queueing since there's no + // scroll in progress. + { + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)).Times(0); + + ReturnedDisposition disposition; + DispatchEvent(ScrollUpdate(), &disposition); + EXPECT_EQ(InputHandlerProxy::DROP_EVENT, *disposition); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } + + // Ensure there's no left-over bad state by sending a new GSB+GSU which + // should be handled by the input handler immediately. A following GSU should + // be queued and dispatched at BeginFrame. + { + EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _)) + .WillOnce(Return(kImplThreadScrollState)); + EXPECT_CALL(mock_input_handler_, ScrollUpdate(_, _)) + .WillOnce(Return(DidScrollResult())) + .WillOnce(Return(DidScrollResult())); + + // Note: The first GSU after a GSB is dispatched immediately without + // queueing. + ReturnedDisposition disposition; + DispatchEvent(ScrollBegin(), &disposition); + DispatchEvent(ScrollUpdate()); + + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *disposition); + disposition = base::nullopt; + + DispatchEvent(ScrollUpdate(), &disposition); + EXPECT_FALSE(disposition); + + BeginFrame(); + EXPECT_EQ(InputHandlerProxy::DID_HANDLE, *disposition); + Mock::VerifyAndClearExpectations(&mock_input_handler_); + } +} + TEST(SynchronousInputHandlerProxyTest, StartupShutdown) { testing::StrictMock<MockInputHandler> mock_input_handler; testing::StrictMock<MockInputHandlerProxyClient> mock_client; @@ -1886,8 +2497,10 @@ TEST_F(InputHandlerProxyEventQueueTest, VSyncAlignedGestureScroll) { EXPECT_EQ(1ul, event_disposition_recorder_.size()); testing::Mock::VerifyAndClearExpectations(&mock_input_handler_); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -1930,8 +2543,10 @@ TEST_F(InputHandlerProxyEventQueueTest, mock_input_handler_, RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -1958,8 +2573,10 @@ TEST_F(InputHandlerProxyEventQueueTest, mock_input_handler_, RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillRepeatedly(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillRepeatedly(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2011,8 +2628,10 @@ TEST_F(InputHandlerProxyEventQueueTest, VSyncAlignedQueueingTime) { RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2151,8 +2770,10 @@ TEST_F(InputHandlerProxyEventQueueTest, OriginalEventsTracing) { .Times(2); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()) .Times(::testing::AtLeast(1)); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillRepeatedly(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillRepeatedly(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2236,8 +2857,10 @@ TEST_F(InputHandlerProxyEventQueueTest, TouchpadGestureScrollEndFlushQueue) { mock_input_handler_, RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(2); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillRepeatedly(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillRepeatedly(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2304,8 +2927,10 @@ TEST_F(InputHandlerProxyEventQueueTest, CoalescedLatencyInfo) { RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(1); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2346,8 +2971,10 @@ TEST_F(InputHandlerProxyEventQueueTest, CoalescedEventSwitchToMainThread) { RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnMain)) .Times(1); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(2); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2416,8 +3043,10 @@ TEST_F(InputHandlerProxyEventQueueTest, ScrollPredictorTest) { RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(2); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillOnce(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillOnce(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2475,8 +3104,10 @@ TEST_F(InputHandlerProxyEventQueueTest, DeliverInputWithHighLatencyMode) { RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnCompositor)) .Times(1); EXPECT_CALL(mock_input_handler_, SetNeedsAnimateInput()).Times(2); - EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) - .WillRepeatedly(testing::Return(false)); + if (!base::FeatureList::IsEnabled(features::kScrollUnification)) { + EXPECT_CALL(mock_input_handler_, ScrollingShouldSwitchtoMainThread()) + .WillRepeatedly(testing::Return(false)); + } EXPECT_CALL( mock_input_handler_, ScrollUpdate(testing::Property(&cc::ScrollState::delta_y, testing::Gt(0)), @@ -2574,7 +3205,7 @@ TEST_P(InputHandlerProxyMainThreadScrollingReasonTest, mock_input_handler_, GetEventListenerProperties(cc::EventListenerClass::kTouchStartOrMove)) .WillOnce(testing::Return(cc::EventListenerProperties::kPassive)); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(_, _, _)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(_, _, _)) .WillOnce(testing::Return()); expected_disposition_ = InputHandlerProxy::DID_HANDLE_NON_BLOCKING; @@ -2621,7 +3252,7 @@ TEST_P(InputHandlerProxyMainThreadScrollingReasonTest, .WillOnce( testing::Return(cc::InputHandler::TouchStartOrMoveEventListenerType:: HANDLER_ON_SCROLLING_LAYER)); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(_, _, _)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(_, _, _)) .WillOnce(testing::Return()); expected_disposition_ = InputHandlerProxy::DID_HANDLE_NON_BLOCKING; @@ -2673,7 +3304,7 @@ TEST_P(InputHandlerProxyMainThreadScrollingReasonTest, .WillOnce( testing::Return(cc::InputHandler::TouchStartOrMoveEventListenerType:: HANDLER_ON_SCROLLING_LAYER)); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(_, _, _)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(_, _, _)) .WillOnce(testing::Return()); expected_disposition_ = InputHandlerProxy::DID_HANDLE_NON_BLOCKING; @@ -2721,7 +3352,7 @@ TEST_P(InputHandlerProxyMainThreadScrollingReasonTest, testing::Property(&gfx::Point::x, testing::Gt(0)), _)) .WillOnce(testing::Return( cc::InputHandler::TouchStartOrMoveEventListenerType::NO_HANDLER)); - EXPECT_CALL(mock_client_, SetWhiteListedTouchAction(_, _, _)) + EXPECT_CALL(mock_client_, SetAllowedTouchAction(_, _, _)) .WillOnce(testing::Return()); EXPECT_CALL(mock_input_handler_, GetEventListenerProperties(_)) .WillRepeatedly(testing::Return(cc::EventListenerProperties::kPassive)); @@ -3126,9 +3757,10 @@ class InputHandlerProxyMomentumScrollJankTest : public testing::Test { protected: void HandleGesture(std::unique_ptr<WebInputEvent> event) { - ui::LatencyInfo latency; input_handler_proxy_.HandleInputEventWithLatencyInfo( - std::move(event), latency, base::DoNothing()); + std::make_unique<WebCoalescedInputEvent>(std::move(event), + ui::LatencyInfo()), + base::DoNothing()); } uint64_t next_begin_frame_number_ = viz::BeginFrameArgs::kStartingFrameNumber; @@ -3372,12 +4004,33 @@ TEST_F(InputHandlerProxyMomentumScrollJankTest, TestNonMomentumNoJank) { 0); } -INSTANTIATE_TEST_SUITE_P(AnimateInput, +const auto kTestCombinations = testing::Combine( + testing::Values(ScrollerType::kRoot, ScrollerType::kChild), + testing::Values(HandlerType::kNormal, HandlerType::kSynchronous), + testing::Values(ScrollUnification::kEnabled, ScrollUnification::kDisabled)); + +const auto kSuffixGenerator = + [](const testing::TestParamInfo< + std::tuple<ScrollerType, HandlerType, ScrollUnification>>& info) { + std::string name = std::get<1>(info.param) == HandlerType::kSynchronous + ? "Synchronous" + : ""; + name += std::get<0>(info.param) == ScrollerType::kRoot ? "Root" : "Child"; + name += std::get<2>(info.param) == ScrollUnification::kEnabled + ? "UnifiedScroll" + : "LegacyScroll"; + return name; + }; + +INSTANTIATE_TEST_SUITE_P(All, InputHandlerProxyTest, - testing::ValuesIn(test_types)); + kTestCombinations, + kSuffixGenerator); -INSTANTIATE_TEST_SUITE_P(AnimateInput, +INSTANTIATE_TEST_SUITE_P(All, InputHandlerProxyMainThreadScrollingReasonTest, - testing::ValuesIn(test_types)); + kTestCombinations, + kSuffixGenerator); + } // namespace test } // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/widget/input/input_scroll_elasticity_controller_unittest.cc b/chromium/third_party/blink/renderer/platform/widget/input/input_scroll_elasticity_controller_unittest.cc index a357bd2852e..ff1abf50eaf 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/input_scroll_elasticity_controller_unittest.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/input_scroll_elasticity_controller_unittest.cc @@ -46,6 +46,8 @@ class MockScrollElasticityHelper : public cc::ScrollElasticityHelper { set_stretch_amount_count_ += 1; stretch_amount_ = stretch_amount; } + + gfx::Size ScrollBounds() const override { return gfx::Size(800, 600); } gfx::ScrollOffset ScrollOffset() const override { return scroll_offset_; } gfx::ScrollOffset MaxScrollOffset() const override { return max_scroll_offset_; diff --git a/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.cc b/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.cc index d63cbf3b054..027077f21fc 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.cc @@ -10,9 +10,11 @@ // TODO(arakeri): This is where all the overscroll specific code will go. namespace blink { +constexpr float kOverscrollBoundaryMultiplier = 0.1f; + OverscrollBounceController::OverscrollBounceController( cc::ScrollElasticityHelper* helper) - : weak_factory_(this) {} + : state_(kStateInactive), helper_(helper), weak_factory_(this) {} OverscrollBounceController::~OverscrollBounceController() = default; @@ -21,12 +23,149 @@ OverscrollBounceController::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } -void OverscrollBounceController::ObserveGestureEventAndResult( - const blink::WebGestureEvent& gesture_event, - const cc::InputHandlerScrollResult& scroll_result) {} - void OverscrollBounceController::Animate(base::TimeTicks time) {} -void OverscrollBounceController::ReconcileStretchAndScroll() {} +// TODO(arakeri): ReconcileStretchAndScroll implementations in both the classes +// InputScrollElasticityController and OverscrollBounceController have common +// code that needs to be evaluated and moved up into the base class. +void OverscrollBounceController::ReconcileStretchAndScroll() { + const gfx::Vector2dF stretch = helper_->StretchAmount(); + if (stretch.IsZero()) + return; + + const gfx::ScrollOffset scroll_offset = helper_->ScrollOffset(); + const gfx::ScrollOffset max_scroll_offset = helper_->MaxScrollOffset(); + + float scroll_adjustment_x = 0; + if (stretch.x() < 0.f) + scroll_adjustment_x = scroll_offset.x(); + else if (stretch.x() > 0.f) + scroll_adjustment_x = max_scroll_offset.x() - scroll_offset.x(); + + float scroll_adjustment_y = 0; + if (stretch.y() < 0.f) + scroll_adjustment_y = scroll_offset.y(); + else if (stretch.y() > 0.f) + scroll_adjustment_y = max_scroll_offset.y() - scroll_offset.y(); + + if (state_ == kStateActiveScroll) { + // During an active scroll, we want to reduce |accumulated_scroll_delta_| by + // the amount that was scrolled (but we don't want to over-consume, so limit + // it by the amount of |accumulated_scroll_delta_|). + scroll_adjustment_x = std::copysign( + std::min(std::abs(accumulated_scroll_delta_.x()), scroll_adjustment_x), + stretch.x()); + scroll_adjustment_y = std::copysign( + std::min(std::abs(accumulated_scroll_delta_.y()), scroll_adjustment_y), + stretch.y()); + + accumulated_scroll_delta_ -= + gfx::Vector2dF(scroll_adjustment_x, scroll_adjustment_y); + helper_->SetStretchAmount(OverscrollBounceDistance( + accumulated_scroll_delta_, helper_->ScrollBounds())); + } + + helper_->ScrollBy(gfx::Vector2dF(scroll_adjustment_x, scroll_adjustment_y)); +} + +// Returns the maximum amount to be overscrolled. +gfx::Vector2dF OverscrollBounceController::OverscrollBoundary( + const gfx::Size& scroller_bounds) const { + return gfx::Vector2dF( + scroller_bounds.width() * kOverscrollBoundaryMultiplier, + scroller_bounds.height() * kOverscrollBoundaryMultiplier); +} + +// The goal of this calculation is to map the distance the user has scrolled +// past the boundary into the distance to actually scroll the elastic scroller. +gfx::Vector2d OverscrollBounceController::OverscrollBounceDistance( + const gfx::Vector2dF& distance_overscrolled, + const gfx::Size& scroller_bounds) const { + // TODO(arakeri): This should change as you pinch zoom in. + gfx::Vector2dF overscroll_boundary = OverscrollBoundary(scroller_bounds); + + // We use the tanh function in addition to the mapping, which gives it more of + // a spring effect. However, we want to use tanh's range from [0, 2], so we + // multiply the value we provide to tanh by 2. + + // Also, it may happen that the scroller_bounds are 0 if the viewport scroll + // nodes are null (see: ScrollElasticityHelper::ScrollBounds). We therefore + // have to check in order to avoid a divide by 0. + gfx::Vector2d overbounce_distance; + if (scroller_bounds.width() > 0.f) { + overbounce_distance.set_x( + tanh(2 * distance_overscrolled.x() / scroller_bounds.width()) * + overscroll_boundary.x()); + } + + if (scroller_bounds.height() > 0.f) { + overbounce_distance.set_y( + tanh(2 * distance_overscrolled.y() / scroller_bounds.height()) * + overscroll_boundary.y()); + } + return overbounce_distance; +} + +void OverscrollBounceController::EnterStateActiveScroll() { + state_ = kStateActiveScroll; +} + +void OverscrollBounceController::ObserveRealScrollBegin( + const blink::WebGestureEvent& gesture_event) { + if (gesture_event.data.scroll_begin.inertial_phase == + blink::WebGestureEvent::InertialPhaseState::kNonMomentum && + gesture_event.data.scroll_begin.delta_hint_units == + ui::ScrollGranularity::kScrollByPrecisePixel) { + EnterStateActiveScroll(); + } +} + +void OverscrollBounceController::ObserveRealScrollEnd() { + state_ = kStateInactive; +} + +void OverscrollBounceController::OverscrollIfNecessary( + const gfx::Vector2dF& overscroll_delta) { + accumulated_scroll_delta_ += overscroll_delta; + gfx::Vector2d overbounce_distance = OverscrollBounceDistance( + accumulated_scroll_delta_, helper_->ScrollBounds()); + helper_->SetStretchAmount(overbounce_distance); +} + +void OverscrollBounceController::ObserveScrollUpdate( + const gfx::Vector2dF& unused_scroll_delta) { + if (state_ == kStateInactive) + return; + + if (state_ == kStateActiveScroll) { + // TODO(arakeri): Implement animate back. + OverscrollIfNecessary(unused_scroll_delta); + } +} + +void OverscrollBounceController::ObserveGestureEventAndResult( + const blink::WebGestureEvent& gesture_event, + const cc::InputHandlerScrollResult& scroll_result) { + switch (gesture_event.GetType()) { + case blink::WebInputEvent::Type::kGestureScrollBegin: { + if (gesture_event.data.scroll_begin.synthetic) + return; + ObserveRealScrollBegin(gesture_event); + break; + } + case blink::WebInputEvent::Type::kGestureScrollUpdate: { + gfx::Vector2dF event_delta(-gesture_event.data.scroll_update.delta_x, + -gesture_event.data.scroll_update.delta_y); + ObserveScrollUpdate(scroll_result.unused_scroll_delta); + break; + } + case blink::WebInputEvent::Type::kGestureScrollEnd: { + ObserveRealScrollEnd(); + break; + } + default: + break; + } +} } // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.h b/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.h index 1231c483aaa..71b16b07556 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.h +++ b/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.h @@ -7,19 +7,17 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "cc/input/input_handler.h" #include "cc/input/overscroll_behavior.h" #include "cc/input/scroll_elasticity_helper.h" #include "third_party/blink/public/common/input/web_gesture_event.h" #include "third_party/blink/public/platform/input/elastic_overscroll_controller.h" -namespace cc { -struct InputHandlerScrollResult; -} // namespace cc - namespace blink { // The overbounce version of elastic overscrolling mimics Windows style // overscroll animations. -class OverscrollBounceController : public ElasticOverscrollController { +class BLINK_PLATFORM_EXPORT OverscrollBounceController + : public ElasticOverscrollController { public: explicit OverscrollBounceController(cc::ScrollElasticityHelper* helper); ~OverscrollBounceController() override; @@ -31,8 +29,37 @@ class OverscrollBounceController : public ElasticOverscrollController { const cc::InputHandlerScrollResult& scroll_result) override; void Animate(base::TimeTicks time) override; void ReconcileStretchAndScroll() override; + gfx::Vector2d OverscrollBounceDistance( + const gfx::Vector2dF& distance_overscrolled, + const gfx::Size& scroller_bounds) const; private: + void ObserveRealScrollBegin(const blink::WebGestureEvent& gesture_event); + void ObserveRealScrollEnd(); + gfx::Vector2dF OverscrollBoundary(const gfx::Size& scroller_bounds) const; + void EnterStateActiveScroll(); + void OverscrollIfNecessary(const gfx::Vector2dF& overscroll_delta); + + void ObserveScrollUpdate(const gfx::Vector2dF& unused_scroll_delta); + + enum State { + // The initial state, during which the overscroll amount is zero. + kStateInactive, + // ActiveScroll indicates that this controller is listening to future GSU + // events, and those events may or may not update the overscroll amount. + // This occurs when the user is actively panning either via a touchscreen or + // touchpad, or is an active fling that has not triggered an overscroll. + kStateActiveScroll, + }; + + State state_; + cc::ScrollElasticityHelper* helper_; + + // This is the accumulated raw delta in pixels that's been overscrolled. It + // will be fed into a tanh function (ranging [0, 2]) that decides the stretch + // bounds. + gfx::Vector2dF accumulated_scroll_delta_; + base::WeakPtrFactory<OverscrollBounceController> weak_factory_; DISALLOW_COPY_AND_ASSIGN(OverscrollBounceController); }; diff --git a/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller_unittest.cc b/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller_unittest.cc new file mode 100644 index 00000000000..bf3d374aee6 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller_unittest.cc @@ -0,0 +1,158 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright (C) Microsoft Corporation. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a fork of the input_scroll_elasticity_controller_unittest.cc. + +#include "third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.h" + +#include "cc/input/input_handler.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/common/input/web_input_event.h" +#include "third_party/blink/public/common/input/web_mouse_wheel_event.h" + +namespace blink { +namespace test { + +class MockScrollElasticityHelper : public cc::ScrollElasticityHelper { + public: + MockScrollElasticityHelper() = default; + ~MockScrollElasticityHelper() override = default; + + // cc::ScrollElasticityHelper implementation: + gfx::Size ScrollBounds() const override { return gfx::Size(1000, 1000); } + bool IsUserScrollable() const override { return false; } + gfx::Vector2dF StretchAmount() const override { return stretch_amount_; } + void SetStretchAmount(const gfx::Vector2dF& stretch_amount) override { + stretch_amount_ = stretch_amount; + } + void ScrollBy(const gfx::Vector2dF& delta) override { + scroll_offset_ += gfx::ScrollOffset(delta); + } + void RequestOneBeginFrame() override {} + gfx::ScrollOffset ScrollOffset() const override { return scroll_offset_; } + gfx::ScrollOffset MaxScrollOffset() const override { + return max_scroll_offset_; + } + + void SetScrollOffsetAndMaxScrollOffset( + const gfx::ScrollOffset& scroll_offset, + const gfx::ScrollOffset& max_scroll_offset) { + scroll_offset_ = scroll_offset; + max_scroll_offset_ = max_scroll_offset; + } + + private: + gfx::Vector2dF stretch_amount_; + gfx::ScrollOffset scroll_offset_, max_scroll_offset_; +}; + +class OverscrollBounceControllerTest : public testing::Test { + public: + OverscrollBounceControllerTest() : controller_(&helper_) {} + ~OverscrollBounceControllerTest() override = default; + + void SetUp() override {} + + void SendGestureScrollBegin( + WebGestureEvent::InertialPhaseState inertialPhase) { + WebGestureEvent event(WebInputEvent::Type::kGestureScrollBegin, + WebInputEvent::kNoModifiers, base::TimeTicks(), + WebGestureDevice::kTouchpad); + event.data.scroll_begin.inertial_phase = inertialPhase; + + controller_.ObserveGestureEventAndResult(event, + cc::InputHandlerScrollResult()); + } + + void SendGestureScrollUpdate( + WebGestureEvent::InertialPhaseState inertialPhase, + const gfx::Vector2dF& scroll_delta, + const gfx::Vector2dF& unused_scroll_delta) { + blink::WebGestureEvent event(WebInputEvent::Type::kGestureScrollUpdate, + WebInputEvent::kNoModifiers, base::TimeTicks(), + blink::WebGestureDevice::kTouchpad); + event.data.scroll_update.inertial_phase = inertialPhase; + event.data.scroll_update.delta_x = -scroll_delta.x(); + event.data.scroll_update.delta_y = -scroll_delta.y(); + + cc::InputHandlerScrollResult scroll_result; + scroll_result.did_overscroll_root = !unused_scroll_delta.IsZero(); + scroll_result.unused_scroll_delta = unused_scroll_delta; + + controller_.ObserveGestureEventAndResult(event, scroll_result); + } + void SendGestureScrollEnd() { + WebGestureEvent event(WebInputEvent::Type::kGestureScrollEnd, + WebInputEvent::kNoModifiers, base::TimeTicks(), + WebGestureDevice::kTouchpad); + + controller_.ObserveGestureEventAndResult(event, + cc::InputHandlerScrollResult()); + } + + MockScrollElasticityHelper helper_; + OverscrollBounceController controller_; +}; + +// Tests the bounds of the overscroll and that the "StretchAmount" returns back +// to 0 once the overscroll is done. +TEST_F(OverscrollBounceControllerTest, VerifyOverscrollStretch) { + // Test vertical overscroll. + SendGestureScrollBegin(WebGestureEvent::InertialPhaseState::kNonMomentum); + gfx::Vector2dF delta(0, -50); + EXPECT_EQ(gfx::Vector2dF(0, 0), helper_.StretchAmount()); + SendGestureScrollUpdate(WebGestureEvent::InertialPhaseState::kNonMomentum, + delta, gfx::Vector2dF(0, -100)); + EXPECT_EQ(gfx::Vector2dF(0, -19), helper_.StretchAmount()); + SendGestureScrollUpdate(WebGestureEvent::InertialPhaseState::kNonMomentum, + delta, gfx::Vector2dF(0, 100)); + EXPECT_EQ(gfx::Vector2dF(0, 0), helper_.StretchAmount()); + SendGestureScrollEnd(); + + // Test horizontal overscroll. + SendGestureScrollBegin(WebGestureEvent::InertialPhaseState::kNonMomentum); + delta = gfx::Vector2dF(-50, 0); + EXPECT_EQ(gfx::Vector2dF(0, 0), helper_.StretchAmount()); + SendGestureScrollUpdate(WebGestureEvent::InertialPhaseState::kNonMomentum, + delta, gfx::Vector2dF(-100, 0)); + EXPECT_EQ(gfx::Vector2dF(-19, 0), helper_.StretchAmount()); + SendGestureScrollUpdate(WebGestureEvent::InertialPhaseState::kNonMomentum, + delta, gfx::Vector2dF(100, 0)); + EXPECT_EQ(gfx::Vector2dF(0, 0), helper_.StretchAmount()); + SendGestureScrollEnd(); +} + +// Verify that ReconcileStretchAndScroll reduces the overscrolled delta. +TEST_F(OverscrollBounceControllerTest, ReconcileStretchAndScroll) { + // Test overscroll in both directions. + gfx::Vector2dF delta(0, -50); + SendGestureScrollBegin(WebGestureEvent::InertialPhaseState::kNonMomentum); + helper_.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 8), + gfx::ScrollOffset(100, 100)); + SendGestureScrollUpdate(WebGestureEvent::InertialPhaseState::kNonMomentum, + delta, gfx::Vector2dF(-100, -100)); + EXPECT_EQ(gfx::Vector2dF(-19, -19), helper_.StretchAmount()); + controller_.ReconcileStretchAndScroll(); + EXPECT_EQ(gfx::Vector2dF(-18, -18), helper_.StretchAmount()); + // Adjustment of gfx::ScrollOffset(-5, -8) should bring back the + // scroll_offset_ to 0. + EXPECT_EQ(helper_.ScrollOffset(), gfx::ScrollOffset(0, 0)); +} + +// Tests if the overscrolled delta maps correctly to the actual amount that the +// scroller gets stretched. +TEST_F(OverscrollBounceControllerTest, VerifyOverscrollBounceDistance) { + gfx::Vector2dF overscroll_bounce_distance( + controller_.OverscrollBounceDistance(gfx::Vector2dF(0, -100), + helper_.ScrollBounds())); + EXPECT_EQ(overscroll_bounce_distance.y(), -19); + + overscroll_bounce_distance = controller_.OverscrollBounceDistance( + gfx::Vector2dF(-100, 0), helper_.ScrollBounds()); + EXPECT_EQ(overscroll_bounce_distance.x(), -19); +} + +} // namespace test +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/widget/input/prediction/input_filter_unittest_helpers.h b/chromium/third_party/blink/renderer/platform/widget/input/prediction/input_filter_unittest_helpers.h index db2041c42bf..5c654520215 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/prediction/input_filter_unittest_helpers.h +++ b/chromium/third_party/blink/renderer/platform/widget/input/prediction/input_filter_unittest_helpers.h @@ -7,6 +7,7 @@ #include "third_party/blink/renderer/platform/widget/input/prediction/input_filter.h" +#include "base/macros.h" #include "testing/gtest/include/gtest/gtest.h" namespace blink { diff --git a/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc b/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc index 5cd5c7139c5..c62a9bd07bd 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc @@ -74,11 +74,11 @@ std::unique_ptr<EventWithCallback> ScrollPredictor::ResampleScrollEvents( return event_with_callback; for (auto& coalesced_event : original_events) - UpdatePrediction(coalesced_event.event_, frame_time); + UpdatePrediction(coalesced_event.event_->Event(), frame_time); if (should_resample_scroll_events_) { ResampleEvent(frame_time, event_with_callback->event_pointer(), - event_with_callback->mutable_latency_info()); + &event_with_callback->latency_info()); } metrics_handler_.EvaluatePrediction(); @@ -100,12 +100,11 @@ void ScrollPredictor::Reset() { metrics_handler_.Reset(); } -void ScrollPredictor::UpdatePrediction( - const std::unique_ptr<WebInputEvent>& event, - base::TimeTicks frame_time) { - DCHECK(event->GetType() == WebInputEvent::Type::kGestureScrollUpdate); +void ScrollPredictor::UpdatePrediction(const WebInputEvent& event, + base::TimeTicks frame_time) { + DCHECK(event.GetType() == WebInputEvent::Type::kGestureScrollUpdate); const WebGestureEvent& gesture_event = - static_cast<const WebGestureEvent&>(*event); + static_cast<const WebGestureEvent&>(event); // When fling, GSU is sending per frame, resampling is not needed. if (gesture_event.data.scroll_update.inertial_phase == WebGestureEvent::InertialPhaseState::kMomentum) { diff --git a/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.h b/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.h index 0678e90c3da..7206159dd7c 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.h +++ b/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.h @@ -49,8 +49,7 @@ class PLATFORM_EXPORT ScrollPredictor { void Reset(); // Update the prediction with GestureScrollUpdate deltaX and deltaY - void UpdatePrediction(const std::unique_ptr<WebInputEvent>& event, - base::TimeTicks frame_time); + void UpdatePrediction(const WebInputEvent& event, base::TimeTicks frame_time); // Apply resampled deltaX/deltaY to gesture events void ResampleEvent(base::TimeTicks frame_time, diff --git a/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc b/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc index ada25ead61f..65b14dc9b39 100644 --- a/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc +++ b/chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc @@ -54,7 +54,8 @@ class ScrollPredictorTest : public testing::Test { gesture.data.scroll_update.delta_y = delta_y; gesture.data.scroll_update.inertial_phase = phase; - original_events_.emplace_back(gesture.Clone(), ui::LatencyInfo(), + original_events_.emplace_back(std::make_unique<WebCoalescedInputEvent>( + gesture.Clone(), ui::LatencyInfo()), base::NullCallback()); return gesture.Clone(); @@ -76,9 +77,10 @@ class ScrollPredictorTest : public testing::Test { void HandleResampleScrollEvents(std::unique_ptr<WebInputEvent>& event, double time_delta_in_milliseconds = 0) { std::unique_ptr<EventWithCallback> event_with_callback = - std::make_unique<EventWithCallback>(std::move(event), ui::LatencyInfo(), - base::TimeTicks(), - base::NullCallback()); + std::make_unique<EventWithCallback>( + std::make_unique<WebCoalescedInputEvent>(std::move(event), + ui::LatencyInfo()), + base::TimeTicks(), base::NullCallback()); event_with_callback->original_events() = std::move(original_events_); event_with_callback = scroll_predictor_->ResampleScrollEvents( diff --git a/chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.cc b/chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.cc new file mode 100644 index 00000000000..47bcd0cdd3a --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.cc @@ -0,0 +1,702 @@ +// Copyright 2015 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/renderer/platform/widget/input/widget_base_input_handler.h" + +#include <stddef.h> +#include <stdint.h> +#include <utility> + +#include "base/metrics/histogram_macros.h" +#include "build/build_config.h" +#include "cc/metrics/event_metrics.h" +#include "cc/paint/element_id.h" +#include "cc/trees/latency_info_swap_promise_monitor.h" +#include "cc/trees/layer_tree_host.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/input/web_gesture_device.h" +#include "third_party/blink/public/common/input/web_gesture_event.h" +#include "third_party/blink/public/common/input/web_input_event_attribution.h" +#include "third_party/blink/public/common/input/web_keyboard_event.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/mojom/input/input_event_result.mojom-shared.h" +#include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_frame_widget.h" +#include "third_party/blink/public/web/web_local_frame.h" +#include "third_party/blink/public/web/web_node.h" +#include "third_party/blink/renderer/platform/widget/widget_base.h" +#include "third_party/blink/renderer/platform/widget/widget_base_client.h" +#include "ui/latency/latency_info.h" + +#if defined(OS_ANDROID) +#include <android/keycodes.h> +#endif + +using perfetto::protos::pbzero::ChromeLatencyInfo; +using perfetto::protos::pbzero::TrackEvent; + +namespace blink { + +namespace { + +int64_t GetEventLatencyMicros(base::TimeTicks event_timestamp, + base::TimeTicks now) { + return (now - event_timestamp).InMicroseconds(); +} + +void LogInputEventLatencyUma(const WebInputEvent& event, base::TimeTicks now) { + UMA_HISTOGRAM_CUSTOM_COUNTS( + "Event.AggregatedLatency.Renderer2", + base::saturated_cast<base::HistogramBase::Sample>( + GetEventLatencyMicros(event.TimeStamp(), now)), + 1, 10000000, 100); +} + +void LogPassiveEventListenersUma(WebInputEventResult result, + WebInputEvent::DispatchType dispatch_type) { + // This enum is backing a histogram. Do not remove or reorder members. + enum ListenerEnum { + PASSIVE_LISTENER_UMA_ENUM_PASSIVE, + PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE, + PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED, + PASSIVE_LISTENER_UMA_ENUM_CANCELABLE, + PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED, + PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING, + PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_MAIN_THREAD_RESPONSIVENESS_DEPRECATED, + PASSIVE_LISTENER_UMA_ENUM_COUNT + }; + + ListenerEnum enum_value; + switch (dispatch_type) { + case WebInputEvent::DispatchType::kListenersForcedNonBlockingDueToFling: + enum_value = PASSIVE_LISTENER_UMA_ENUM_FORCED_NON_BLOCKING_DUE_TO_FLING; + break; + case WebInputEvent::DispatchType::kListenersNonBlockingPassive: + enum_value = PASSIVE_LISTENER_UMA_ENUM_PASSIVE; + break; + case WebInputEvent::DispatchType::kEventNonBlocking: + enum_value = PASSIVE_LISTENER_UMA_ENUM_UNCANCELABLE; + break; + case WebInputEvent::DispatchType::kBlocking: + if (result == WebInputEventResult::kHandledApplication) + enum_value = PASSIVE_LISTENER_UMA_ENUM_CANCELABLE_AND_CANCELED; + else if (result == WebInputEventResult::kHandledSuppressed) + enum_value = PASSIVE_LISTENER_UMA_ENUM_SUPPRESSED; + else + enum_value = PASSIVE_LISTENER_UMA_ENUM_CANCELABLE; + break; + default: + NOTREACHED(); + return; + } + + UMA_HISTOGRAM_ENUMERATION("Event.PassiveListeners", enum_value, + PASSIVE_LISTENER_UMA_ENUM_COUNT); +} + +void LogAllPassiveEventListenersUma(const WebInputEvent& input_event, + WebInputEventResult result) { + // TODO(dtapuska): Use the input_event.timeStampSeconds as the start + // ideally this should be when the event was sent by the compositor to the + // renderer. https://crbug.com/565348. + if (input_event.GetType() == WebInputEvent::Type::kTouchStart || + input_event.GetType() == WebInputEvent::Type::kTouchMove || + input_event.GetType() == WebInputEvent::Type::kTouchEnd) { + const WebTouchEvent& touch = static_cast<const WebTouchEvent&>(input_event); + + LogPassiveEventListenersUma(result, touch.dispatch_type); + } else if (input_event.GetType() == WebInputEvent::Type::kMouseWheel) { + LogPassiveEventListenersUma( + result, + static_cast<const WebMouseWheelEvent&>(input_event).dispatch_type); + } +} + +WebCoalescedInputEvent GetCoalescedWebPointerEventForTouch( + const WebPointerEvent& pointer_event, + const std::vector<std::unique_ptr<WebInputEvent>>& coalesced_events, + const std::vector<std::unique_ptr<WebInputEvent>>& predicted_events, + const ui::LatencyInfo& latency) { + std::vector<std::unique_ptr<WebInputEvent>> related_pointer_events; + for (const std::unique_ptr<WebInputEvent>& event : coalesced_events) { + DCHECK(WebInputEvent::IsTouchEventType(event->GetType())); + const WebTouchEvent& touch_event = + static_cast<const WebTouchEvent&>(*event); + for (unsigned i = 0; i < touch_event.touches_length; ++i) { + if (touch_event.touches[i].id == pointer_event.id && + touch_event.touches[i].state != + WebTouchPoint::State::kStateStationary) { + related_pointer_events.emplace_back(std::make_unique<WebPointerEvent>( + touch_event, touch_event.touches[i])); + } + } + } + std::vector<std::unique_ptr<WebInputEvent>> predicted_pointer_events; + for (const std::unique_ptr<WebInputEvent>& event : predicted_events) { + DCHECK(WebInputEvent::IsTouchEventType(event->GetType())); + const WebTouchEvent& touch_event = + static_cast<const WebTouchEvent&>(*event); + for (unsigned i = 0; i < touch_event.touches_length; ++i) { + if (touch_event.touches[i].id == pointer_event.id && + touch_event.touches[i].state != + WebTouchPoint::State::kStateStationary) { + predicted_pointer_events.emplace_back(std::make_unique<WebPointerEvent>( + touch_event, touch_event.touches[i])); + } + } + } + + return WebCoalescedInputEvent(pointer_event.Clone(), + std::move(related_pointer_events), + std::move(predicted_pointer_events), latency); +} + +mojom::InputEventResultState GetAckResult(WebInputEventResult processed) { + return processed == WebInputEventResult::kNotHandled + ? mojom::InputEventResultState::kNotConsumed + : mojom::InputEventResultState::kConsumed; +} + +bool IsGestureScroll(WebInputEvent::Type type) { + switch (type) { + case WebGestureEvent::Type::kGestureScrollBegin: + case WebGestureEvent::Type::kGestureScrollUpdate: + case WebGestureEvent::Type::kGestureScrollEnd: + return true; + default: + return false; + } +} + +gfx::PointF PositionInWidgetFromInputEvent(const WebInputEvent& event) { + if (WebInputEvent::IsMouseEventType(event.GetType())) { + return static_cast<const WebMouseEvent&>(event).PositionInWidget(); + } else if (WebInputEvent::IsGestureEventType(event.GetType())) { + return static_cast<const WebGestureEvent&>(event).PositionInWidget(); + } else { + return gfx::PointF(0, 0); + } +} + +bool IsTouchStartOrMove(const WebInputEvent& event) { + if (WebInputEvent::IsPointerEventType(event.GetType())) { + return static_cast<const WebPointerEvent&>(event) + .touch_start_or_first_touch_move; + } else if (WebInputEvent::IsTouchEventType(event.GetType())) { + return static_cast<const WebTouchEvent&>(event) + .touch_start_or_first_touch_move; + } else { + return false; + } +} + +} // namespace + +// This class should be placed on the stack when handling an input event. It +// stores information from callbacks from blink while handling an input event +// and allows them to be returned in the InputEventAck result. +class WidgetBaseInputHandler::HandlingState { + public: + HandlingState(base::WeakPtr<WidgetBaseInputHandler> input_handler_param, + bool is_touch_start_or_move) + : touch_start_or_move(is_touch_start_or_move), + input_handler(std::move(input_handler_param)) { + previous_was_handling_input = input_handler->handling_input_event_; + previous_state = input_handler->handling_input_state_; + input_handler->handling_input_event_ = true; + input_handler->handling_input_state_ = this; + } + + ~HandlingState() { + // Unwinding the HandlingState on the stack might result in an + // input_handler_ that got destroyed. i.e. via a nested event loop. + if (!input_handler) + return; + input_handler->handling_input_event_ = previous_was_handling_input; + DCHECK_EQ(input_handler->handling_input_state_, this); + input_handler->handling_input_state_ = previous_state; + +#if defined(OS_ANDROID) + if (show_virtual_keyboard) + input_handler->ShowVirtualKeyboard(); + else + input_handler->UpdateTextInputState(); +#endif + } + + // Used to intercept overscroll notifications while an event is being + // handled. If the event causes overscroll, the overscroll metadata can be + // bundled in the event ack, saving an IPC. Note that we must continue + // supporting overscroll IPC notifications due to fling animation updates. + std::unique_ptr<InputHandlerProxy::DidOverscrollParams> event_overscroll; + + base::Optional<WebTouchAction> touch_action; + + // Used to hold a sequence of parameters corresponding to scroll gesture + // events that should be injected once the current input event is done + // being processed. + std::vector<WidgetBaseInputHandler::InjectScrollGestureParams> + injected_scroll_params; + + // Whether the event we are handling is a touch start or move. + bool touch_start_or_move; + +#if defined(OS_ANDROID) + // Whether to show the virtual keyboard or not at the end of processing. + bool show_virtual_keyboard = false; +#endif + + private: + HandlingState* previous_state; + bool previous_was_handling_input; + base::WeakPtr<WidgetBaseInputHandler> input_handler; +}; + +WidgetBaseInputHandler::WidgetBaseInputHandler(WidgetBase* widget) + : widget_(widget), + supports_buffered_touch_( + widget_->client()->SupportsBufferedTouchEvents()) {} + +WebInputEventResult WidgetBaseInputHandler::HandleTouchEvent( + const WebCoalescedInputEvent& coalesced_event) { + const WebInputEvent& input_event = coalesced_event.Event(); + + if (input_event.GetType() == WebInputEvent::Type::kTouchScrollStarted) { + WebPointerEvent pointer_event = + WebPointerEvent::CreatePointerCausesUaActionEvent( + WebPointerProperties::PointerType::kUnknown, + input_event.TimeStamp()); + return widget_->client()->HandleInputEvent( + WebCoalescedInputEvent(pointer_event, coalesced_event.latency_info())); + } + + const WebTouchEvent touch_event = + static_cast<const WebTouchEvent&>(input_event); + for (unsigned i = 0; i < touch_event.touches_length; ++i) { + const WebTouchPoint& touch_point = touch_event.touches[i]; + if (touch_point.state != WebTouchPoint::State::kStateStationary) { + const WebPointerEvent& pointer_event = + WebPointerEvent(touch_event, touch_point); + const WebCoalescedInputEvent& coalesced_pointer_event = + GetCoalescedWebPointerEventForTouch( + pointer_event, coalesced_event.GetCoalescedEventsPointers(), + coalesced_event.GetPredictedEventsPointers(), + coalesced_event.latency_info()); + widget_->client()->HandleInputEvent(coalesced_pointer_event); + } + } + return widget_->client()->DispatchBufferedTouchEvents(); +} + +void WidgetBaseInputHandler::HandleInputEvent( + const WebCoalescedInputEvent& coalesced_event, + HandledEventCallback callback) { + const WebInputEvent& input_event = coalesced_event.Event(); + + // Keep a WeakPtr to this WidgetBaseInputHandler to detect if executing the + // input event destroyed the associated RenderWidget (and this handler). + base::WeakPtr<WidgetBaseInputHandler> weak_self = + weak_ptr_factory_.GetWeakPtr(); + HandlingState handling_state(weak_self, IsTouchStartOrMove(input_event)); + + base::TimeTicks start_time; + if (base::TimeTicks::IsHighResolution()) + start_time = base::TimeTicks::Now(); + + TRACE_EVENT1("renderer,benchmark,rail", + "WidgetBaseInputHandler::OnHandleInputEvent", "event", + WebInputEvent::GetName(input_event.GetType())); + int64_t trace_id = coalesced_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_MAIN); + tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT, + trace_id); + }); + + // If we don't have a high res timer, these metrics won't be accurate enough + // to be worth collecting. Note that this does introduce some sampling bias. + if (!start_time.is_null()) + LogInputEventLatencyUma(input_event, start_time); + + ui::LatencyInfo swap_latency_info(coalesced_event.latency_info()); + swap_latency_info.AddLatencyNumber( + ui::LatencyComponentType::INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT); + cc::LatencyInfoSwapPromiseMonitor swap_promise_monitor( + &swap_latency_info, widget_->LayerTreeHost()->GetSwapPromiseManager(), + nullptr); + auto scoped_event_metrics_monitor = + widget_->LayerTreeHost()->GetScopedEventMetricsMonitor( + cc::EventMetrics::Create(input_event.GetTypeAsUiEventType(), + input_event.TimeStamp(), + input_event.GetScrollInputType())); + + bool prevent_default = false; + bool show_virtual_keyboard_for_mouse = false; + if (WebInputEvent::IsMouseEventType(input_event.GetType())) { + const WebMouseEvent& mouse_event = + static_cast<const WebMouseEvent&>(input_event); + TRACE_EVENT2("renderer", "HandleMouseMove", "x", + mouse_event.PositionInWidget().x(), "y", + mouse_event.PositionInWidget().y()); + + prevent_default = widget_->client()->WillHandleMouseEvent(mouse_event); + + // Reset the last known cursor if mouse has left this widget. So next + // time that the mouse enters we always set the cursor accordingly. + if (mouse_event.GetType() == WebInputEvent::Type::kMouseLeave) + current_cursor_.reset(); + + if (mouse_event.button == WebPointerProperties::Button::kLeft && + mouse_event.GetType() == WebInputEvent::Type::kMouseUp) { + show_virtual_keyboard_for_mouse = true; + } + } + + if (WebInputEvent::IsKeyboardEventType(input_event.GetType())) { +#if defined(OS_ANDROID) + // The DPAD_CENTER key on Android has a dual semantic: (1) in the general + // case it should behave like a select key (i.e. causing a click if a button + // is focused). However, if a text field is focused (2), its intended + // behavior is to just show the IME and don't propagate the key. + // A typical use case is a web form: the DPAD_CENTER should bring up the IME + // when clicked on an input text field and cause the form submit if clicked + // when the submit button is focused, but not vice-versa. + // The UI layer takes care of translating DPAD_CENTER into a RETURN key, + // but at this point we have to swallow the event for the scenario (2). + const WebKeyboardEvent& key_event = + static_cast<const WebKeyboardEvent&>(input_event); + if (key_event.native_key_code == AKEYCODE_DPAD_CENTER && + widget_->client()->GetTextInputType() != + WebTextInputType::kWebTextInputTypeNone) { + // Show the keyboard on keyup (not keydown) to match the behavior of + // Android's TextView. + if (key_event.GetType() == WebInputEvent::Type::kKeyUp) + widget_->ShowVirtualKeyboardOnElementFocus(); + // Prevent default for both keydown and keyup (letting the keydown go + // through to the web app would cause compatibility problems since + // DPAD_CENTER is also used as a "confirm" button). + prevent_default = true; + } +#endif + } + + if (WebInputEvent::IsGestureEventType(input_event.GetType())) { + const WebGestureEvent& gesture_event = + static_cast<const WebGestureEvent&>(input_event); + prevent_default = prevent_default || + widget_->client()->WillHandleGestureEvent(gesture_event); + } + + WebInputEventResult processed = prevent_default + ? WebInputEventResult::kHandledSuppressed + : WebInputEventResult::kNotHandled; + if (input_event.GetType() != WebInputEvent::Type::kChar || + !suppress_next_char_events_) { + suppress_next_char_events_ = false; + if (processed == WebInputEventResult::kNotHandled) { + if (supports_buffered_touch_ && + WebInputEvent::IsTouchEventType(input_event.GetType())) + processed = HandleTouchEvent(coalesced_event); + else + processed = widget_->client()->HandleInputEvent(coalesced_event); + } + + // The associated WidgetBase (and this WidgetBaseInputHandler) could + // have been destroyed. If it was return early before accessing any more of + // this class. + if (!weak_self) { + if (callback) { + std::move(callback).Run(GetAckResult(processed), swap_latency_info, + std::move(handling_state.event_overscroll), + handling_state.touch_action); + } + return; + } + } + + // Handling |input_event| is finished and further down, we might start + // handling injected scroll events. So, stop monitoring EventMetrics for + // |input_event| to avoid nested monitors. + scoped_event_metrics_monitor = nullptr; + + LogAllPassiveEventListenersUma(input_event, processed); + + // If this RawKeyDown event corresponds to a browser keyboard shortcut and + // it's not processed by webkit, then we need to suppress the upcoming Char + // events. + bool is_keyboard_shortcut = + input_event.GetType() == WebInputEvent::Type::kRawKeyDown && + static_cast<const WebKeyboardEvent&>(input_event).is_browser_shortcut; + if (processed == WebInputEventResult::kNotHandled && is_keyboard_shortcut) + suppress_next_char_events_ = true; + + // The handling of some input events on the main thread may require injecting + // scroll gestures back into blink, e.g., a mousedown on a scrollbar. We + // do this here so that we can attribute latency information from the mouse as + // a scroll interaction, instead of just classifying as mouse input. + if (handling_state.injected_scroll_params.size()) { + HandleInjectedScrollGestures( + std::move(handling_state.injected_scroll_params), input_event, + coalesced_event.latency_info()); + } + + // Send gesture scroll events and their dispositions to the compositor thread, + // so that they can be used to produce the elastic overscroll effect. + if (input_event.GetType() == WebInputEvent::Type::kGestureScrollBegin || + input_event.GetType() == WebInputEvent::Type::kGestureScrollEnd || + input_event.GetType() == WebInputEvent::Type::kGestureScrollUpdate) { + const WebGestureEvent& gesture_event = + static_cast<const WebGestureEvent&>(input_event); + if (gesture_event.SourceDevice() == WebGestureDevice::kTouchpad || + gesture_event.SourceDevice() == WebGestureDevice::kTouchscreen) { + gfx::Vector2dF latest_overscroll_delta = + handling_state.event_overscroll + ? handling_state.event_overscroll->latest_overscroll_delta + : gfx::Vector2dF(); + cc::OverscrollBehavior overscroll_behavior = + handling_state.event_overscroll + ? handling_state.event_overscroll->overscroll_behavior + : cc::OverscrollBehavior(); + widget_->client()->ObserveGestureEventAndResult( + gesture_event, latest_overscroll_delta, overscroll_behavior, + processed != WebInputEventResult::kNotHandled); + } + } + + if (callback) { + std::move(callback).Run(GetAckResult(processed), swap_latency_info, + std::move(handling_state.event_overscroll), + handling_state.touch_action); + } else { + DCHECK(!handling_state.event_overscroll) + << "Unexpected overscroll for un-acked event"; + } + + // Show the virtual keyboard if enabled and a user gesture triggers a focus + // change. + if ((processed != WebInputEventResult::kNotHandled && + input_event.GetType() == WebInputEvent::Type::kTouchEnd) || + show_virtual_keyboard_for_mouse) { + ShowVirtualKeyboard(); + } + + if (!prevent_default && + WebInputEvent::IsKeyboardEventType(input_event.GetType())) + widget_->client()->DidHandleKeyEvent(); + +// TODO(rouslan): Fix ChromeOS and Windows 8 behavior of autofill popup with +// virtual keyboard. +#if !defined(OS_ANDROID) + // Virtual keyboard is not supported, so react to focus change immediately. + if ((processed != WebInputEventResult::kNotHandled && + input_event.GetType() == WebInputEvent::Type::kMouseDown) || + input_event.GetType() == WebInputEvent::Type::kGestureTap) { + widget_->client()->FocusChangeComplete(); + } +#endif + + // Ensure all injected scrolls were handled or queue up - any remaining + // injected scrolls at this point would not be processed. + DCHECK(handling_state.injected_scroll_params.empty()); +} + +bool WidgetBaseInputHandler::DidOverscrollFromBlink( + const gfx::Vector2dF& overscroll_delta, + const gfx::Vector2dF& accumulated_overscroll, + const gfx::PointF& position, + const gfx::Vector2dF& velocity, + const cc::OverscrollBehavior& behavior) { + // We aren't currently handling an event. Allow the processing to be + // dispatched separately from the ACK. + if (!handling_input_state_) + return true; + + // If we're currently handling an event, stash the overscroll data such that + // it can be bundled in the event ack. + std::unique_ptr<InputHandlerProxy::DidOverscrollParams> params = + std::make_unique<InputHandlerProxy::DidOverscrollParams>(); + params->accumulated_overscroll = accumulated_overscroll; + params->latest_overscroll_delta = overscroll_delta; + params->current_fling_velocity = velocity; + params->causal_event_viewport_point = position; + params->overscroll_behavior = behavior; + handling_input_state_->event_overscroll = std::move(params); + return false; +} + +void WidgetBaseInputHandler::InjectGestureScrollEvent( + WebGestureDevice device, + const gfx::Vector2dF& delta, + ui::ScrollGranularity granularity, + cc::ElementId scrollable_area_element_id, + WebInputEvent::Type injected_type) { + DCHECK(IsGestureScroll(injected_type)); + // If we're currently handling an input event, cache the appropriate + // parameters so we can dispatch the events directly once blink finishes + // handling the event. + // Otherwise, queue the event on the main thread event queue. + // The latter may occur when scrollbar scrolls are injected due to + // autoscroll timer - i.e. not within the handling of a mouse event. + // We don't always just enqueue events, since events queued to the + // MainThreadEventQueue in the middle of dispatch (which we are) won't + // be dispatched until the next time the queue gets to run. The side effect + // of that would be an extra frame of latency if we're injecting a scroll + // during the handling of a rAF aligned input event, such as mouse move. + if (handling_input_state_) { + InjectScrollGestureParams params{device, delta, granularity, + scrollable_area_element_id, injected_type}; + handling_input_state_->injected_scroll_params.push_back(params); + } else { + base::TimeTicks now = base::TimeTicks::Now(); + std::unique_ptr<WebGestureEvent> gesture_event = + WebGestureEvent::GenerateInjectedScrollGesture( + injected_type, now, device, gfx::PointF(0, 0), delta, granularity); + if (injected_type == WebInputEvent::Type::kGestureScrollBegin) { + gesture_event->data.scroll_begin.scrollable_area_element_id = + scrollable_area_element_id.GetStableId(); + } + + std::unique_ptr<WebCoalescedInputEvent> web_scoped_gesture_event = + std::make_unique<WebCoalescedInputEvent>(std::move(gesture_event), + ui::LatencyInfo()); + widget_->client()->QueueSyntheticEvent(std::move(web_scoped_gesture_event)); + } +} + +void WidgetBaseInputHandler::HandleInjectedScrollGestures( + std::vector<InjectScrollGestureParams> injected_scroll_params, + const WebInputEvent& input_event, + const ui::LatencyInfo& original_latency_info) { + DCHECK(injected_scroll_params.size()); + + base::TimeTicks original_timestamp; + bool found_original_component = original_latency_info.FindLatency( + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, &original_timestamp); + DCHECK(found_original_component); + + gfx::PointF position = PositionInWidgetFromInputEvent(input_event); + for (const InjectScrollGestureParams& params : injected_scroll_params) { + // Set up a new LatencyInfo for the injected scroll - this is the original + // LatencyInfo for the input event that was being handled when the scroll + // was injected. This new LatencyInfo will have a modified type, and an + // additional scroll update component. Also set up a SwapPromiseMonitor that + // will cause the LatencyInfo to be sent up with the compositor frame, if + // the GSU causes a commit. This allows end to end latency to be logged for + // the injected scroll, annotated with the correct type. + ui::LatencyInfo scrollbar_latency_info(original_latency_info); + + // Currently only scrollbar is supported - if this DCHECK hits due to a + // new type being injected, please modify the type passed to + // |set_source_event_type()|. + DCHECK(params.device == WebGestureDevice::kScrollbar); + scrollbar_latency_info.set_source_event_type( + ui::SourceEventType::SCROLLBAR); + scrollbar_latency_info.AddLatencyNumber( + ui::LatencyComponentType::INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT); + + if (params.type == WebInputEvent::Type::kGestureScrollUpdate) { + if (input_event.GetType() != WebInputEvent::Type::kGestureScrollUpdate) { + 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); + } else { + // If we're injecting a GSU in response to a GSU (touch drags of the + // scrollbar thumb in Blink handles GSUs, and reverses them with + // injected GSUs), the LatencyInfo will already have the appropriate + // SCROLL_UPDATE component set. + DCHECK( + scrollbar_latency_info.FindLatency( + ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT, + nullptr) || + scrollbar_latency_info.FindLatency( + ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, + nullptr)); + } + } + + std::unique_ptr<WebGestureEvent> gesture_event = + WebGestureEvent::GenerateInjectedScrollGesture( + params.type, input_event.TimeStamp(), params.device, position, + params.scroll_delta, params.granularity); + if (params.type == WebInputEvent::Type::kGestureScrollBegin) { + gesture_event->data.scroll_begin.scrollable_area_element_id = + params.scrollable_area_element_id.GetStableId(); + last_injected_gesture_was_begin_ = true; + } else { + last_injected_gesture_was_begin_ = false; + } + + { + cc::LatencyInfoSwapPromiseMonitor swap_promise_monitor( + &scrollbar_latency_info, + widget_->LayerTreeHost()->GetSwapPromiseManager(), nullptr); + auto scoped_event_metrics_monitor = + widget_->LayerTreeHost()->GetScopedEventMetricsMonitor( + cc::EventMetrics::Create(gesture_event->GetTypeAsUiEventType(), + gesture_event->TimeStamp(), + gesture_event->GetScrollInputType())); + widget_->client()->HandleInputEvent( + WebCoalescedInputEvent(*gesture_event, scrollbar_latency_info)); + } + } +} + +bool WidgetBaseInputHandler::DidChangeCursor(const ui::Cursor& cursor) { + if (current_cursor_.has_value() && current_cursor_.value() == cursor) + return false; + current_cursor_ = cursor; + return true; +} + +bool WidgetBaseInputHandler::ProcessTouchAction(WebTouchAction touch_action) { + if (!handling_input_state_) + return false; + // Ignore setTouchAction calls that result from synthetic touch events (eg. + // when blink is emulating touch with mouse). + if (!handling_input_state_->touch_start_or_move) + return false; + handling_input_state_->touch_action = touch_action; + return true; +} + +void WidgetBaseInputHandler::ShowVirtualKeyboard() { +#if defined(OS_ANDROID) + if (handling_input_state_) { + handling_input_state_->show_virtual_keyboard = true; + return; + } +#endif + widget_->ShowVirtualKeyboard(); +} + +void WidgetBaseInputHandler::UpdateTextInputState() { +#if defined(OS_ANDROID) + if (handling_input_state_) + return; +#endif + widget_->UpdateTextInputState(); +} + +bool WidgetBaseInputHandler::ProtectedByIMEGuard(bool show_virtual_keyboard) { +#if defined(OS_ANDROID) + if (show_virtual_keyboard && handling_input_state_) { + handling_input_state_->show_virtual_keyboard = true; + } + return handling_input_state_; +#else + return false; +#endif +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.h b/chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.h new file mode 100644 index 00000000000..e54011feaed --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.h @@ -0,0 +1,144 @@ +// Copyright 2015 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_WIDGET_BASE_INPUT_HANDLER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_WIDGET_BASE_INPUT_HANDLER_H_ + +#include <memory> + +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "third_party/blink/public/common/input/web_coalesced_input_event.h" +#include "third_party/blink/public/common/input/web_gesture_event.h" +#include "third_party/blink/public/mojom/input/input_event_result.mojom-blink.h" +#include "third_party/blink/public/platform/input/input_handler_proxy.h" +#include "third_party/blink/public/platform/web_input_event_result.h" +#include "third_party/blink/public/platform/web_touch_action.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "ui/base/cursor/cursor.h" +#include "ui/events/types/scroll_types.h" + +namespace cc { +struct ElementId; +struct OverscrollBehavior; +} // namespace cc + +namespace ui { +class LatencyInfo; +} + +namespace viz { +class FrameSinkId; +} + +namespace blink { + +class WidgetBase; + +class PLATFORM_EXPORT WidgetBaseInputHandler { + public: + WidgetBaseInputHandler(WidgetBase* widget); + WidgetBaseInputHandler(const WidgetBaseInputHandler&) = delete; + WidgetBaseInputHandler& operator=(const WidgetBaseInputHandler&) = delete; + + // Hit test the given point to find out the frame underneath and + // returns the FrameSinkId for that frame. |local_point| returns the point + // in the coordinate space of the FrameSinkId that was hit. + viz::FrameSinkId GetFrameSinkIdAtPoint(const gfx::PointF& point, + gfx::PointF* local_point); + + using HandledEventCallback = base::OnceCallback<void( + mojom::InputEventResultState ack_state, + const ui::LatencyInfo& latency_info, + std::unique_ptr<InputHandlerProxy::DidOverscrollParams>, + base::Optional<WebTouchAction>)>; + + // Handle input events from the input event provider. + void HandleInputEvent(const blink::WebCoalescedInputEvent& coalesced_event, + HandledEventCallback callback); + + // Handle overscroll from Blink. Returns whether the should be sent to the + // browser. This will return false if an event is currently being processed + // and will be returned part of the input ack. + bool DidOverscrollFromBlink(const gfx::Vector2dF& overscrollDelta, + const gfx::Vector2dF& accumulatedOverscroll, + const gfx::PointF& position, + const gfx::Vector2dF& velocity, + const cc::OverscrollBehavior& behavior); + + void InjectGestureScrollEvent(blink::WebGestureDevice device, + const gfx::Vector2dF& delta, + ui::ScrollGranularity granularity, + cc::ElementId scrollable_area_element_id, + blink::WebInputEvent::Type injected_type); + + bool handling_input_event() const { return handling_input_event_; } + void set_handling_input_event(bool handling_input_event) { + handling_input_event_ = handling_input_event; + } + + // Whether the event is protected by an IME guard to prevent intermediate + // IPC messages from being dispatched. + bool ProtectedByIMEGuard(bool show_virtual_keyboard); + + // Process the touch action, returning whether the action should be relayed + // to the browser. + bool ProcessTouchAction(WebTouchAction touch_action); + + // Process the new cursor and returns true if it has changed from the last + // cursor. + bool DidChangeCursor(const ui::Cursor& cursor); + + // Request virtual keyboard be shown. The message will be debounced during + // handling of input events. + void ShowVirtualKeyboard(); + void UpdateTextInputState(); + + private: + class HandlingState; + struct InjectScrollGestureParams { + WebGestureDevice device; + gfx::Vector2dF scroll_delta; + ui::ScrollGranularity granularity; + cc::ElementId scrollable_area_element_id; + blink::WebInputEvent::Type type; + }; + + WebInputEventResult HandleTouchEvent( + const WebCoalescedInputEvent& coalesced_event); + + void HandleInjectedScrollGestures( + std::vector<InjectScrollGestureParams> injected_scroll_params, + const WebInputEvent& input_event, + const ui::LatencyInfo& original_latency_info); + + WidgetBase* widget_; + + // Are we currently handling an input event? + bool handling_input_event_ = false; + + // Current state from HandleInputEvent. This variable is stack allocated + // and is not owned. + HandlingState* handling_input_state_ = nullptr; + + // We store the current cursor object so we can avoid spamming SetCursor + // messages. + base::Optional<ui::Cursor> current_cursor_; + + // Indicates if the next sequence of Char events should be suppressed or not. + bool suppress_next_char_events_ = false; + + // Whether the last injected scroll gesture was a GestureScrollBegin. Used to + // determine which GestureScrollUpdate is the first in a gesture sequence for + // latency classification. + bool last_injected_gesture_was_begin_ = false; + + const bool supports_buffered_touch_ = false; + + base::WeakPtrFactory<WidgetBaseInputHandler> weak_ptr_factory_{this}; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WIDGET_INPUT_WIDGET_BASE_INPUT_HANDLER_H_ |