Commit 07b882d4 authored by Sahir Vellani's avatar Sahir Vellani Committed by Chromium LUCI CQ

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/+/2488042Reviewed-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@{#835318}
parent 3f339e21
...@@ -1842,6 +1842,7 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state, ...@@ -1842,6 +1842,7 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state,
ui::ScrollInputType type) { ui::ScrollInputType type) {
ScrollTree& scroll_tree = GetScrollTree(); ScrollTree& scroll_tree = GetScrollTree();
ScrollNode* scroll_node = nullptr; ScrollNode* scroll_node = nullptr;
ScrollNode* first_scrollable_node = nullptr;
for (ScrollNode* cur_node = starting_node; cur_node; for (ScrollNode* cur_node = starting_node; cur_node;
cur_node = scroll_tree.parent(cur_node)) { cur_node = scroll_tree.parent(cur_node)) {
if (GetViewport().ShouldScroll(*cur_node)) { if (GetViewport().ShouldScroll(*cur_node)) {
...@@ -1855,10 +1856,11 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state, ...@@ -1855,10 +1856,11 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state,
if (!cur_node->scrollable) if (!cur_node->scrollable)
continue; continue;
// For UX reasons, autoscrolling should always latch to the top-most if (!first_scrollable_node) {
// scroller, even if it can't scroll in the initial direction. first_scrollable_node = cur_node;
if (type == ui::ScrollInputType::kAutoscroll || }
CanConsumeDelta(*scroll_state, *cur_node)) {
if (CanConsumeDelta(*scroll_state, *cur_node)) {
scroll_node = cur_node; scroll_node = cur_node;
break; break;
} }
...@@ -1880,6 +1882,13 @@ ScrollNode* ThreadedInputHandler::FindNodeToLatch(ScrollState* scroll_state, ...@@ -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; return scroll_node;
} }
......
...@@ -299,8 +299,10 @@ void EventHandler::StartMiddleClickAutoscroll(LayoutObject* layout_object) { ...@@ -299,8 +299,10 @@ void EventHandler::StartMiddleClickAutoscroll(LayoutObject* layout_object) {
AutoscrollController* controller = scroll_manager_->GetAutoscrollController(); AutoscrollController* controller = scroll_manager_->GetAutoscrollController();
if (!controller) if (!controller)
return; return;
LayoutBox* scrollable = LayoutBox::FindAutoscrollable( LayoutBox* scrollable = LayoutBox::FindAutoscrollable(
layout_object, /*is_middle_click_autoscroll*/ true); layout_object, /*is_middle_click_autoscroll*/ true);
controller->StartMiddleClickAutoscroll( controller->StartMiddleClickAutoscroll(
layout_object->GetFrame(), scrollable, layout_object->GetFrame(), scrollable,
LastKnownMousePositionInRootFrame(), LastKnownMousePositionInRootFrame(),
......
...@@ -133,8 +133,20 @@ AutoscrollController* ScrollManager::GetAutoscrollController() const { ...@@ -133,8 +133,20 @@ AutoscrollController* ScrollManager::GetAutoscrollController() const {
return nullptr; return nullptr;
} }
static bool CanPropagate(const ScrollState& scroll_state, const Node& node) { ScrollPropagationDirection ScrollManager::ComputePropagationDirection(
ScrollableArea* scrollable_area = node.GetLayoutBox()->GetScrollableArea(); 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) if (!scrollable_area)
return true; return true;
...@@ -142,54 +154,106 @@ static bool CanPropagate(const ScrollState& scroll_state, const Node& node) { ...@@ -142,54 +154,106 @@ static bool CanPropagate(const ScrollState& scroll_state, const Node& node) {
!scrollable_area->UserInputScrollable(kVerticalScrollbar)) !scrollable_area->UserInputScrollable(kVerticalScrollbar))
return true; return true;
return (scroll_state.deltaXHint() == 0 || switch (direction) {
node.GetComputedStyle()->OverscrollBehaviorX() == case ScrollPropagationDirection::kBoth:
EOverscrollBehavior::kAuto) && return ((layout_box->StyleRef().OverscrollBehaviorX() ==
(scroll_state.deltaYHint() == 0 || EOverscrollBehavior::kAuto) &&
node.GetComputedStyle()->OverscrollBehaviorY() == (layout_box->StyleRef().OverscrollBehaviorY() ==
EOverscrollBehavior::kAuto); 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, void ScrollManager::RecomputeScrollChain(const Node& start_node,
const ScrollState& scroll_state, const ScrollState& scroll_state,
Deque<DOMNodeId>& scroll_chain) { Deque<DOMNodeId>& scroll_chain,
bool is_autoscroll) {
DCHECK(scroll_chain.IsEmpty()); DCHECK(scroll_chain.IsEmpty());
scroll_chain.clear(); scroll_chain.clear();
DCHECK(start_node.GetLayoutObject()); 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 (is_autoscroll) {
if (CanScroll(scroll_state, *cur_node)) // Propagate the autoscroll along the layout object chain, and
scroll_chain.push_front(DOMNodeIds::IdForNode(cur_node)); // 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()) // Scrolling propagates along the containing block chain and ends at the
break; // 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)) { if (cur_node) {
// We should add the first node with non-auto overscroll-behavior to if (CanScroll(scroll_state, *cur_node, /* for_autoscroll */ false))
// 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)); 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, bool ScrollManager::CanScroll(const ScrollState& scroll_state,
const Node& current_node) { const Node& current_node,
bool for_autoscroll) {
LayoutBox* scrolling_box = current_node.GetLayoutBox(); LayoutBox* scrolling_box = current_node.GetLayoutBox();
if (auto* element = DynamicTo<Element>(current_node)) if (auto* element = DynamicTo<Element>(current_node))
scrolling_box = element->GetLayoutBoxForScrolling(); scrolling_box = element->GetLayoutBoxForScrolling();
...@@ -198,8 +262,9 @@ bool ScrollManager::CanScroll(const ScrollState& scroll_state, ...@@ -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 // 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 // since we can always pinch-zoom and scroll as well as for overscroll
// effects. // effects. If autoscrolling, ignore this condition because we latch on
if (scrolling_box->IsGlobalRootScroller()) // to the deepest autoscrollable node.
if (scrolling_box->IsGlobalRootScroller() && !for_autoscroll)
return true; return true;
// If this is the main LayoutView, and it's not the root scroller, that means // 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, ...@@ -207,9 +272,10 @@ bool ScrollManager::CanScroll(const ScrollState& scroll_state,
// scroll the LayoutView should cause panning of the visual viewport as well // 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 // so ensure it gets added to the scroll chain. See LTHI::ApplyScroll for the
// equivalent behavior in CC. Node::NativeApplyScroll contains a special // 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) && if (IsA<LayoutView>(scrolling_box) &&
current_node.GetDocument().GetFrame()->IsMainFrame()) { current_node.GetDocument().GetFrame()->IsMainFrame() && !for_autoscroll) {
return true; return true;
} }
...@@ -273,7 +339,8 @@ bool ScrollManager::LogicalScroll(mojom::blink::ScrollDirection direction, ...@@ -273,7 +339,8 @@ bool ScrollManager::LogicalScroll(mojom::blink::ScrollDirection direction,
std::make_unique<ScrollStateData>(); std::make_unique<ScrollStateData>();
auto* scroll_state = auto* scroll_state =
MakeGarbageCollected<ScrollState>(std::move(scroll_state_data)); 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()) { while (!scroll_chain.IsEmpty()) {
Node* scroll_chain_node = DOMNodeIds::NodeForId(scroll_chain.TakeLast()); Node* scroll_chain_node = DOMNodeIds::NodeForId(scroll_chain.TakeLast());
...@@ -497,21 +564,11 @@ WebInputEventResult ScrollManager::HandleGestureScrollBegin( ...@@ -497,21 +564,11 @@ WebInputEventResult ScrollManager::HandleGestureScrollBegin(
delta_consumed_for_scroll_sequence_; delta_consumed_for_scroll_sequence_;
auto* scroll_state = auto* scroll_state =
MakeGarbageCollected<ScrollState>(std::move(scroll_state_data)); 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 RecomputeScrollChain(
// events. It should not bubble up to the ancestor. *scroll_gesture_handling_node_.Get(), *scroll_state,
if (gesture_event.SourceDevice() == WebGestureDevice::kSyntheticAutoscroll) { current_scroll_chain_,
LayoutBox* scrollable = LayoutBox::FindAutoscrollable( gesture_event.SourceDevice() == WebGestureDevice::kSyntheticAutoscroll);
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_);
}
TRACE_EVENT_INSTANT1("input", "Computed Scroll Chain", TRACE_EVENT_INSTANT1("input", "Computed Scroll Chain",
TRACE_EVENT_SCOPE_THREAD, "length", TRACE_EVENT_SCOPE_THREAD, "length",
......
...@@ -34,6 +34,10 @@ class Scrollbar; ...@@ -34,6 +34,10 @@ class Scrollbar;
class ScrollState; class ScrollState;
class WebGestureEvent; 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 // 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 // user action that causes scrolling or resizing is determined in other *Manager
// classes and they call into this class for doing the work. // classes and they call into this class for doing the work.
...@@ -108,6 +112,13 @@ class CORE_EXPORT ScrollManager : public GarbageCollected<ScrollManager>, ...@@ -108,6 +112,13 @@ class CORE_EXPORT ScrollManager : public GarbageCollected<ScrollManager>,
void AnimateSnapFling(base::TimeTicks monotonic_time); 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: private:
Node* NodeTargetForScrollableAreaElementId( Node* NodeTargetForScrollableAreaElementId(
CompositorElementId scrollable_area_element_id) const; CompositorElementId scrollable_area_element_id) const;
...@@ -133,8 +144,11 @@ class CORE_EXPORT ScrollManager : public GarbageCollected<ScrollManager>, ...@@ -133,8 +144,11 @@ class CORE_EXPORT ScrollManager : public GarbageCollected<ScrollManager>,
void RecomputeScrollChain(const Node& start_node, void RecomputeScrollChain(const Node& start_node,
const ScrollState&, const ScrollState&,
Deque<DOMNodeId>& scroll_chain); Deque<DOMNodeId>& scroll_chain,
bool CanScroll(const ScrollState&, const Node& current_node); bool is_autoscroll);
bool CanScroll(const ScrollState&,
const Node& current_node,
bool for_autoscroll);
// scroller_size is set only when scrolling non root scroller. // scroller_size is set only when scrolling non root scroller.
void ComputeScrollRelatedMetrics( void ComputeScrollRelatedMetrics(
......
...@@ -29,11 +29,13 @@ ...@@ -29,11 +29,13 @@
#include "third_party/blink/renderer/core/page/autoscroll_controller.h" #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.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.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/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.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/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/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_box.h" #include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/chrome_client.h"
...@@ -250,7 +252,16 @@ void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll( ...@@ -250,7 +252,16 @@ void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll(
if (!MiddleClickAutoscrollInProgress()) if (!MiddleClickAutoscrollInProgress())
return; 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); StopMiddleClickAutoscroll(frame);
return; return;
} }
...@@ -277,11 +288,17 @@ void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll( ...@@ -277,11 +288,17 @@ void AutoscrollController::HandleMouseMoveForMiddleClickAutoscroll(
pow(fabs(distance.Height()), kExponent) * kMultiplier * y_signum); pow(fabs(distance.Height()), kExponent) * kMultiplier * y_signum);
bool can_scroll_vertically = bool can_scroll_vertically =
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(), vertical_autoscroll_possible
ScrollOrientation::kVerticalScroll); ? CanScrollDirection(vertical_autoscroll_layout_box_,
frame->GetPage(),
ScrollOrientation::kVerticalScroll)
: false;
bool can_scroll_horizontally = bool can_scroll_horizontally =
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(), horizontal_autoscroll_possible
ScrollOrientation::kHorizontalScroll); ? CanScrollDirection(horizontal_autoscroll_layout_box_,
frame->GetPage(),
ScrollOrientation::kHorizontalScroll)
: false;
if (velocity != last_velocity_) { if (velocity != last_velocity_) {
last_velocity_ = velocity; last_velocity_ = velocity;
...@@ -338,17 +355,68 @@ void AutoscrollController::StartMiddleClickAutoscroll( ...@@ -338,17 +355,68 @@ void AutoscrollController::StartMiddleClickAutoscroll(
if (autoscroll_type_ != kNoAutoscroll) if (autoscroll_type_ != kNoAutoscroll)
return; return;
autoscroll_layout_object_ = scrollable;
autoscroll_type_ = kAutoscrollForMiddleClick; autoscroll_type_ = kAutoscrollForMiddleClick;
middle_click_mode_ = kMiddleClickInitial; middle_click_mode_ = kMiddleClickInitial;
middle_click_autoscroll_start_pos_global_ = position_global; middle_click_autoscroll_start_pos_global_ = position_global;
bool can_scroll_vertically = bool can_scroll_vertically = false;
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(), bool can_scroll_horizontally = false;
ScrollOrientation::kVerticalScroll);
bool can_scroll_horizontally = // Scroll propagation can be prevented in either direction independently.
CanScrollDirection(autoscroll_layout_object_, frame->GetPage(), // We check whether autoscroll can be prevented in either direction after
ScrollOrientation::kHorizontalScroll); // 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(), UseCounter::Count(frame->GetDocument(),
WebFeature::kMiddleClickAutoscrollStart); WebFeature::kMiddleClickAutoscrollStart);
......
...@@ -84,7 +84,7 @@ class CORE_EXPORT AutoscrollController final ...@@ -84,7 +84,7 @@ class CORE_EXPORT AutoscrollController final
// Middle-click autoscroll. // Middle-click autoscroll.
void StartMiddleClickAutoscroll(LocalFrame*, void StartMiddleClickAutoscroll(LocalFrame*,
LayoutBox*, LayoutBox* scrollable,
const FloatPoint& position, const FloatPoint& position,
const FloatPoint& position_global); const FloatPoint& position_global);
void HandleMouseMoveForMiddleClickAutoscroll( void HandleMouseMoveForMiddleClickAutoscroll(
...@@ -102,15 +102,17 @@ class CORE_EXPORT AutoscrollController final ...@@ -102,15 +102,17 @@ class CORE_EXPORT AutoscrollController final
Member<Page> page_; Member<Page> page_;
AutoscrollType autoscroll_type_ = kNoAutoscroll; AutoscrollType autoscroll_type_ = kNoAutoscroll;
LayoutBox* autoscroll_layout_object_ = nullptr;
// Selection and drag-and-drop autoscroll. // Selection and drag-and-drop autoscroll.
void ScheduleMainThreadAnimation(); void ScheduleMainThreadAnimation();
LayoutBox* autoscroll_layout_object_ = nullptr;
LayoutBox* pressed_layout_object_ = nullptr; LayoutBox* pressed_layout_object_ = nullptr;
PhysicalOffset drag_and_drop_autoscroll_reference_position_; PhysicalOffset drag_and_drop_autoscroll_reference_position_;
base::TimeTicks drag_and_drop_autoscroll_start_time_; base::TimeTicks drag_and_drop_autoscroll_start_time_;
// Middle-click autoscroll. // Middle-click autoscroll.
LayoutBox* horizontal_autoscroll_layout_box_ = nullptr;
LayoutBox* vertical_autoscroll_layout_box_ = nullptr;
FloatPoint middle_click_autoscroll_start_pos_global_; FloatPoint middle_click_autoscroll_start_pos_global_;
gfx::Vector2dF last_velocity_; gfx::Vector2dF last_velocity_;
MiddleClickMode middle_click_mode_ = kMiddleClickInitial; MiddleClickMode middle_click_mode_ = kMiddleClickInitial;
...@@ -120,6 +122,9 @@ class CORE_EXPORT AutoscrollController final ...@@ -120,6 +122,9 @@ class CORE_EXPORT AutoscrollController final
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest, FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest,
ContinueAutoscrollAfterMouseLeaveEvent); ContinueAutoscrollAfterMouseLeaveEvent);
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest, StopAutoscrollOnResize); FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest, StopAutoscrollOnResize);
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest, AutoscrollIsNotPropagated);
FRIEND_TEST_ALL_PREFIXES(AutoscrollControllerTest,
AutoscrollIsPropagatedInYDirection);
}; };
} // namespace blink } // namespace blink
......
...@@ -182,4 +182,102 @@ TEST_F(AutoscrollControllerTest, StopAutoscrollOnResize) { ...@@ -182,4 +182,102 @@ TEST_F(AutoscrollControllerTest, StopAutoscrollOnResize) {
EXPECT_TRUE(controller.IsAutoscrolling()); 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 } // 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', () => { ...@@ -86,15 +86,14 @@ window.addEventListener('load', () => {
// Autoscroll over the inner scroller. // Autoscroll over the inner scroller.
await autoScroll(startX, startY, endX, endY); await autoScroll(startX, startY, endX, endY);
await waitForAnimationEndTimeBased( () => { return window.scrollY; } ); 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. // Autoscroll over the iframe.
startX = rect.right - 20; startX = rect.right - 20;
endX = startX; endX = startX;
await autoScroll(startX, startY, endX, endY); await autoScroll(startX, startY, endX, endY);
await waitForAnimationEndTimeBased( () => { return window.scrollY; } ); 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."); 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