Commit 63c7d741 authored by David Bokan's avatar David Bokan Committed by Commit Bot

[ScrollUnification] HitTest plumbing and event flow

This CL implements the compositor-thread<->main-thread routing and flow
for cases that need to be main thread hit tested under scroll
unification. Scroll unification is a project to remove all gesture
scroll handling from Blink and rely solely on the compositor gesture
handler.

Design doc: https://docs.google.com/document/d/1smLAXs-DSLLmkEt4FIPP7PVglJXOcwRc7A5G0SEwxaY/edit

Today, some scrollers cannot be scrolled on the compositor thread.
There's a few primary reasons why this can happen:

  - The scroller is not backed by a cc::Layer so we can't hit test it.
  - Unreliable hit test in the presence of squashing layers
  - The scroller has a main thread scrolling reason. It has a cc::Layer
    and we can hit test it but, because of style reasons, scrolling it
    from the compositor would produce incorrect results.

Under scroll unification, if the compositor can determine the correct
ScrollNode to use, it can perform these kinds scrolls by mutating just
the scroll node, then wait on a BeginMainFrame and commit to repaint the
layers and update the property trees, just like today's main thread
scrolling but without invoking the input handling code paths in Blink.

In https://crrev.com/c/2089973 we made it so that the entire scroll tree
is passed to the compositor. Thus, the first two cases above can be
addressed by asking the main thread for the element_id of the ScrollNode
to use. This CL implements the plumbing for this functionality.

Background:

When a gesture scroll begin (GSB) arrives, it first hits the
WidgetInputHandlerManager. This will first dispatch to the compositor by
passing it to InputHandlerProxy. In turn, InputHandlerProxy calls
LayerTreeHostImpl::ScrollBegin. If LTHI determines it cannot handle the
scroll, IHP returns into WIHM (via callback) which can queue events to
the MainThreadEventQueue bound for Blink:

                           DID_NOT_HANDLE
                       v----------<---------+
                       |                    |
GSB+-------------------v-------+            +
-->+ WidgetInputHandlerManager +-->InputHandlerProxy<->LayerTreeHostImpl
   +---------------------------+
            GSB  |
                 v
   +-------------+-------------+
   |           BLINK           |
   +---------------------------+

Today, if the IHP returns event disposition DID_NOT_HANDLE to indicate
that the compositor didn't consume the event, WIHM will forward the
event and further events in the same sequence to Blink. The compositor
will not be involved in handling these events.

Changes in this CL:

In this CL, we add a new compositor event disposition
REQUIRES_MAIN_THREAD_HIT_TEST. This indicates to
WidgetInputHandlerManager that the compositor can handle the scroll but
it cannot resolve the ScrollNode and requests that the WIHM provide the
element_id of the scroller at the event's position. Conceptually, a
scroll has started as far as IHP is concerned; however, while we're
waiting on the WIHM to provide an element_id, the compositor thread
event queue is blocked. This ensures any incoming scrolls are queued and
coalesced until the scrolling can truly begin. Note: Only gestures like
Scroll and Pinch are processed in the queue; other events will be
dispatched directly to the compositor as usual.

When WIHM receives a REQUIRES_MAIN_THREAD_HIT_TEST disposition, it will
post a task to Blink. However, unlike DID_NOT_HANDLE, the event isn't
passed to Blink, WIHM will keep it and redispatch it to IHP when Blink
replies.

When Blink responds, WIHM passes the original event, as well as the
Blink-provided element_id back to IHP. This once again calls
LTHI::ScrollBegin, this time providing a ScrollNode element id so that
hit testing isn't required. The queue is now unblocked and flushed.
Subsequent scroll update events will be routed to the LTHI as if we are
in a traditional compositor scroll:

                      REQUEST_MAIN_THREAD_HIT_TEST
                        v---------<---------+
                        |                   |
GSB +-------------------v------+            +
--->+ WidgetInputHandlerManager+-->InputHandlerProxy<->LayerTreeHostImpl
    +----+---+-----------------+                              ^
         |   ^                                                |
 HitTest |   +---------------------->InputHandlerProxy::      |
         |   |                       ContinueScrollBegin+-----+
         |   | element_id
   +-----v---+-------+
   |     BLINK       |
   +-----------------+

