summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/platform/widget/input
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/widget/input')
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/DEPS3
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/compositor_thread_event_queue.cc6
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.cc60
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/event_with_callback.h31
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc227
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc905
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/input_scroll_elasticity_controller_unittest.cc2
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.cc151
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller.h37
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/overscroll_bounce_controller_unittest.cc158
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/prediction/input_filter_unittest_helpers.h1
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.cc13
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor.h3
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/scroll_predictor_unittest.cc10
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.cc702
-rw-r--r--chromium/third_party/blink/renderer/platform/widget/input/widget_base_input_handler.h144
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_