Commit 67eedb97 authored by Sahir Vellani's avatar Sahir Vellani Committed by Chromium LUCI CQ

Reland "Change autoscroll latching to top-most delta-consumable scroller"

This is a reland of 07b882d4

The original CL was reverted due to a few layout tests failing in ASAN.
The cause of these failures was that vertical_autoscroll_layout_box_
and horizontal_autoscroll_layout_box were not being cleared in
StopMiddleClickAutocroll. This resulted in heap-use-after-free errors.

In order to fix this issue, the two pointers are cleared in
StopMiddleClickAutoscroll. They are also cleared if necessary in
StopAutoscrollIfNeeded. In the latter, middle click autoscroll would be
stopped if both layout boxes are to be cleared.

Original change's description:
> Change autoscroll latching to top-most delta-consumable scroller
>
> Users will now be able to use middle click autoscroll to scroll a
> parent div if the inner-most scroller is unable to scroll in that
> direction.
>
> If there is no delta-consumable scroller, the top-most autoscrollable
> scroller will be latched.
>
> Bug: 1107648
> Change-Id: Iccd4efec3b1ce5d09c701d3d46052176275dbc32
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2488042
> Reviewed-by: Robert Flack <flackr@chromium.org>
> Reviewed-by: Rahul Arakeri <arakeri@microsoft.com>
> Commit-Queue: Sahir Vellani <sahir.vellani@microsoft.com>
> Cr-Commit-Position: refs/heads/master@{#835318}

Bug: 1107648
Change-Id: Idf3c2253a25d2cfbe12a8ffe30bbf697c636d222
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2582733Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Reviewed-by: default avatarRahul Arakeri <arakeri@microsoft.com>
Commit-Queue: Sahir Vellani <sahir.vellani@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#842593}
parent 51f7cd45
......@@ -1842,6 +1842,7 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state,
ui::ScrollInputType type) {
ScrollTree& scroll_tree = GetScrollTree();
ScrollNode* scroll_node = nullptr;
ScrollNode* first_scrollable_node = nullptr;
for (ScrollNode* cur_node = starting_node; cur_node;
cur_node = scroll_tree.parent(cur_node)) {
if (GetViewport().ShouldScroll(*cur_node)) {
......@@ -1855,10 +1856,11 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state,
if (!cur_node->scrollable)
continue;
// For UX reasons, autoscrolling should always latch to the top-most
// scroller, even if it can't scroll in the initial direction.
if (type == ui::ScrollInputType::kAutoscroll ||
CanConsumeDelta(*scroll_state, *cur_node)) {
if (!first_scrollable_node) {
first_scrollable_node = cur_node;
}
if (CanConsumeDelta(*scroll_state, *cur_node)) {
scroll_node = cur_node;
break;
}
......@@ -1880,6 +1882,13 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state,
}
}
// If the root scroller can not consume delta in an autoscroll, latch on
// to the top most autoscrollable scroller. See https://crbug.com/969150
if ((type == ui::ScrollInputType::kAutoscroll) && first_scrollable_node &&
!CanConsumeDelta(*scroll_state, *scroll_node)) {
scroll_node = first_scrollable_node;
}
return scroll_node;
}
......
......@@ -300,8 +300,10 @@ void EventHandler::StartMiddleClickAutoscroll(LayoutObject* layout_object) {
AutoscrollController* controller = scroll_manager_->GetAutoscrollController();
if (!controller)
return;
LayoutBox* scrollable = LayoutBox::FindAutoscrollable(
layout_object, /*is_middle_click_autoscroll*/ true);
controller->StartMiddleClickAutoscroll(
layout_object->GetFrame(), scrollable,
LastKnownMousePositionInRootFrame(),
......
......@@ -133,8 +133,20 @@ AutoscrollController* ScrollManager::GetAutoscrollController() const {
return nullptr;
}
static bool CanPropagate(const ScrollState& scroll_state, const Node& node) {
ScrollableArea* scrollable_area = node.GetLayoutBox()->GetScrollableArea();
ScrollPropagationDirection ScrollManager::ComputePropagationDirection(
const ScrollState& scroll_state) {
if (scroll_state.deltaXHint() == 0 && scroll_state.deltaYHint() != 0)
return ScrollPropagationDirection::kVertical;
if (scroll_state.deltaXHint() != 0 && scroll_state.deltaYHint() == 0)
return ScrollPropagationDirection::kHorizontal;
if (scroll_state.deltaXHint() != 0 && scroll_state.deltaYHint() != 0)
return ScrollPropagationDirection::kBoth;
return ScrollPropagationDirection::kNone;
}
bool ScrollManager::CanPropagate(const LayoutBox* layout_box,
ScrollPropagationDirection direction) {
ScrollableArea* scrollable_area = layout_box->GetScrollableArea();
if (!scrollable_area)
return true;
......@@ -142,54 +154,106 @@ static bool CanPropagate(const ScrollState& scroll_state, const Node& node) {
!scrollable_area->UserInputScrollable(kVerticalScrollbar))
return true;
return (scroll_state.deltaXHint() == 0 ||
node.GetComputedStyle()->OverscrollBehaviorX() ==
EOverscrollBehavior::kAuto) &&
(scroll_state.deltaYHint() == 0 ||
node.GetComputedStyle()->OverscrollBehaviorY() ==
EOverscrollBehavior::kAuto);
switch (direction) {
case ScrollPropagationDirection::kBoth:
return ((layout_box->StyleRef().OverscrollBehaviorX() ==
EOverscrollBehavior::kAuto) &&
(layout_box->StyleRef().OverscrollBehaviorY() ==
EOverscrollBehavior::kAuto));
case ScrollPropagationDirection::kVertical:
return layout_box->StyleRef().OverscrollBehaviorY() ==
EOverscrollBehavior::kAuto;
case ScrollPropagationDirection::kHorizontal:
return layout_box->StyleRef().OverscrollBehaviorX() ==
EOverscrollBehavior::kAuto;
case ScrollPropagationDirection::kNone:
return true;
default:
NOTREACHED();
}
}
void ScrollManager::RecomputeScrollChain(const Node& start_node,
const ScrollState& scroll_state,
Deque<DOMNodeId>& scroll_chain) {
Deque<DOMNodeId>& scroll_chain,
bool is_autoscroll) {
DCHECK(scroll_chain.IsEmpty());
scroll_chain.clear();
DCHECK(start_node.GetLayoutObject());
LayoutBox* cur_box = start_node.GetLayoutObject()->EnclosingBox();
// Scrolling propagates along the containing block chain and ends at the
// RootScroller node. The RootScroller node will have a custom applyScroll
// callback that performs scrolling as well as associated "root" actions like
// browser control movement and overscroll glow.
while (cur_box) {
Node* cur_node = cur_box->GetNode();
if (cur_node) {
if (CanScroll(scroll_state, *cur_node))
scroll_chain.push_front(DOMNodeIds::IdForNode(cur_node));
if (is_autoscroll) {
// Propagate the autoscroll along the layout object chain, and
// append only the first node which is able to consume the scroll delta.
// The scroll node is computed differently to regular scrolls in order to
// maintain consistency with the autoscroll controller.
LayoutBox* autoscrollable = LayoutBox::FindAutoscrollable(
start_node.GetLayoutObject(), is_autoscroll);
if (autoscrollable) {
Node* cur_node = autoscrollable->GetNode();
LayoutObject* layout_object = cur_node->GetLayoutObject();
while (layout_object &&
!CanScroll(scroll_state, *cur_node, is_autoscroll)) {
ScrollPropagationDirection direction =
ComputePropagationDirection(scroll_state);
if (!CanPropagate(cur_node->GetLayoutBox(), direction))
break;
if (!layout_object->Parent() &&
layout_object->GetNode() == layout_object->GetDocument() &&
layout_object->GetDocument().LocalOwner()) {
layout_object =
layout_object->GetDocument().LocalOwner()->GetLayoutObject();
} else {
layout_object = layout_object->Parent();
}
LayoutBox* new_autoscrollable =
LayoutBox::FindAutoscrollable(layout_object, is_autoscroll);
if (new_autoscrollable)
cur_node = new_autoscrollable->GetNode();
}
scroll_chain.push_front(DOMNodeIds::IdForNode(cur_node));
}
} else {
LayoutBox* cur_box = start_node.GetLayoutObject()->EnclosingBox();
if (cur_node->IsEffectiveRootScroller())
break;
// Scrolling propagates along the containing block chain and ends at the
// RootScroller node. The RootScroller node will have a custom applyScroll
// callback that performs scrolling as well as associated "root" actions
// like browser control movement and overscroll glow.
while (cur_box) {
Node* cur_node = cur_box->GetNode();
if (!CanPropagate(scroll_state, *cur_node)) {
// We should add the first node with non-auto overscroll-behavior to
// the scroll chain regardlessly, as it's the only node we can latch to.
if (scroll_chain.empty() ||
scroll_chain.front() != DOMNodeIds::IdForNode(cur_node)) {
if (cur_node) {
if (CanScroll(scroll_state, *cur_node, /* for_autoscroll */ false))
scroll_chain.push_front(DOMNodeIds::IdForNode(cur_node));
if (cur_node->IsEffectiveRootScroller())
break;
ScrollPropagationDirection direction =
ComputePropagationDirection(scroll_state);
if (!CanPropagate(cur_node->GetLayoutBox(), direction)) {
// We should add the first node with non-auto overscroll-behavior to
// the scroll chain regardlessly, as it's the only node we can latch
// to.
if (scroll_chain.empty() ||
scroll_chain.front() != DOMNodeIds::IdForNode(cur_node)) {
scroll_chain.push_front(DOMNodeIds::IdForNode(cur_node));
}
break;
}
break;
}
}
cur_box = cur_box->ContainingBlock();
cur_box = cur_box->ContainingBlock();
}
}
}
bool ScrollManager::CanScroll(const ScrollState& scroll_state,
const Node& current_node) {
const Node& current_node,
bool for_autoscroll) {
LayoutBox* scrolling_box = current_node.GetLayoutBox();
if (auto* element = DynamicTo<Element>(current_node))
scrolling_box = element->GetLayoutBoxForScrolling();
......@@ -198,8 +262,9 @@ bool ScrollManager::CanScroll(const ScrollState& scroll_state,
// We need to always add the global root scroller even if it isn't scrollable
// since we can always pinch-zoom and scroll as well as for overscroll
// effects.
if (scrolling_box->IsGlobalRootScroller())
// effects. If autoscrolling, ignore this condition because we latch on
// to the deepest autoscrollable node.
if (scrolling_box->IsGlobalRootScroller() && !for_autoscroll)
return true;
// If this is the main LayoutView, and it's not the root scroller, that means
......@@ -207,9 +272,10 @@ bool ScrollManager::CanScroll(const ScrollState& scroll_state,
// scroll the LayoutView should cause panning of the visual viewport as well
// so ensure it gets added to the scroll chain. See LTHI::ApplyScroll for the
// equivalent behavior in CC. Node::NativeApplyScroll contains a special
// handler for this case.
// handler for this case. If autoscrolling, ignore this condition because we
// latch on to the deepest autoscrollable node.
if (IsA<LayoutView>(scrolling_box) &&
current_node.GetDocument().GetFrame()->IsMainFrame()) {
current_node.GetDocument().GetFrame()->IsMainFrame() && !for_autoscroll) {
return true;
}
......@@ -273,7 +339,8 @@ bool ScrollManager::LogicalScroll(mojom::blink::ScrollDirection direction,
std::make_unique<ScrollStateData>();
auto* scroll_state =
MakeGarbageCollected<ScrollState>(std::move(scroll_state_data));
RecomputeScrollChain(*node, *scroll_state, scroll_chain);
RecomputeScrollChain(*node, *scroll_state, scroll_chain,
/* is_autoscroll */ false);
while (!scroll_chain.IsEmpty()) {
Node* scroll_chain_node = DOMNodeIds::NodeForId(scroll_chain.TakeLast());
......@@ -497,21 +564,11 @@ WebInputEventResult ScrollManager::HandleGestureScrollBegin(
delta_consumed_for_scroll_sequence_;
auto* scroll_state =
MakeGarbageCollected<ScrollState>(std::move(scroll_state_data));
// For middle click autoscroll, only scrollable area for
// |scroll_gesture_handling_node_| should receive and handle all scroll
// events. It should not bubble up to the ancestor.
if (gesture_event.SourceDevice() == WebGestureDevice::kSyntheticAutoscroll) {
LayoutBox* scrollable = LayoutBox::FindAutoscrollable(
scroll_gesture_handling_node_->GetLayoutObject(),
/*is_middle_click_autoscroll*/ true);
if (scrollable) {
Node* scrollable_node = scrollable->GetNode();
current_scroll_chain_.push_back(DOMNodeIds::IdForNode(scrollable_node));
}
} else {
RecomputeScrollChain(*scroll_gesture_handling_node_.Get(), *scroll_state,
current_scroll_chain_);
}
RecomputeScrollChain(
*scroll_gesture_handling_node_.Get(), *scroll_state,
current_scroll_chain_,
gesture_event.SourceDevice() == WebGestureDevice::kSyntheticAutoscroll);
TRACE_EVENT_INSTANT1("input", "Computed Scroll Chain",
TRACE_EVENT_SCOPE_THREAD, "length",
......
......@@ -34,6 +34,10 @@ class Scrollbar;
class ScrollState;
class WebGestureEvent;
// Scroll directions used to check whether propagation is possible in a given
// direction. Used in CanPropagate.
enum class ScrollPropagationDirection { kHorizontal, kVertical, kBoth, kNone };
// This class takes care of scrolling and resizing and the related states. The
// user action that causes scrolling or resizing is determined in other *Manager
// classes and they call into this class for doing the work.
......@@ -108,6 +112,13 @@ class CORE_EXPORT ScrollManager : public GarbageCollected<ScrollManager>,
void AnimateSnapFling(base::TimeTicks monotonic_time);
// Determines whether the scroll-chain should be propagated upwards given a
// scroll direction.
static bool CanPropagate(const LayoutBox* layout_box,
ScrollPropagationDirection direction);
static ScrollPropagationDirection ComputePropagationDirection(
const ScrollState&);
private:
Node* NodeTargetForScrollableAreaElementId(
CompositorElementId scrollable_area_element_id) const;
......@@ -133,8 +144,11 @@ class CORE_EXPORT ScrollManager : public GarbageCollected<ScrollManager>,
void RecomputeScrollChain(const Node& start_node,
const ScrollState&,
Deque<DOMNodeId>& scroll_chain);
bool CanScroll(const ScrollState&, const Node& current_node);
Deque<DOMNodeId>& scroll_chain,
bool is_autoscroll);
bool CanScroll(const ScrollState&,
const Node& current_node,
bool for_autoscroll);
// scroller_size is set only when scrolling non root scroller.
void ComputeScrollRelatedMetrics(
......
......@@ -29,11 +29,13 @@
#include "third_party/blink/renderer/core/page/autoscroll_controller.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/input/scroll_manager.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
......@@ -143,6 +145,18 @@ void AutoscrollController::StopAutoscrollIfNeeded(LayoutObject* layout_object) {
if (pressed_layout_object_ == layout_object)
pressed_layout_object_ = nullptr;
if (horizontal_autoscroll_layout_box_ == layout_object)
horizontal_autoscroll_layout_box_ = nullptr;
if (vertical_autoscroll_layout_box_ == layout_object)
vertical_autoscroll_layout_box_ = nullptr;
if (MiddleClickAutoscrollInProgress() && !horizontal_autoscroll_layout_box_ &&
!vertical_autoscroll_layout_box_) {
page_->GetChromeClient().AutoscrollEnd(layout_object->GetFrame());
autoscroll_type_ = kNoAutoscroll;
}
if (autoscroll_layout_object_ != layout_object)
return;
autoscroll_layout_object_ = nullptr;
......@@ -250,7 +264,16 @@ void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll(
if (!MiddleClickAutoscrollInProgress())
return;
if (!autoscroll_layout_object_->CanBeScrolledAndHasScrollableArea()) {
bool horizontal_autoscroll_possible =
horizontal_autoscroll_layout_box_ &&
horizontal_autoscroll_layout_box_->GetNode();
bool vertical_autoscroll_possible =
vertical_autoscroll_layout_box_ &&
vertical_autoscroll_layout_box_->GetNode();
if (horizontal_autoscroll_possible &&
!horizontal_autoscroll_layout_box_->CanBeScrolledAndHasScrollableArea() &&
vertical_autoscroll_possible &&
!vertical_autoscroll_layout_box_->CanBeScrolledAndHasScrollableArea()) {
StopMiddleClickAutoscroll(frame);
return;
}
......@@ -277,11 +300,17 @@ void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll(
pow(fabs(distance.Height()), kExponent) * kMultiplier * y_signum);
bool can_scroll_vertically =
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(),
ScrollOrientation::kVerticalScroll);
vertical_autoscroll_possible
? CanScrollDirection(vertical_autoscroll_layout_box_,
frame->GetPage(),
ScrollOrientation::kVerticalScroll)
: false;
bool can_scroll_horizontally =
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(),
ScrollOrientation::kHorizontalScroll);
horizontal_autoscroll_possible
? CanScrollDirection(horizontal_autoscroll_layout_box_,
frame->GetPage(),
ScrollOrientation::kHorizontalScroll)
: false;
if (velocity != last_velocity_) {
last_velocity_ = velocity;
......@@ -319,7 +348,8 @@ void AutoscrollController::StopMiddleClickAutoscroll(LocalFrame* frame) {
autoscroll_type_ = kNoAutoscroll;
page_->GetChromeClient().SetCursorOverridden(false);
frame->LocalFrameRoot().GetEventHandler().UpdateCursor();
autoscroll_layout_object_ = nullptr;
horizontal_autoscroll_layout_box_ = nullptr;
vertical_autoscroll_layout_box_ = nullptr;
}
bool AutoscrollController::MiddleClickAutoscrollInProgress() const {
......@@ -338,17 +368,68 @@ void AutoscrollController::StartMiddleClickAutoscroll(
if (autoscroll_type_ != kNoAutoscroll)
return;
autoscroll_layout_object_ = scrollable;
autoscroll_type_ = kAutoscrollForMiddleClick;
middle_click_mode_ = kMiddleClickInitial;
middle_click_autoscroll_start_pos_global_ = position_global;
bool can_scroll_vertically =
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(),
ScrollOrientation::kVerticalScroll);
bool can_scroll_horizontally =
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(),
ScrollOrientation::kHorizontalScroll);
bool can_scroll_vertically = false;
bool can_scroll_horizontally = false;
// Scroll propagation can be prevented in either direction independently.
// We check whether autoscroll can be prevented in either direction after
// checking whether the layout box can be scrolled. If propagation is not
// allowed, we do not perform further checks for whether parents can be
// scrolled in that direction.
bool can_propagate_vertically = true;
bool can_propagate_horizontally = true;
LayoutObject* layout_object = scrollable->GetNode()->GetLayoutObject();
while (layout_object && !(can_scroll_horizontally && can_scroll_vertically)) {
LayoutBox* layout_box;
if (layout_object->IsBox()) {
layout_box = To<LayoutBox>(layout_object);
// Check whether the layout box can be scrolled and has horizontal
// scrollable area.
if (can_propagate_vertically &&
CanScrollDirection(layout_box, frame->GetPage(),
ScrollOrientation::kVerticalScroll) &&
!vertical_autoscroll_layout_box_) {
vertical_autoscroll_layout_box_ = layout_box;
can_scroll_vertically = true;
}
// Check whether the layout box can be scrolled and has vertical
// scrollable area.
if (can_propagate_horizontally &&
CanScrollDirection(layout_box, frame->GetPage(),
ScrollOrientation::kHorizontalScroll) &&
!horizontal_autoscroll_layout_box_) {
horizontal_autoscroll_layout_box_ = layout_box;
can_scroll_horizontally = true;
}
}
can_propagate_vertically = ScrollManager::CanPropagate(
layout_box, ScrollPropagationDirection::kVertical);
can_propagate_horizontally = ScrollManager::CanPropagate(
layout_box, ScrollPropagationDirection::kHorizontal);
// Exit loop if we can't propagate to the parent in any direction or if
// layout boxes have been found for both directions.
if ((!can_propagate_vertically && !can_propagate_horizontally) ||
(can_scroll_horizontally && can_scroll_vertically))
break;
if (!layout_object->Parent() &&
layout_object->GetNode() == layout_object->GetDocument() &&
layout_object->GetDocument().LocalOwner()) {
layout_object =
layout_object->GetDocument().LocalOwner()->GetLayoutObject();
} else {
layout_object = layout_object->Parent();
}
}
UseCounter::Count(frame->GetDocument(),
WebFeature::kMiddleClickAutoscrollStart);
......
......@@ -84,7 +84,7 @@ class CORE_EXPORT AutoscrollController final
// Middle-click autoscroll.
void StartMiddleClickAutoscroll(LocalFrame*,
LayoutBox*,
LayoutBox* scrollable,
const FloatPoint& position,
const FloatPoint& position_global);
void HandleMouseMoveForMiddleClickAutoscroll(
......@@ -102,15 +102,17 @@ class CORE_EXPORT AutoscrollController final
Member<Page> page_;
AutoscrollType autoscroll_type_ = kNoAutoscroll;
LayoutBox* autoscroll_layout_object_ = nullptr;
// Selection and drag-and-drop autoscroll.
void ScheduleMainThreadAnimation();
LayoutBox* autoscroll_layout_object_ = nullptr;
LayoutBox* pressed_layout_object_ = nullptr;
PhysicalOffset drag_and_drop_autoscroll_reference_position_;
base::TimeTicks drag_and_drop_autoscroll_start_time_;
// Middle-click autoscroll.
LayoutBox* horizontal_autoscroll_layout_box_ = nullptr;
LayoutBox* vertical_autoscroll_layout_box_ = nullptr;
FloatPoint middle_click_autoscroll_start_pos_global_;
gfx::Vector2dF last_velocity_;
MiddleClickMode middle_click_mode_ = kMiddleClickInitial;
......@@ -120,6 +122,9 @@ class CORE_EXPORT AutoscrollController final
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest,
ContinueAutoscrollAfterMouseLeaveEvent);
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest, StopAutoscrollOnResize);
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest, AutoscrollIsNotPropagated);
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest,
AutoscrollIsPropagatedInYDirection);
};
} // namespace blink
......
......@@ -182,4 +182,102 @@ TEST_F(AutoscrollControllerTest, StopAutoscrollOnResize) {
EXPECT_TRUE(controller.IsAutoscrolling());
}
// Ensure that middle click autoscroll is not propagated in a direction when
// propagation is not allowed.
TEST_F(AutoscrollControllerTest, AutoscrollIsNotPropagated) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<html>
<head>
<style>
#scrollable {
width: 820px;
height: 620px;
overflow: auto;
overscroll-behavior: contain;
}
#inner {
width: 2500px;
background-color: aqua;
height: 100px;
}
</style>
</head>
<body style='width: 3000px; height: 3000px;'>
<div id="scrollable">
<div id="inner"></div>
</div>
</body>
</html>
)HTML");
Compositor().BeginFrame();
AutoscrollController& controller = GetAutoscrollController();
EXPECT_FALSE(controller.IsAutoscrolling());
LocalFrame* frame = GetDocument().GetFrame();
LayoutBox* scrollable =
GetDocument().getElementById("scrollable")->GetLayoutBox();
controller.StartMiddleClickAutoscroll(
frame, scrollable, FloatPoint(15.0, 15.0), FloatPoint(15.0, 15.0));
EXPECT_TRUE(controller.IsAutoscrolling());
EXPECT_TRUE(controller.horizontal_autoscroll_layout_box_);
EXPECT_FALSE(controller.vertical_autoscroll_layout_box_);
}
// Ensure that middle click autoscroll is propagated in a direction when
// overscroll-behavior is set to auto for a that direction.
TEST_F(AutoscrollControllerTest, AutoscrollIsPropagatedInYDirection) {
SimRequest request("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
request.Complete(R"HTML(
<!DOCTYPE html>
<html>
<head>
<style>
#scrollable {
width: 820px;
height: 620px;
overflow: auto;
overscroll-behavior-x: contain;
}
#inner {
width: 1000px;
background-color: aqua;
height: 100px;
}
</style>
</head>
<body style='width: 3000px; height: 3000px;'>
<div id="scrollable">
<div id="inner"></div>
</div>
</body>
</html>
)HTML");
Compositor().BeginFrame();
AutoscrollController& controller = GetAutoscrollController();
EXPECT_FALSE(controller.IsAutoscrolling());
LocalFrame* frame = GetDocument().GetFrame();
LayoutBox* scrollable =
GetDocument().getElementById("scrollable")->GetLayoutBox();
controller.StartMiddleClickAutoscroll(
frame, scrollable, FloatPoint(15.0, 15.0), FloatPoint(15.0, 15.0));
EXPECT_TRUE(controller.IsAutoscrolling());
EXPECT_TRUE(controller.vertical_autoscroll_layout_box_);
EXPECT_TRUE(controller.horizontal_autoscroll_layout_box_);
}
} // namespace blink
<!DOCTYPE HTML>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src='../../resources/gesture-util.js'></script>
<style>
body {
height: 2000px;
width: 2000px;
background: repeating-linear-gradient(
45deg,
#606dbc,
#606dbc 40px,
#465298 40px,
#465298 80px
) local;
}
#scroller {
width: 90px;
border: 2px solid black;
height: 90px;
position: absolute;
top: 50px;
overflow: auto;
background: repeating-linear-gradient(
135deg,
#bc6d60,
#bc6d60 40px,
#985246 40px,
#985246 80px
) local;
}
#contents {
height: 200px;
}
</style>
<div id="scroller">
<div id="contents"></div>
</div>
<script>
var scroller = document.getElementById('scroller');
promise_test(async () => {
assert_equals(scroller.scrollTop, 0);
await waitForCompositorCommit();
await mouseClickOn(12, 60, 1);
await mouseMoveTo(12, 260);
await mouseClickOn(12, 260, 1);
await waitFor(() => {
return scroller.scrollTop > 0;
}, "failed to scroll scroller vertically");
}, "Middle click autoscroll should scroll child if child delta can be consumed.");
promise_test(async () => {
assert_equals(document.scrollingElement.scrollLeft, 0)
await waitForCompositorCommit();
await mouseClickOn(12, 60, 1);
await mouseMoveTo(212, 60);
await mouseClickOn(212, 60, 1);
await waitFor(() => {
return document.scrollingElement.scrollLeft > 0;
}, "failed to scroll document body horizontally");
}, "Middle click autoscroll should scroll parent if child delta can not be consumed.");
</script>
\ No newline at end of file
......@@ -86,15 +86,14 @@ window.addEventListener('load', () => {
// Autoscroll over the inner scroller.
await autoScroll(startX, startY, endX, endY);
await waitForAnimationEndTimeBased( () => { return window.scrollY; } );
assert_equals(window.scrollY, 0, "Main frame should not scroll");
assert_equals(frames[0].window.scrollY, 0, "Iframe frame should not scroll");
// Autoscroll over the iframe.
startX = rect.right - 20;
endX = startX;
await autoScroll(startX, startY, endX, endY);
await waitForAnimationEndTimeBased( () => { return window.scrollY; } );
assert_equals(window.scrollY, 0, "Main frame should not scroll");
assert_true(window.scrollY > 0, "Main frame should not scroll");
assert_equals(frames[0].window.scrollY, 0, "IFrame must NOT scroll.");
});
});
......
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/gesture-util.js"></script>
<style>
#container {
width:500px;
height:100px;
overflow:auto;
border:2px solid red;
padding:0px;
}
#spacer {
height:200px;
}
</style>
<ol>
<li>Middle-click inside the &lt;div&gt; with the red border below to activate autoscroll.</li>
<li>First, move the mouse such that you scroll the &lt;div&gt; in unavailable scroll direction and then in scrollable direction.</li>
<li>If bug repros, it won't scroll in available direction.</li>
</ol>
<div id="container">
<div id="spacer"></div>
</div>
<script>
window.onload = async function()
{
const container = document.getElementById("container");
const leftButton = 0;
const middleButton = 1;
promise_test (async (t) => {
await waitForCompositorCommit();
await mouseClickOn(container.offsetLeft + 10, container.offsetTop + 10, middleButton);
//Move mouse in unavailable direction first.
await mouseMoveTo(container.offsetLeft + (container.offsetWidth/2), container.offsetTop + 10);
//Move mouse in available direction to scroll the content.
await mouseMoveTo(container.offsetLeft + (container.offsetWidth/2), container.offsetTop + container.offsetHeight);
await waitFor(() => {
return container.scrollTop === container.scrollHeight - container.clientHeight;
}, "Failed to scroll container with middle-click autoscroll.");
}, "Container scrolled in available direction.");
}
</script>
\ No newline at end of file
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