Commit 3bfc0979 authored by Robert Flack's avatar Robert Flack Committed by Commit Bot

Preserve pressed part of scrollbar when scroll anchoring.

When we are applying scroll anchoring we should not update the pressed
part of the scrollbar as it leads to unstable scrolling.

Bug: 1043674
Change-Id: Id19f33626be2c2607351bb208f1b4f340477db8c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2010053
Commit-Queue: Robert Flack <flackr@chromium.org>
Reviewed-by: default avatarDavid Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#733975}
parent 8ef0da96
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "third_party/blink/renderer/core/layout/scroll_anchor.h" #include "third_party/blink/renderer/core/layout/scroll_anchor.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/renderer/core/dom/static_node_list.h" #include "third_party/blink/renderer/core/dom/static_node_list.h"
#include "third_party/blink/renderer/core/frame/root_frame_viewport.h" #include "third_party/blink/renderer/core/frame/root_frame_viewport.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/frame/visual_viewport.h"
...@@ -28,6 +29,11 @@ class ScrollAnchorTest : public testing::WithParamInterface<bool>, ...@@ -28,6 +29,11 @@ class ScrollAnchorTest : public testing::WithParamInterface<bool>,
ScrollAnchorTest() : ScopedLayoutNGForTest(GetParam()) {} ScrollAnchorTest() : ScopedLayoutNGForTest(GetParam()) {}
protected: protected:
void SetUp() override {
EnableCompositing();
RenderingTest::SetUp();
}
void Update() { void Update() {
// TODO(skobes): Use SimTest instead of RenderingTest and move into // TODO(skobes): Use SimTest instead of RenderingTest and move into
// Source/web? // Source/web?
...@@ -79,6 +85,52 @@ class ScrollAnchorTest : public testing::WithParamInterface<bool>, ...@@ -79,6 +85,52 @@ class ScrollAnchorTest : public testing::WithParamInterface<bool>,
GetDocument().QuerySelectorAll(AtomicString(serialized.selector)); GetDocument().QuerySelectorAll(AtomicString(serialized.selector));
EXPECT_EQ(ele_list->length(), 1u); EXPECT_EQ(ele_list->length(), 1u);
} }
Scrollbar* VerticalScrollbarForElement(Element* element) {
return ToLayoutBox(element->GetLayoutObject())
->GetScrollableArea()
->VerticalScrollbar();
}
void MouseDownOnVerticalScrollbar(Scrollbar* scrollbar) {
DCHECK_EQ(true, scrollbar->GetTheme().AllowsHitTest());
int thumb_center = scrollbar->GetTheme().ThumbPosition(*scrollbar) +
scrollbar->GetTheme().ThumbLength(*scrollbar) / 2;
scrollbar_drag_point_ =
gfx::PointF(scrollbar->GetScrollableArea()
->ConvertFromScrollbarToContainingEmbeddedContentView(
*scrollbar, IntPoint(0, thumb_center)));
scrollbar->MouseDown(blink::WebMouseEvent(
blink::WebInputEvent::kMouseDown, *scrollbar_drag_point_,
*scrollbar_drag_point_, blink::WebPointerProperties::Button::kLeft, 0,
blink::WebInputEvent::kNoModifiers, base::TimeTicks::Now()));
}
void MouseDragVerticalScrollbar(Scrollbar* scrollbar, float scroll_delta_y) {
DCHECK(scrollbar_drag_point_);
ScrollableArea* scroller = scrollbar->GetScrollableArea();
scrollbar_drag_point_->Offset(
0, scroll_delta_y *
(scrollbar->GetTheme().TrackLength(*scrollbar) -
scrollbar->GetTheme().ThumbLength(*scrollbar)) /
(scroller->MaximumScrollOffset().Height() -
scroller->MinimumScrollOffset().Height()));
scrollbar->MouseMoved(blink::WebMouseEvent(
blink::WebInputEvent::kMouseMove, *scrollbar_drag_point_,
*scrollbar_drag_point_, blink::WebPointerProperties::Button::kLeft, 0,
blink::WebInputEvent::kNoModifiers, base::TimeTicks::Now()));
}
void MouseUpOnVerticalScrollbar(Scrollbar* scrollbar) {
DCHECK(scrollbar_drag_point_);
scrollbar->MouseDown(blink::WebMouseEvent(
blink::WebInputEvent::kMouseUp, *scrollbar_drag_point_,
*scrollbar_drag_point_, blink::WebPointerProperties::Button::kLeft, 0,
blink::WebInputEvent::kNoModifiers, base::TimeTicks::Now()));
scrollbar_drag_point_.reset();
}
base::Optional<gfx::PointF> scrollbar_drag_point_;
}; };
INSTANTIATE_TEST_SUITE_P(All, ScrollAnchorTest, testing::Bool()); INSTANTIATE_TEST_SUITE_P(All, ScrollAnchorTest, testing::Bool());
...@@ -328,6 +380,51 @@ TEST_P(ScrollAnchorTest, AnchorWithLayerInScrollingDiv) { ...@@ -328,6 +380,51 @@ TEST_P(ScrollAnchorTest, AnchorWithLayerInScrollingDiv) {
EXPECT_EQ(250, scroller->ScrollOffsetInt().Height()); EXPECT_EQ(250, scroller->ScrollOffsetInt().Height());
} }
TEST_P(ScrollAnchorTest, AnchorWhileDraggingScrollbar) {
// Dragging the scrollbar is inherently inaccurate. Allow many pixels slop in
// the scroll position.
const int kScrollbarDragAccuracy = 10;
USE_NON_OVERLAY_SCROLLBARS();
SetBodyInnerHTML(R"HTML(
<style>
#scroller { overflow: scroll; width: 500px; height: 400px; }
div { height: 100px }
#block2 { overflow: hidden }
#space { height: 1000px; }
</style>
<div id='scroller'><div id='space'>
<div id='block1'>abc</div>
<div id='block2'>def</div>
</div></div>
)HTML");
Element* scroller_element = GetDocument().getElementById("scroller");
ScrollableArea* scroller = ScrollerForElement(scroller_element);
Element* block1 = GetDocument().getElementById("block1");
Element* block2 = GetDocument().getElementById("block2");
Scrollbar* scrollbar = VerticalScrollbarForElement(scroller_element);
scroller->MouseEnteredScrollbar(*scrollbar);
MouseDownOnVerticalScrollbar(scrollbar);
MouseDragVerticalScrollbar(scrollbar, 150);
EXPECT_NEAR(150, scroller->GetScrollOffset().Height(),
kScrollbarDragAccuracy);
// In this layout pass we will anchor to #block2 which has its own PaintLayer.
SetHeight(block1, 200);
EXPECT_NEAR(250, scroller->ScrollOffsetInt().Height(),
kScrollbarDragAccuracy);
EXPECT_EQ(block2->GetLayoutObject(),
GetScrollAnchor(scroller).AnchorObject());
// If we continue dragging the scroller should scroll from the newly anchored
// position.
MouseDragVerticalScrollbar(scrollbar, 10);
EXPECT_NEAR(260, scroller->ScrollOffsetInt().Height(),
kScrollbarDragAccuracy);
MouseUpOnVerticalScrollbar(scrollbar);
}
// Verify that a nested scroller with a div that has its own PaintLayer can be // Verify that a nested scroller with a div that has its own PaintLayer can be
// removed without causing a crash. This test passes if it doesn't crash. // removed without causing a crash. This test passes if it doesn't crash.
TEST_P(ScrollAnchorTest, RemoveScrollerWithLayerInScrollingDiv) { TEST_P(ScrollAnchorTest, RemoveScrollerWithLayerInScrollingDiv) {
......
...@@ -379,9 +379,9 @@ void ScrollableArea::ScrollOffsetChanged(const ScrollOffset& offset, ...@@ -379,9 +379,9 @@ void ScrollableArea::ScrollOffsetChanged(const ScrollOffset& offset,
// invalidated to reflect the new thumb offset, even if the theme did not // invalidated to reflect the new thumb offset, even if the theme did not
// invalidate any individual part. // invalidate any individual part.
if (Scrollbar* horizontal_scrollbar = this->HorizontalScrollbar()) if (Scrollbar* horizontal_scrollbar = this->HorizontalScrollbar())
horizontal_scrollbar->OffsetDidChange(); horizontal_scrollbar->OffsetDidChange(scroll_type);
if (Scrollbar* vertical_scrollbar = this->VerticalScrollbar()) if (Scrollbar* vertical_scrollbar = this->VerticalScrollbar())
vertical_scrollbar->OffsetDidChange(); vertical_scrollbar->OffsetDidChange(scroll_type);
ScrollOffset delta = GetScrollOffset() - old_offset; ScrollOffset delta = GetScrollOffset() - old_offset;
// TODO(skobes): Should we exit sooner when the offset has not changed? // TODO(skobes): Should we exit sooner when the offset has not changed?
......
...@@ -141,7 +141,7 @@ int Scrollbar::Maximum() const { ...@@ -141,7 +141,7 @@ int Scrollbar::Maximum() const {
: max_offset.Height(); : max_offset.Height();
} }
void Scrollbar::OffsetDidChange() { void Scrollbar::OffsetDidChange(ScrollType scroll_type) {
DCHECK(scrollable_area_); DCHECK(scrollable_area_);
float position = ScrollableAreaCurrentPos(); float position = ScrollableAreaCurrentPos();
...@@ -157,9 +157,13 @@ void Scrollbar::OffsetDidChange() { ...@@ -157,9 +157,13 @@ void Scrollbar::OffsetDidChange() {
position); position);
SetNeedsPaintInvalidation(invalid_parts); SetNeedsPaintInvalidation(invalid_parts);
if (pressed_part_ == kThumbPart) // Don't update the pressed position if scroll anchoring takes place as
// otherwise the next thumb movement will undo anchoring.
if (pressed_part_ == kThumbPart &&
scroll_type != ScrollType::kAnchoringScroll) {
SetPressedPos(pressed_pos_ + GetTheme().ThumbPosition(*this) - SetPressedPos(pressed_pos_ + GetTheme().ThumbPosition(*this) -
old_thumb_position); old_thumb_position);
}
} }
void Scrollbar::DisconnectFromScrollableArea() { void Scrollbar::DisconnectFromScrollableArea() {
......
...@@ -111,7 +111,7 @@ class CORE_EXPORT Scrollbar : public GarbageCollected<Scrollbar>, ...@@ -111,7 +111,7 @@ class CORE_EXPORT Scrollbar : public GarbageCollected<Scrollbar>,
// Called by the ScrollableArea when the scroll offset changes. // Called by the ScrollableArea when the scroll offset changes.
// Will trigger paint invalidation if required. // Will trigger paint invalidation if required.
void OffsetDidChange(); void OffsetDidChange(ScrollType scroll_type);
virtual void DisconnectFromScrollableArea(); virtual void DisconnectFromScrollableArea();
ScrollableArea* GetScrollableArea() const { return scrollable_area_; } ScrollableArea* GetScrollableArea() const { return scrollable_area_; }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h" #include "third_party/blink/renderer/core/scroll/scrollbar_theme_overlay.h"
#include "third_party/blink/renderer/core/scroll/scroll_types.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h" #include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h" #include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
...@@ -58,8 +59,8 @@ TEST_F(ScrollbarThemeOverlayTest, PaintInvalidation) { ...@@ -58,8 +59,8 @@ TEST_F(ScrollbarThemeOverlayTest, PaintInvalidation) {
// should cause a "general" invalidation for non-composited scrollbars. // should cause a "general" invalidation for non-composited scrollbars.
// Ensure the horizontal scrollbar is unaffected. // Ensure the horizontal scrollbar is unaffected.
mock_scrollable_area->UpdateScrollOffset(ScrollOffset(0, 5), kUserScroll); mock_scrollable_area->UpdateScrollOffset(ScrollOffset(0, 5), kUserScroll);
vertical_scrollbar->OffsetDidChange(); vertical_scrollbar->OffsetDidChange(ScrollType::kUserScroll);
horizontal_scrollbar->OffsetDidChange(); horizontal_scrollbar->OffsetDidChange(ScrollType::kUserScroll);
EXPECT_FALSE(vertical_scrollbar->ThumbNeedsRepaint()); EXPECT_FALSE(vertical_scrollbar->ThumbNeedsRepaint());
EXPECT_FALSE(vertical_scrollbar->TrackNeedsRepaint()); EXPECT_FALSE(vertical_scrollbar->TrackNeedsRepaint());
EXPECT_TRUE(mock_scrollable_area->VerticalScrollbarNeedsPaintInvalidation()); EXPECT_TRUE(mock_scrollable_area->VerticalScrollbarNeedsPaintInvalidation());
...@@ -71,8 +72,8 @@ TEST_F(ScrollbarThemeOverlayTest, PaintInvalidation) { ...@@ -71,8 +72,8 @@ TEST_F(ScrollbarThemeOverlayTest, PaintInvalidation) {
// Try the horizontal scrollbar. // Try the horizontal scrollbar.
mock_scrollable_area->ClearNeedsPaintInvalidationForScrollControls(); mock_scrollable_area->ClearNeedsPaintInvalidationForScrollControls();
mock_scrollable_area->UpdateScrollOffset(ScrollOffset(5, 5), kUserScroll); mock_scrollable_area->UpdateScrollOffset(ScrollOffset(5, 5), kUserScroll);
horizontal_scrollbar->OffsetDidChange(); horizontal_scrollbar->OffsetDidChange(ScrollType::kUserScroll);
vertical_scrollbar->OffsetDidChange(); vertical_scrollbar->OffsetDidChange(ScrollType::kUserScroll);
EXPECT_FALSE(vertical_scrollbar->ThumbNeedsRepaint()); EXPECT_FALSE(vertical_scrollbar->ThumbNeedsRepaint());
EXPECT_FALSE(vertical_scrollbar->TrackNeedsRepaint()); EXPECT_FALSE(vertical_scrollbar->TrackNeedsRepaint());
EXPECT_FALSE(mock_scrollable_area->VerticalScrollbarNeedsPaintInvalidation()); EXPECT_FALSE(mock_scrollable_area->VerticalScrollbarNeedsPaintInvalidation());
......
...@@ -7,6 +7,7 @@ include_rules = [ ...@@ -7,6 +7,7 @@ include_rules = [
"+content/renderer/compositor", "+content/renderer/compositor",
"+content/test", "+content/test",
"+gpu/command_buffer/client/gles2_interface.h", "+gpu/command_buffer/client/gles2_interface.h",
"+ui/events/blink/blink_event_util.h"
] ]
specific_include_rules = { specific_include_rules = {
......
...@@ -6,11 +6,13 @@ ...@@ -6,11 +6,13 @@
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h" #include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h" #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h" #include "third_party/blink/renderer/platform/loader/fetch/memory_cache.h"
#include "ui/events/blink/blink_event_util.h"
namespace blink { namespace blink {
...@@ -36,6 +38,27 @@ void LocalFrameClientWithParent::Detached(FrameDetachType) { ...@@ -36,6 +38,27 @@ void LocalFrameClientWithParent::Detached(FrameDetachType) {
->DidDetachChild(); ->DidDetachChild();
} }
void RenderingTestChromeClient::InjectGestureScrollEvent(
LocalFrame& local_frame,
WebGestureDevice device,
const gfx::Vector2dF& delta,
ScrollGranularity granularity,
CompositorElementId scrollable_area_element_id,
WebInputEvent::Type injected_type) {
// Directly handle injected gesture scroll events. In a real browser, these
// would be added to the event queue and handled asynchronously but immediate
// handling is sufficient to test scrollbar dragging.
std::unique_ptr<WebGestureEvent> gesture_event =
ui::GenerateInjectedScrollGesture(injected_type, base::TimeTicks::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();
}
local_frame.GetEventHandler().HandleGestureEvent(*gesture_event);
}
RenderingTestChromeClient& RenderingTest::GetChromeClient() const { RenderingTestChromeClient& RenderingTest::GetChromeClient() const {
DEFINE_STATIC_LOCAL(Persistent<RenderingTestChromeClient>, client, DEFINE_STATIC_LOCAL(Persistent<RenderingTestChromeClient>, client,
(MakeGarbageCollected<RenderingTestChromeClient>())); (MakeGarbageCollected<RenderingTestChromeClient>()));
......
...@@ -97,6 +97,13 @@ class RenderingTestChromeClient : public EmptyChromeClient { ...@@ -97,6 +97,13 @@ class RenderingTestChromeClient : public EmptyChromeClient {
return device_emulation_transform_; return device_emulation_transform_;
} }
void InjectGestureScrollEvent(LocalFrame& local_frame,
WebGestureDevice device,
const gfx::Vector2dF& delta,
ScrollGranularity granularity,
CompositorElementId scrollable_area_element_id,
WebInputEvent::Type injected_type) override;
private: private:
std::unique_ptr<LayerTreeHostEmbedder> layer_tree_; std::unique_ptr<LayerTreeHostEmbedder> layer_tree_;
TransformationMatrix device_emulation_transform_; TransformationMatrix device_emulation_transform_;
......
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