This CL doesn't yet implement the logic in LayerTreeHostImpl to return a
REQUEST_MAIN_THREAD_HIT_TEST, hence this path isn't yet "live". Instead
we leave some TODOs for a follow up CL.

Bug: 1047182
Change-Id: I68a0ec3dddc23c7344ddbfc334f1e25618e43404
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2199610
Commit-Queue: David Bokan <bokan@chromium.org>
Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Reviewed-by: default avatarNavid Zolghadr <nzolghadr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#771249}
parent 69ffdad6
......@@ -143,13 +143,28 @@ class CC_EXPORT InputHandler {
: thread(SCROLL_ON_IMPL_THREAD),
main_thread_scrolling_reasons(
MainThreadScrollingReason::kNotScrollingOnMain),
bubble(false) {}
bubble(false),
needs_main_thread_hit_test(false) {}
ScrollStatus(ScrollThread thread, uint32_t main_thread_scrolling_reasons)
: thread(thread),
main_thread_scrolling_reasons(main_thread_scrolling_reasons) {}
main_thread_scrolling_reasons(main_thread_scrolling_reasons),
bubble(false),
needs_main_thread_hit_test(false) {}
ScrollStatus(ScrollThread thread,
uint32_t main_thread_scrolling_reasons,
bool needs_main_thread_hit_test)
: thread(thread),
main_thread_scrolling_reasons(main_thread_scrolling_reasons),
bubble(false),
needs_main_thread_hit_test(needs_main_thread_hit_test) {}
ScrollThread thread;
uint32_t main_thread_scrolling_reasons;
bool bubble;
// Used only in scroll unification. Tells the caller that the input handler
// detected a case where it cannot reliably target a scroll node and needs
// the main thread to perform a hit test.
bool needs_main_thread_hit_test;
};
enum class TouchStartOrMoveEventListenerType {
......
......@@ -86,6 +86,14 @@ class CC_EXPORT ScrollState {
// it's a scroll update
gfx::ScrollOffset DeltaOrHint() const;
ElementId target_element_id() const {
return data_.current_native_scrolling_element();
}
bool is_main_thread_hit_tested() const {
return data_.is_main_thread_hit_tested;
}
ScrollStateData* data() { return &data_; }
private:
......
......@@ -25,7 +25,8 @@ ScrollStateData::ScrollStateData()
delta_granularity(ui::ScrollGranularity::kScrollByPrecisePixel),
caused_scroll_x(false),
caused_scroll_y(false),
is_scroll_chain_cut(false) {}
is_scroll_chain_cut(false),
is_main_thread_hit_tested(false) {}
ScrollStateData::ScrollStateData(const ScrollStateData& other) = default;
......
......@@ -70,12 +70,18 @@ class CC_EXPORT ScrollStateData {
ElementId current_native_scrolling_element() const;
void set_current_native_scrolling_element(ElementId element_id);
// Used in scroll unification to specify that a scroll state has been hit
// tested on the main thread. If this is true, the hit test result will be
// placed in the current_native_scrolling_element_.
bool is_main_thread_hit_tested;
private:
// The id of the last native element to respond to a scroll, or 0 if none
// exists.
// TODO(bokan): In the compositor, this is now only used as an override to
// scroller targeting, i.e. we'll latch scrolling to the specified
// element_id. It will be renamed when the main thread is also converted.
// scroller targeting. I.e. we'll latch scrolling to the specified
// element_id. It will be renamed to a better name (target_element_id?) when
// the main thread is also converted.
ElementId current_native_scrolling_element_;
};
......
......@@ -15,6 +15,11 @@ namespace cc {
const ElementIdType ElementId::kInvalidElementId = 0;
// static
bool ElementId::IsValid(ElementIdType id) {
return id != kInvalidElementId;
}
ElementId LayerIdToElementIdForTesting(int layer_id) {
return ElementId(std::numeric_limits<int>::max() - layer_id);
}
......
......@@ -63,6 +63,8 @@ struct CC_PAINT_EXPORT ElementId {
std::string ToString() const;
static bool IsValid(ElementIdType id);
private:
friend struct ElementIdHash;
static const ElementIdType kInvalidElementId;
......
......@@ -30,6 +30,7 @@
#include "base/trace_event/traced_value.h"
#include "build/build_config.h"
#include "cc/base/devtools_instrumentation.h"
#include "cc/base/features.h"
#include "cc/base/histograms.h"
#include "cc/base/math_util.h"
#include "cc/base/switches.h"
......@@ -3894,7 +3895,13 @@ InputHandler::ScrollStatus LayerTreeHostImpl::RootScrollBegin(
scroll_state->data()->set_current_native_scrolling_element(
OuterViewportScrollNode()->element_id);
return ScrollBegin(scroll_state, type);
InputHandler::ScrollStatus scroll_status = ScrollBegin(scroll_state, type);
// Since we provided an ElementId, there should never be a need to perform a
// hit test.
DCHECK(!scroll_status.needs_main_thread_hit_test);
return scroll_status;
}
InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
......@@ -3937,13 +3944,16 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
// this should only happen in ScrollEnd. We should DCHECK here that the state
// is cleared instead. https://crbug.com/1016229
ClearCurrentlyScrollingNode();
auto& scroll_tree = active_tree_->property_trees()->scroll_tree;
ElementId target_element_id = scroll_state->target_element_id();
if (auto specified_element_id =
scroll_state->data()->current_native_scrolling_element()) {
if (target_element_id && !scroll_state->is_main_thread_hit_tested()) {
TRACE_EVENT_INSTANT0("cc", "Latched scroll node provided",
TRACE_EVENT_SCOPE_THREAD);
// If the caller passed in an element_id we can skip all the hit-testing
// bits and provide a node straight-away.
auto& scroll_tree = active_tree_->property_trees()->scroll_tree;
scrolling_node = scroll_tree.FindNodeFromElementId(specified_element_id);
scrolling_node = scroll_tree.FindNodeFromElementId(target_element_id);
// We still need to confirm the targeted node exists and can scroll on the
// compositor.
......@@ -3954,11 +3964,31 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
scroll_on_main_thread = true;
}
} else {
ScrollNode* starting_node = nullptr;
if (target_element_id) {
TRACE_EVENT_INSTANT0("cc", "Unlatched scroll node provided",
TRACE_EVENT_SCOPE_THREAD);
// We had an element id but we should still perform the walk up the
// scroll tree from the targeted node to latch to a scroller that can
// scroll in the given direction. This mode is only used when scroll
// unification is enabled and the targeted scroller comes back from a
// main thread hit test.
DCHECK(scroll_state->data()->is_main_thread_hit_tested);
DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification));
starting_node = scroll_tree.FindNodeFromElementId(target_element_id);
} else {
TRACE_EVENT_INSTANT0("cc", "Hit Testing for ScrollNode",
TRACE_EVENT_SCOPE_THREAD);
gfx::Point viewport_point(scroll_state->position_x(),
scroll_state->position_y());
gfx::PointF device_viewport_point = gfx::ScalePoint(
gfx::PointF(viewport_point), active_tree_->device_scale_factor());
if (base::FeatureList::IsEnabled(features::kScrollUnification)) {
// TODO(bokan): Implement - either set starting node to the hit tested
// scroll node or request a main thread hit test.
} else {
LayerImpl* layer_impl =
active_tree_->FindLayerThatIsHitByPoint(device_viewport_point);
......@@ -3969,7 +3999,8 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
// Touch dragging the scrollbar requires falling back to main-thread
// scrolling.
if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar, type)) {
if (IsTouchDraggingScrollbar(first_scrolling_layer_or_scrollbar,
type)) {
TRACE_EVENT_INSTANT0("cc", "Scrollbar Scrolling",
TRACE_EVENT_SCOPE_THREAD);
scroll_status.thread = SCROLL_ON_MAIN_THREAD;
......@@ -3978,7 +4009,8 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
return scroll_status;
} else if (!IsInitialScrollHitTestReliable(
layer_impl, first_scrolling_layer_or_scrollbar)) {
TRACE_EVENT_INSTANT0("cc", "Failed Hit Test", TRACE_EVENT_SCOPE_THREAD);
TRACE_EVENT_INSTANT0("cc", "Failed Hit Test",
TRACE_EVENT_SCOPE_THREAD);
scroll_status.thread = SCROLL_UNKNOWN;
scroll_status.main_thread_scrolling_reasons =
MainThreadScrollingReason::kFailedHitTest;
......@@ -3986,9 +4018,11 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
}
}
ScrollNode* starting_node = FindScrollNodeForCompositedScrolling(
starting_node = FindScrollNodeForCompositedScrolling(
device_viewport_point, layer_impl, &scroll_on_main_thread,
&scroll_status.main_thread_scrolling_reasons);
}
}
// The above finds the ScrollNode that's hit by the given point but we
// still need to walk up the scroll tree looking for the first node that
......@@ -3997,6 +4031,10 @@ InputHandler::ScrollStatus LayerTreeHostImpl::ScrollBegin(
}
if (scroll_on_main_thread) {
// Under scroll unification we can request a main thread hit test, but we
// should never send scrolls to the main thread.
DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification));
RecordCompositorSlowScrollMetric(type, MAIN_THREAD);
scroll_status.thread = SCROLL_ON_MAIN_THREAD;
return scroll_status;
......
......@@ -10,6 +10,7 @@
#include "base/check_op.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "cc/base/features.h"
#include "content/common/input_messages.h"
#include "content/renderer/ime_event_guard.h"
#include "content/renderer/input/widget_input_handler_impl.h"
......@@ -82,9 +83,11 @@ blink::mojom::InputEventResultState InputEventDispositionToAck(
return blink::mojom::InputEventResultState::kSetNonBlocking;
case blink::InputHandlerProxy::DID_HANDLE_SHOULD_BUBBLE:
return blink::mojom::InputEventResultState::kConsumedShouldBubble;
}
case blink::InputHandlerProxy::REQUIRES_MAIN_THREAD_HIT_TEST:
default:
NOTREACHED();
return blink::mojom::InputEventResultState::kUnknown;
}
}
} // namespace
......@@ -257,7 +260,11 @@ void WidgetInputHandlerManager::DispatchNonBlockingEventToMainThread(
void WidgetInputHandlerManager::FindScrollTargetOnMainThread(
const gfx::PointF& point,
ElementAtPointCallback callback) {
TRACE_EVENT2("input",
"WidgetInputHandlerManager::FindScrollTargetOnMainThread",
"point.x", point.x(), "point.y", point.y());
DCHECK(main_thread_task_runner_->BelongsToCurrentThread());
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
uint64_t element_id =
render_widget_->GetHitTestResultAtPoint(point).GetScrollableContainerId();
......@@ -592,6 +599,34 @@ void WidgetInputHandlerManager::DispatchDirectlyToWidget(
render_widget_->HandleInputEvent(*event, std::move(send_callback));
}
void WidgetInputHandlerManager::FindScrollTargetReply(
std::unique_ptr<blink::WebCoalescedInputEvent> event,
blink::mojom::WidgetInputHandler::DispatchEventCallback browser_callback,
uint64_t hit_test_result) {
TRACE_EVENT1("input", "WidgetInputHandlerManager::FindScrollTargetReply",
"hit_test_result", hit_test_result);
DCHECK(InputThreadTaskRunner()->BelongsToCurrentThread());
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
// If the input_handler was destroyed in the mean time just ACK the event as
// unconsumed to the browser and drop further handling.
if (!input_handler_proxy_) {
std::move(browser_callback)
.Run(blink::mojom::InputEventResultSource::kMainThread,
ui::LatencyInfo(),
blink::mojom::InputEventResultState::kNotConsumed, nullptr,
nullptr);
return;
}
input_handler_proxy_->ContinueScrollBeginAfterMainThreadHitTest(
std::move(event),
base::BindOnce(
&WidgetInputHandlerManager::DidHandleInputEventSentToCompositor, this,
std::move(browser_callback)),
hit_test_result);
}
void WidgetInputHandlerManager::DidHandleInputEventSentToCompositor(
blink::mojom::WidgetInputHandler::DispatchEventCallback callback,
blink::InputHandlerProxy::EventDisposition event_disposition,
......@@ -608,6 +643,34 @@ void WidgetInputHandlerManager::DidHandleInputEventSentToCompositor(
{event->latency_info()},
ChromeLatencyInfo::STEP_DID_HANDLE_INPUT_AND_OVERSCROLL);
if (event_disposition ==
blink::InputHandlerProxy::REQUIRES_MAIN_THREAD_HIT_TEST) {
TRACE_EVENT_INSTANT0("input", "PostingHitTestToMainThread",
TRACE_EVENT_SCOPE_THREAD);
// TODO(bokan): We're going to need to perform a hit test on the main thread
// before we can continue handling the event. This is the critical path of a
// scroll so we should probably ensure the scheduler can prioritize it
// accordingly. https://crbug.com/1082618.
DCHECK(base::FeatureList::IsEnabled(::features::kScrollUnification));
DCHECK_EQ(event->Event().GetType(),
blink::WebInputEvent::Type::kGestureScrollBegin);
DCHECK(input_handler_proxy_);
gfx::PointF event_position =
static_cast<const blink::WebGestureEvent&>(event->Event())
.PositionInWidget();
ElementAtPointCallback result_callback = base::BindOnce(
&WidgetInputHandlerManager::FindScrollTargetReply, this->AsWeakPtr(),
std::move(event), std::move(callback));
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WidgetInputHandlerManager::FindScrollTargetOnMainThread,
this, event_position, std::move(result_callback)));
return;
}
blink::mojom::InputEventResultState ack_state =
InputEventDispositionToAck(event_disposition);
if (ack_state == blink::mojom::InputEventResultState::kConsumed) {
......
......@@ -173,6 +173,13 @@ class CONTENT_EXPORT WidgetInputHandlerManager final
std::unique_ptr<blink::WebCoalescedInputEvent> event,
blink::mojom::WidgetInputHandler::DispatchEventCallback callback);
// Used to return a result from FindScrollTargetOnMainThread. Will be called
// on the input handling thread.
void FindScrollTargetReply(
std::unique_ptr<blink::WebCoalescedInputEvent> event,
blink::mojom::WidgetInputHandler::DispatchEventCallback browser_callback,
uint64_t hit_test_result);
// This method is the callback used by the compositor input handler to
// communicate back whether the event was successfully handled on the
// compositor thread or whether it needs to forwarded to the main thread.
......
......@@ -76,7 +76,10 @@ class BLINK_COMMON_EXPORT WebGestureEvent : public WebInputEvent {
// a hit-test. Should be used for gestures queued up internally within
// the renderer process. This is an ElementIdType instead of ElementId
// due to the fact that ElementId has a non-trivial constructor that
// can't easily participate in this union of structs.
// can't easily participate in this union of structs. Note that while
// this is used in scroll unification to perform a main thread hit test,
// in which case |main_thread_hit_tested| is true, it is also used in
// other cases like scroll events reinjected for scrollbar scrolling.
cc::ElementIdType scrollable_area_element_id;
// Initial motion that triggered the scroll.
float delta_x_hint;
......@@ -95,6 +98,11 @@ class BLINK_COMMON_EXPORT WebGestureEvent : public WebInputEvent {
// True if this event is generated from a wheel event with synthetic
// phase.
bool synthetic;
// If true, this event has been hit tested by the main thread and the
// result is stored in scrollable_area_element_id. Used only in scroll
// unification when the event is sent back the the compositor for a
// second time after the main thread hit test is complete.
bool main_thread_hit_tested;
} scroll_begin;
struct {
......
......@@ -10,6 +10,7 @@
#include "base/macros.h"
#include "cc/input/input_handler.h"
#include "cc/input/snap_fling_controller.h"
#include "cc/paint/element_id.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/platform/input/elastic_overscroll_controller.h"
......@@ -37,6 +38,7 @@ class InputHandlerProxyTest;
class InputHandlerProxyEventQueueTest;
class InputHandlerProxyMomentumScrollJankTest;
class TestInputHandlerProxy;
class UnifiedScrollingInputHandlerProxyTest;
} // namespace test
class CompositorThreadEventQueue;
......@@ -125,6 +127,11 @@ class BLINK_PLATFORM_EXPORT InputHandlerProxy
// pass it to the next consumer (either overscrolling or bubbling the event
// to the next renderer).
DID_HANDLE_SHOULD_BUBBLE,
// Used only in scroll unification; the compositor couldn't determine the
// scroll node to handle the event and requires a second try with an
// ElementId provided by a hit test in Blink.
REQUIRES_MAIN_THREAD_HIT_TEST,
};
using EventDispositionCallback = base::OnceCallback<void(
EventDisposition,
......@@ -134,6 +141,17 @@ class BLINK_PLATFORM_EXPORT InputHandlerProxy
void HandleInputEventWithLatencyInfo(
std::unique_ptr<blink::WebCoalescedInputEvent> event,
EventDispositionCallback callback);
// In scroll unification, a scroll begin event may initially return unhandled
// due to requiring the main thread to perform a hit test. In that case, the
// client will perform the hit test by calling into Blink. When it has a
// result, it can try handling the event again by calling back through this
// method.
void ContinueScrollBeginAfterMainThreadHitTest(
std::unique_ptr<blink::WebCoalescedInputEvent> event,
EventDispositionCallback callback,
cc::ElementIdType hit_tests_result);
void InjectScrollbarGestureScroll(
const blink::WebInputEvent::Type type,
const gfx::PointF& position_in_widget,
......@@ -194,6 +212,7 @@ class BLINK_PLATFORM_EXPORT InputHandlerProxy
private:
friend class test::TestInputHandlerProxy;
friend class test::InputHandlerProxyTest;
friend class test::UnifiedScrollingInputHandlerProxyTest;
friend class test::InputHandlerProxyEventQueueTest;
friend class test::InputHandlerProxyMomentumScrollJankTest;
......@@ -316,6 +335,14 @@ class BLINK_PLATFORM_EXPORT InputHandlerProxy
bool skip_touch_filter_discrete_ = false;
bool skip_touch_filter_all_ = false;
// This bit is set when the input handler proxy has requested that the client
// perform a hit test for a scroll begin on the main thread. During that
// time, scroll updates need to be queued. The reply from the main thread
// will come by calling ContinueScrollBeginAfterMainThreadHitTest where the
// queue will be flushed and this bit cleared. Used only in scroll
// unification.
bool hit_testing_scroll_begin_on_main_thread_ = false;
// Helpers for the momentum scroll jank UMAs.
std::unique_ptr<MomentumScrollJankTracker> momentum_scroll_jank_tracker_;
......
......@@ -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",
......
......@@ -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;
......@@ -347,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) {
......@@ -375,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();
}
......@@ -419,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())
......@@ -903,14 +996,11 @@ 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) {
// TODO(bokan): This will have to be implemented on the main thread for
// scroll unification. Perhaps using a percent-based scroll?
// https://crbug.com/1082589.
scroll_status.thread = cc::InputHandler::SCROLL_ON_MAIN_THREAD;
scroll_status.main_thread_scrolling_reasons =
cc::MainThreadScrollingReason::kContinuingMainThreadScroll;
......@@ -921,6 +1011,16 @@ InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin(
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 +1050,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());
......@@ -1027,6 +1130,13 @@ InputHandlerProxy::HandleGestureScrollUpdate(
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()));
......@@ -1290,6 +1400,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();
......
......@@ -65,8 +65,12 @@ bool ScrollInputHandler::OnScrollEvent(const ScrollEvent& event,
// Note: the WHEEL type covers both actual wheels as well as trackpad
// scrolling.
input_handler_weak_ptr_->ScrollBegin(&scroll_state_begin,
ui::ScrollInputType::kWheel);
cc::InputHandler::ScrollStatus result = input_handler_weak_ptr_->ScrollBegin(
&scroll_state_begin, ui::ScrollInputType::kWheel);
// Falling back to the main thread should never be required when an explicit
// ElementId is provided.
DCHECK(!result.needs_main_thread_hit_test);
cc::ScrollState scroll_state = CreateScrollState(event, false);
input_handler_weak_ptr_->ScrollUpdate(&scroll_state, base::TimeDelta());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment