Commit 8a650314 authored by Rahul Arakeri's avatar Rahul Arakeri Committed by Commit Bot

[Reland] Fix for thumb drag and pointer offset.

The original CL was reverted because one of the content_browsertests
(ScrollLatencyCompositedScrollbarBrowserTest.ScrollbarThumbDragLatency)
got flaky. This failure was not caught because all TryJobs passed the
dry run on retries (crbug.com/988443)

Reason for the flakiness:
This CL exposed a pre existing race condition. The issue only now came
to light because this CL changes how thumb drag takes place.
ScrollbarController::HandleMouseMove now calls ComputeScrollDelta
for calculating the clamped delta. ComputeScrollDelta is where execution
aborts due to the scroll_node being null. The reason the scroll_node is
sometimes null here is because the mousemove reaches ComputeScrollDelta
before GSB has had a chance to set up the scroll_node (due to queueing).
See crbug.com/988308 for more details.

Reverted CL is crrev.com/c/1684532
Reverted CL is in PS2.
New CL is in PS6.

[Description from the original CL]:
Fixes an issue with thumb drags for compositor threaded scrollbar
scrolling. When you click and drag the thumb past the track and then
reverse your drag direction, the pointer no longer sticks to the thumb
and instead, maintains a constant offset with the thumb as it moves.
The reason this happens is because the scroll offset calculation in the
ScrollbarController simply uses 2 factors to calculate the delta. One
of them is the previous_pointer_position_ and the other is the current
pointer location (i.e position_in_widget). There is no logic to check
if the difference between these 2 factors should indeed generate a GSU
or not. The fix is to make the thumb drag delta calculation relative to
the layer and then use LayerTreeHostImpl::CanConsumeDelta to determine
if a scroll should be initiated.

Bug: 1684532
Change-Id: I4a12729ae77725f4a7aac0b02c4b7a3368d8e935
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1722157Reviewed-by: default avatarDavid Bokan <bokan@chromium.org>
Commit-Queue: Rahul Arakeri <arakeri@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#682326}
parent dbd6ef7c
...@@ -25,7 +25,6 @@ ScrollbarController::ScrollbarController( ...@@ -25,7 +25,6 @@ ScrollbarController::ScrollbarController(
LayerTreeHostImpl* layer_tree_host_impl) LayerTreeHostImpl* layer_tree_host_impl)
: layer_tree_host_impl_(layer_tree_host_impl), : layer_tree_host_impl_(layer_tree_host_impl),
scrollbar_scroll_is_active_(false), scrollbar_scroll_is_active_(false),
thumb_drag_in_progress_(false),
autoscroll_state_(AutoScrollState::NO_AUTOSCROLL), autoscroll_state_(AutoScrollState::NO_AUTOSCROLL),
currently_captured_scrollbar_(nullptr), currently_captured_scrollbar_(nullptr),
previous_pointer_position_(gfx::PointF(0, 0)), previous_pointer_position_(gfx::PointF(0, 0)),
...@@ -41,6 +40,21 @@ void ScrollbarController::WillBeginImplFrame() { ...@@ -41,6 +40,21 @@ void ScrollbarController::WillBeginImplFrame() {
layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort();
} }
gfx::Vector2dF ScrollbarController::GetThumbRelativePoint(
const gfx::PointF position_in_widget) {
bool clipped;
const gfx::PointF position_in_layer =
GetScrollbarRelativePosition(position_in_widget, &clipped);
if (clipped)
return gfx::Vector2d(0, 0);
const gfx::RectF thumb_rect(
currently_captured_scrollbar_->ComputeThumbQuadRect());
DCHECK(thumb_rect.Contains(position_in_layer));
return position_in_layer - gfx::PointF(thumb_rect.origin());
}
// Performs hit test and prepares scroll deltas that will be used by GSB and // Performs hit test and prepares scroll deltas that will be used by GSB and
// GSU. // GSU.
InputHandlerPointerResult ScrollbarController::HandleMouseDown( InputHandlerPointerResult ScrollbarController::HandleMouseDown(
...@@ -56,16 +70,16 @@ InputHandlerPointerResult ScrollbarController::HandleMouseDown( ...@@ -56,16 +70,16 @@ InputHandlerPointerResult ScrollbarController::HandleMouseDown(
currently_captured_scrollbar_ = layer_impl->ToScrollbarLayer(); currently_captured_scrollbar_ = layer_impl->ToScrollbarLayer();
scroll_result.type = PointerResultType::kScrollbarScroll; scroll_result.type = PointerResultType::kScrollbarScroll;
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
ScrollbarPart scrollbar_part = const ScrollbarPart scrollbar_part =
GetScrollbarPartFromPointerDown(position_in_widget); GetScrollbarPartFromPointerDown(position_in_widget);
scroll_result.scroll_offset = GetScrollOffsetForScrollbarPart( scroll_result.scroll_offset = GetScrollOffsetForScrollbarPart(
scrollbar_part, currently_captured_scrollbar_->orientation()); scrollbar_part, currently_captured_scrollbar_->orientation());
previous_pointer_position_ = position_in_widget; previous_pointer_position_ = position_in_widget;
scrollbar_scroll_is_active_ = true; scrollbar_scroll_is_active_ = true;
if (scrollbar_part == ScrollbarPart::THUMB) { if (scrollbar_part == ScrollbarPart::THUMB) {
thumb_drag_in_progress_ = true;
scroll_result.scroll_units = scroll_result.scroll_units =
ui::input_types::ScrollGranularity::kScrollByPrecisePixel; ui::input_types::ScrollGranularity::kScrollByPrecisePixel;
drag_anchor_relative_to_thumb_ = GetThumbRelativePoint(position_in_widget);
} else { } else {
// TODO(arakeri): This needs to be updated to kLine once cc implements // TODO(arakeri): This needs to be updated to kLine once cc implements
// handling it. crbug.com/959441 // handling it. crbug.com/959441
...@@ -73,10 +87,12 @@ InputHandlerPointerResult ScrollbarController::HandleMouseDown( ...@@ -73,10 +87,12 @@ InputHandlerPointerResult ScrollbarController::HandleMouseDown(
ui::input_types::ScrollGranularity::kScrollByPixel; ui::input_types::ScrollGranularity::kScrollByPixel;
} }
// Thumb drag is the only scrollbar manipulation that cannot produce an if (!scroll_result.scroll_offset.IsZero()) {
// autoscroll. All other interactions like clicking on arrows/trackparts have // Thumb drag is the only scrollbar manipulation that cannot produce an
// the potential of initiating an autoscroll (if held down long enough). // autoscroll. All other interactions like clicking on arrows/trackparts
if (!scroll_result.scroll_offset.IsZero() && !thumb_drag_in_progress_) { // have the potential of initiating an autoscroll (if held down for long
// enough).
DCHECK(scrollbar_part != ScrollbarPart::THUMB);
cancelable_autoscroll_task_ = std::make_unique<base::CancelableClosure>( cancelable_autoscroll_task_ = std::make_unique<base::CancelableClosure>(
base::Bind(&ScrollbarController::StartAutoScrollAnimation, base::Bind(&ScrollbarController::StartAutoScrollAnimation,
base::Unretained(this), scroll_result.scroll_offset, base::Unretained(this), scroll_result.scroll_offset,
...@@ -89,29 +105,79 @@ InputHandlerPointerResult ScrollbarController::HandleMouseDown( ...@@ -89,29 +105,79 @@ InputHandlerPointerResult ScrollbarController::HandleMouseDown(
return scroll_result; return scroll_result;
} }
gfx::ScrollOffset ScrollbarController::GetScrollOffsetForDragPosition(
const gfx::PointF pointer_position_in_widget) {
layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
const gfx::Rect thumb_rect(
currently_captured_scrollbar_->ComputeThumbQuadRect());
const gfx::PointF drag_position_relative_to_layer =
gfx::PointF(thumb_rect.origin()) + drag_anchor_relative_to_thumb_.value();
bool clipped = false;
const gfx::PointF pointer_position_in_layer =
GetScrollbarRelativePosition(pointer_position_in_widget, &clipped);
if (clipped)
return gfx::ScrollOffset(0, 0);
// Calculate the delta based on the previously known thumb drag point.
const gfx::Vector2dF pointer_delta =
pointer_position_in_layer - drag_position_relative_to_layer;
gfx::ScrollOffset scaled_thumb_drag_delta;
const ScrollbarOrientation orientation =
currently_captured_scrollbar_->orientation();
orientation == ScrollbarOrientation::VERTICAL
? scaled_thumb_drag_delta.set_y(pointer_delta.y())
: scaled_thumb_drag_delta.set_x(pointer_delta.x());
float scaled_scroller_to_scrollbar_ratio = GetScrollerToScrollbarRatio();
scaled_thumb_drag_delta.Scale(scaled_scroller_to_scrollbar_ratio);
return scaled_thumb_drag_delta;
}
// Performs hit test and prepares scroll deltas that will be used by GSU. // Performs hit test and prepares scroll deltas that will be used by GSU.
InputHandlerPointerResult ScrollbarController::HandleMouseMove( InputHandlerPointerResult ScrollbarController::HandleMouseMove(
const gfx::PointF position_in_widget) { const gfx::PointF position_in_widget) {
const gfx::PointF previous_pointer_position = previous_pointer_position_;
previous_pointer_position_ = position_in_widget; previous_pointer_position_ = position_in_widget;
InputHandlerPointerResult scroll_result; InputHandlerPointerResult scroll_result;
if (!thumb_drag_in_progress_)
// If a thumb drag is not in progress, there's no point in continuing on.
if (!drag_anchor_relative_to_thumb_.has_value())
return scroll_result;
const ScrollNode* currently_scrolling_node =
layer_tree_host_impl_->CurrentlyScrollingNode();
// Thumb drag needs a scroll_node. Clear the thumb drag state and exit if it
// is unset.
if (currently_scrolling_node == nullptr) {
drag_anchor_relative_to_thumb_ = base::nullopt;
return scroll_result; return scroll_result;
}
// If scroll_offset can't be consumed, there's no point in continuing on.
const gfx::ScrollOffset scroll_offset(
GetScrollOffsetForDragPosition(position_in_widget));
const gfx::Vector2dF clamped_scroll_offset(
layer_tree_host_impl_->ComputeScrollDelta(
*currently_scrolling_node, ScrollOffsetToVector2dF(scroll_offset)));
if (clamped_scroll_offset.IsZero())
return scroll_result;
// Thumb drags have more granularity and are purely dependent on the pointer
// movement. Hence we use kPrecisePixel when dragging the thumb.
scroll_result.scroll_units =
ui::input_types::ScrollGranularity::kScrollByPrecisePixel;
scroll_result.type = PointerResultType::kScrollbarScroll; scroll_result.type = PointerResultType::kScrollbarScroll;
const ScrollbarOrientation orientation = scroll_result.scroll_offset = gfx::ScrollOffset(clamped_scroll_offset);
currently_captured_scrollbar_->orientation();
if (orientation == ScrollbarOrientation::VERTICAL) return scroll_result;
scroll_result.scroll_offset.set_y(position_in_widget.y() - }
previous_pointer_position.y());
else
scroll_result.scroll_offset.set_x(position_in_widget.x() -
previous_pointer_position.x());
LayerImpl* owner_scroll_layer =
layer_tree_host_impl_->active_tree()->ScrollableLayerByElementId(
currently_captured_scrollbar_->scroll_element_id());
float ScrollbarController::GetScrollerToScrollbarRatio() {
// Calculating the delta by which the scroller layer should move when // Calculating the delta by which the scroller layer should move when
// dragging the thumb depends on the following factors: // dragging the thumb depends on the following factors:
// - scrollbar_track_length // - scrollbar_track_length
...@@ -155,10 +221,15 @@ InputHandlerPointerResult ScrollbarController::HandleMouseMove( ...@@ -155,10 +221,15 @@ InputHandlerPointerResult ScrollbarController::HandleMouseMove(
currently_captured_scrollbar_->scroll_layer_length(); currently_captured_scrollbar_->scroll_layer_length();
float scrollbar_track_length = currently_captured_scrollbar_->TrackLength(); float scrollbar_track_length = currently_captured_scrollbar_->TrackLength();
gfx::Rect thumb_rect(currently_captured_scrollbar_->ComputeThumbQuadRect()); gfx::Rect thumb_rect(currently_captured_scrollbar_->ComputeThumbQuadRect());
const ScrollbarOrientation orientation =
currently_captured_scrollbar_->orientation();
float scrollbar_thumb_length = orientation == ScrollbarOrientation::VERTICAL float scrollbar_thumb_length = orientation == ScrollbarOrientation::VERTICAL
? thumb_rect.height() ? thumb_rect.height()
: thumb_rect.width(); : thumb_rect.width();
const LayerImpl* owner_scroll_layer =
layer_tree_host_impl_->active_tree()->ScrollableLayerByElementId(
currently_captured_scrollbar_->scroll_element_id());
float viewport_length = float viewport_length =
orientation == ScrollbarOrientation::VERTICAL orientation == ScrollbarOrientation::VERTICAL
? owner_scroll_layer->scroll_container_bounds().height() ? owner_scroll_layer->scroll_container_bounds().height()
...@@ -167,13 +238,17 @@ InputHandlerPointerResult ScrollbarController::HandleMouseMove( ...@@ -167,13 +238,17 @@ InputHandlerPointerResult ScrollbarController::HandleMouseMove(
((scroll_layer_length - viewport_length) / ((scroll_layer_length - viewport_length) /
(scrollbar_track_length - scrollbar_thumb_length)) * (scrollbar_track_length - scrollbar_thumb_length)) *
layer_tree_host_impl_->active_tree()->device_scale_factor(); layer_tree_host_impl_->active_tree()->device_scale_factor();
scroll_result.scroll_offset.Scale(scaled_scroller_to_scrollbar_ratio);
// Thumb drags have more granularity and are purely dependent on the pointer
// movement. Hence we use kPrecisePixel when dragging the thumb.
scroll_result.scroll_units =
ui::input_types::ScrollGranularity::kScrollByPrecisePixel;
return scroll_result; // Avoid precision loss later on by rounding up 3 decimal places here.
// TODO(arakeri): Revisit this while fixing crbug.com/986174. There is a
// precision loss that is happening somewhere which affects root scrollbars
// only. Even though it is not visible to the end user, without rounding up,
// the scrolling tests will fail due to this precision loss.
DCHECK_GT(scaled_scroller_to_scrollbar_ratio, 0);
scaled_scroller_to_scrollbar_ratio =
ceil(scaled_scroller_to_scrollbar_ratio * 1000.0) / 1000.0;
return scaled_scroller_to_scrollbar_ratio;
} }
bool ScrollbarController::ShouldCancelTrackAutoscroll() { bool ScrollbarController::ShouldCancelTrackAutoscroll() {
...@@ -285,7 +360,7 @@ InputHandlerPointerResult ScrollbarController::HandleMouseUp( ...@@ -285,7 +360,7 @@ InputHandlerPointerResult ScrollbarController::HandleMouseUp(
cancelable_autoscroll_task_.reset(); cancelable_autoscroll_task_.reset();
} }
thumb_drag_in_progress_ = false; drag_anchor_relative_to_thumb_ = base::nullopt;
autoscroll_state_ = AutoScrollState::NO_AUTOSCROLL; autoscroll_state_ = AutoScrollState::NO_AUTOSCROLL;
return scroll_result; return scroll_result;
} }
......
...@@ -58,6 +58,18 @@ class CC_EXPORT ScrollbarController { ...@@ -58,6 +58,18 @@ class CC_EXPORT ScrollbarController {
// Decides whether a track autoscroll should be aborted. // Decides whether a track autoscroll should be aborted.
bool ShouldCancelTrackAutoscroll(); bool ShouldCancelTrackAutoscroll();
// Calculates the scroll_offset based on position_in_widget and
// drag_anchor_relative_to_thumb_.
gfx::ScrollOffset GetScrollOffsetForDragPosition(
const gfx::PointF pointer_position_in_widget);
// Returns a Vector2dF for position_in_widget relative to the scrollbar thumb.
gfx::Vector2dF GetThumbRelativePoint(const gfx::PointF position_in_widget);
// Returns the ratio of the scroller length to the scrollbar length. This is
// needed to scale the scroll delta for thumb drag.
float GetScrollerToScrollbarRatio();
LayerTreeHostImpl* layer_tree_host_impl_; LayerTreeHostImpl* layer_tree_host_impl_;
// Used to safeguard against firing GSE without firing GSB and GSU. For // Used to safeguard against firing GSE without firing GSB and GSU. For
...@@ -65,9 +77,6 @@ class CC_EXPORT ScrollbarController { ...@@ -65,9 +77,6 @@ class CC_EXPORT ScrollbarController {
// moving inside the scrollbar, a GSE will get queued up without this flag. // moving inside the scrollbar, a GSE will get queued up without this flag.
bool scrollbar_scroll_is_active_; bool scrollbar_scroll_is_active_;
// Used to tell if the scrollbar thumb is getting dragged.
bool thumb_drag_in_progress_;
// "Autoscroll" here means the continuous scrolling that occurs when the // "Autoscroll" here means the continuous scrolling that occurs when the
// pointer is held down on a hit-testable area of the scrollbar such as an // pointer is held down on a hit-testable area of the scrollbar such as an
// arrows of the track itself. // arrows of the track itself.
...@@ -77,6 +86,10 @@ class CC_EXPORT ScrollbarController { ...@@ -77,6 +86,10 @@ class CC_EXPORT ScrollbarController {
// This is relative to the RenderWidget's origin. // This is relative to the RenderWidget's origin.
gfx::PointF previous_pointer_position_; gfx::PointF previous_pointer_position_;
// This is used to track the pointer location relative to the thumb origin
// when a drag has started.
base::Optional<gfx::Vector2dF> drag_anchor_relative_to_thumb_;
std::unique_ptr<base::CancelableClosure> cancelable_autoscroll_task_; std::unique_ptr<base::CancelableClosure> cancelable_autoscroll_task_;
}; };
......
...@@ -140,6 +140,12 @@ window.onload = () => { ...@@ -140,6 +140,12 @@ window.onload = () => {
await mouseDownAt(x, y); await mouseDownAt(x, y);
assert_equals(standardDivFast.scrollTop, 0, "Mousedown on vertical scrollbar thumb is not expected to scroll."); assert_equals(standardDivFast.scrollTop, 0, "Mousedown on vertical scrollbar thumb is not expected to scroll.");
await mouseMoveTo(x, y-10);
assert_equals(standardDivFast.scrollTop, 0, "Vertical thumb drag beyond the track should not cause a scroll.");
await mouseMoveTo(x, y);
assert_equals(standardDivFast.scrollTop, 0, "Vertical thumb drag beyond the track and back should not cause a scroll.");
y += SCROLL_DELTA; y += SCROLL_DELTA;
await mouseMoveTo(x, y); await mouseMoveTo(x, y);
assert_equals(standardDivFast.scrollTop, 915, "Vertical thumb drag downwards did not scroll as expected."); assert_equals(standardDivFast.scrollTop, 915, "Vertical thumb drag downwards did not scroll as expected.");
...@@ -159,6 +165,12 @@ window.onload = () => { ...@@ -159,6 +165,12 @@ window.onload = () => {
await mouseDownAt(x, y); await mouseDownAt(x, y);
assert_equals(standardDivFast.scrollLeft, 0, "Mousedown on horizontal scrollbar thumb is not expected to scroll."); assert_equals(standardDivFast.scrollLeft, 0, "Mousedown on horizontal scrollbar thumb is not expected to scroll.");
await mouseMoveTo(x-10, y);
assert_equals(standardDivFast.scrollLeft, 0, "Horizontal thumb drag beyond the track should not cause a scroll.");
await mouseMoveTo(x, y);
assert_equals(standardDivFast.scrollLeft, 0, "Horizontal thumb drag beyond the track and back should not cause a scroll.");
x += SCROLL_DELTA; x += SCROLL_DELTA;
await mouseMoveTo(x, y); await mouseMoveTo(x, y);
assert_equals(standardDivFast.scrollLeft, 915, "Horizontal thumb drag towards the right did not scroll as expected."); assert_equals(standardDivFast.scrollLeft, 915, "Horizontal thumb drag towards the right did not scroll as expected.");
......
...@@ -85,6 +85,12 @@ window.onload = () => { ...@@ -85,6 +85,12 @@ window.onload = () => {
await mouseDownAt(x, y); await mouseDownAt(x, y);
assert_equals(window.scrollY, 0, "Mousedown on vertical scrollbar thumb is not expected to scroll."); assert_equals(window.scrollY, 0, "Mousedown on vertical scrollbar thumb is not expected to scroll.");
await mouseMoveTo(x, y-10);
assert_equals(window.scrollY, 0, "Vertical thumb drag beyond the track should not cause a scroll.");
await mouseMoveTo(x, y);
assert_equals(window.scrollY, 0, "Vertical thumb drag beyond the track and back should not cause a scroll.");
y += SCROLL_DELTA; y += SCROLL_DELTA;
await mouseMoveTo(x, y); await mouseMoveTo(x, y);
// TODO(arakeri): There is currently a 1 px difference when dragging the thumb on the compositor thread // TODO(arakeri): There is currently a 1 px difference when dragging the thumb on the compositor thread
...@@ -107,6 +113,12 @@ window.onload = () => { ...@@ -107,6 +113,12 @@ window.onload = () => {
await mouseDownAt(x, y); await mouseDownAt(x, y);
assert_equals(window.scrollX, 0, "Mousedown on horizontal scrollbar thumb is not expected to scroll."); assert_equals(window.scrollX, 0, "Mousedown on horizontal scrollbar thumb is not expected to scroll.");
await mouseMoveTo(x-10, y);
assert_equals(window.scrollX, 0, "Horizontal thumb drag beyond the track should not cause a scroll.");
await mouseMoveTo(x, y);
assert_equals(window.scrollX, 0, "Horizontal thumb drag beyond the track and back should not cause a scroll.");
x += SCROLL_DELTA; x += SCROLL_DELTA;
await mouseMoveTo(x, y); await mouseMoveTo(x, y);
assert_approx_equals(window.scrollX, 133, 1, "Horizontal thumb drag towards the right did not scroll as expected."); assert_approx_equals(window.scrollX, 133, 1, "Horizontal thumb drag towards the right did not scroll as expected.");
......
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