// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cc/layers/scrollbar_layer_impl_base.h" #include #include "base/cancelable_callback.h" #include "cc/base/math_util.h" #include "cc/input/scroll_utils.h" #include "cc/input/scrollbar.h" #include "cc/input/scrollbar_controller.h" #include "cc/layers/viewport.h" #include "cc/trees/layer_tree_impl.h" #include "cc/trees/scroll_node.h" namespace cc { ScrollbarController::~ScrollbarController() { if (cancelable_autoscroll_task_) { cancelable_autoscroll_task_->Cancel(); cancelable_autoscroll_task_.reset(); } } ScrollbarController::ScrollbarController( LayerTreeHostImpl* layer_tree_host_impl) : layer_tree_host_impl_(layer_tree_host_impl), scrollbar_scroll_is_active_(false), last_known_pointer_position_(gfx::PointF(0, 0)), drag_processed_for_current_frame_(false), cancelable_autoscroll_task_(nullptr) {} void ScrollbarController::WillBeginImplFrame() { drag_processed_for_current_frame_ = false; RecomputeAutoscrollStateIfNeeded(); } // Retrieves the ScrollbarLayerImplBase corresponding to the stashed ElementId. ScrollbarLayerImplBase* ScrollbarController::ScrollbarLayer() const { if (!captured_scrollbar_metadata_.has_value()) return nullptr; const ScrollbarSet scrollbars = layer_tree_host_impl_->ScrollbarsFor( captured_scrollbar_metadata_->scroll_element_id); for (ScrollbarLayerImplBase* scrollbar : scrollbars) { if (captured_scrollbar_metadata_->orientation == scrollbar->orientation()) return scrollbar; } return nullptr; } PointerResultType ScrollbarController::HitTest( const gfx::PointF position_in_widget) const { // If a non-custom scrollbar layer was not found, we return early as there is // no point in setting additional state in the ScrollbarController. Return an // empty InputHandlerPointerResult in this case so that when it is bubbled up // to InputHandlerProxy::RouteToTypeSpecificHandler, the pointer event gets // passed on to the main thread. const LayerImpl* layer_impl = GetLayerHitByPoint(position_in_widget); if (!(layer_impl && layer_impl->IsScrollbarLayer())) return PointerResultType::kUnhandled; // If the scrollbar layer has faded out (eg: Overlay scrollbars), don't // initiate a scroll. const ScrollbarLayerImplBase* scrollbar = ToScrollbarLayer(layer_impl); if (scrollbar->OverlayScrollbarOpacity() == 0.f) return PointerResultType::kUnhandled; // If the scroll_node has a main_thread_scrolling_reason, don't initiate a // scroll. const ScrollNode* target_node = layer_tree_host_impl_->active_tree() ->property_trees() ->scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id()); if (target_node->main_thread_scrolling_reasons) return PointerResultType::kUnhandled; return PointerResultType::kScrollbarScroll; } // Performs hit test and prepares scroll deltas that will be used by GSB and // GSU. InputHandlerPointerResult ScrollbarController::HandlePointerDown( const gfx::PointF position_in_widget, bool jump_key_modifier) { if (HitTest(position_in_widget) != PointerResultType::kScrollbarScroll) return InputHandlerPointerResult(); // TODO(arakeri): GetLayerHitByPoint should ideally be called only once per // pointerdown. This needs to be optimized. See crbug.com/1156922. const ScrollbarLayerImplBase* scrollbar = ToScrollbarLayer(GetLayerHitByPoint(position_in_widget)); captured_scrollbar_metadata_ = CapturedScrollbarMetadata(); captured_scrollbar_metadata_->scroll_element_id = scrollbar->scroll_element_id(); captured_scrollbar_metadata_->orientation = scrollbar->orientation(); InputHandlerPointerResult scroll_result; scroll_result.target_scroller = scrollbar->scroll_element_id(); scroll_result.type = PointerResultType::kScrollbarScroll; layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); const ScrollbarPart scrollbar_part = GetScrollbarPartFromPointerDown(position_in_widget); const bool perform_jump_click_on_track = scrollbar->JumpOnTrackClick() != jump_key_modifier; scroll_result.scroll_offset = GetScrollOffsetForScrollbarPart( scrollbar_part, perform_jump_click_on_track); last_known_pointer_position_ = position_in_widget; scrollbar_scroll_is_active_ = true; scroll_result.scroll_units = Granularity(scrollbar_part, perform_jump_click_on_track); // Initialize drag state if either the scrollbar thumb is being dragged OR the // user has initiated a jump click (since the thumb would have jumped under // the pointer). if (scrollbar_part == ScrollbarPart::THUMB || perform_jump_click_on_track) { drag_state_ = DragState(); bool clipped = false; drag_state_->drag_origin = GetScrollbarRelativePosition(position_in_widget, &clipped); // If the point were clipped we shouldn't have hit tested to a valid part. DCHECK(!clipped); // Record the current scroller offset. This will be needed to snap the // thumb back to its original position if the pointer moves too far away // from the track during a thumb drag. Additionally, if a thumb drag is // being initiated *after* a jump click, scroll_position_at_start_ needs // to account for that. const float jump_click_thumb_drag_offset = scrollbar->orientation() == ScrollbarOrientation::HORIZONTAL ? scroll_result.scroll_offset.x() : scroll_result.scroll_offset.y(); drag_state_->scroll_position_at_start_ = scrollbar->current_pos() + (perform_jump_click_on_track ? jump_click_thumb_drag_offset : 0); drag_state_->scroller_length_at_previous_move = scrollbar->scroll_layer_length(); } if (!scroll_result.scroll_offset.IsZero() && !perform_jump_click_on_track) { // Thumb drag is the only scrollbar manipulation that cannot produce an // autoscroll. All other interactions like clicking on arrows/trackparts // 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::BindOnce( &ScrollbarController::StartAutoScrollAnimation, base::Unretained(this), InitialDeltaToAutoscrollVelocity(scroll_result.scroll_offset), scrollbar_part)); layer_tree_host_impl_->GetTaskRunner()->PostDelayedTask( FROM_HERE, cancelable_autoscroll_task_->callback(), kInitialAutoscrollTimerDelay); } return scroll_result; } bool ScrollbarController::SnapToDragOrigin( const gfx::PointF pointer_position_in_widget) const { // Consult the ScrollbarTheme to check if thumb snapping is supported on the // current platform. const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); if (!(scrollbar && scrollbar->SupportsDragSnapBack())) return false; bool clipped = false; const gfx::PointF pointer_position_in_layer = GetScrollbarRelativePosition(pointer_position_in_widget, &clipped); if (clipped) return false; layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); const ScrollbarOrientation orientation = scrollbar->orientation(); const gfx::Rect forward_track_rect = scrollbar->ForwardTrackRect(); // When dragging the thumb, there needs to exist "gutters" on either side of // the track. The thickness of these gutters is a multiple of the track (or // thumb) thickness. As long as the pointer remains within the bounds of these // gutters in the non-scrolling direction, thumb drag proceeds as expected. // The moment the pointer moves outside the bounds, the scroller needs to snap // back to the drag_origin (aka the scroll offset of the parent scroller // before the thumb drag initiated). int track_thickness = orientation == ScrollbarOrientation::VERTICAL ? forward_track_rect.width() : forward_track_rect.height(); if (!track_thickness) { // For overlay scrollbars (or for tests that do not set up a track // thickness), use the thumb_thickness instead to determine the gutters. const int thumb_thickness = scrollbar->ThumbThickness(); // If the thumb doesn't have thickness, the gutters can't be determined. // Snapping shouldn't occur in this case. if (!thumb_thickness) return false; track_thickness = thumb_thickness; } const float gutter_thickness = kOffSideMultiplier * track_thickness; const float gutter_min_bound = orientation == ScrollbarOrientation::VERTICAL ? (forward_track_rect.x() - gutter_thickness) : (forward_track_rect.y() - gutter_thickness); const float gutter_max_bound = orientation == ScrollbarOrientation::VERTICAL ? (forward_track_rect.x() + track_thickness + gutter_thickness) : (forward_track_rect.y() + track_thickness + gutter_thickness); const float pointer_location = orientation == ScrollbarOrientation::VERTICAL ? pointer_position_in_layer.x() : pointer_position_in_layer.y(); return pointer_location < gutter_min_bound || pointer_location > gutter_max_bound; } ui::ScrollGranularity ScrollbarController::Granularity( const ScrollbarPart scrollbar_part, const bool jump_key_modifier) const { const bool shift_click_on_scrollbar_track = jump_key_modifier && (scrollbar_part == ScrollbarPart::FORWARD_TRACK || scrollbar_part == ScrollbarPart::BACK_TRACK); if (shift_click_on_scrollbar_track || scrollbar_part == ScrollbarPart::THUMB) return ui::ScrollGranularity::kScrollByPrecisePixel; // TODO(arakeri): This needs to be updated to kLine once cc implements // handling it. crbug.com/959441 return ui::ScrollGranularity::kScrollByPixel; } float ScrollbarController::GetScrollDeltaForAbsoluteJump() const { layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); bool clipped = false; const gfx::PointF pointer_position_in_layer = GetScrollbarRelativePosition(last_known_pointer_position_, &clipped); if (clipped) return 0; const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const float pointer_location = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? pointer_position_in_layer.y() : pointer_position_in_layer.x(); // During a shift + click, the pointers current location (on the track) needs // to be considered as the center of the thumb and the thumb origin needs to // be calculated based on that. This will ensure that when shift + click is // processed, the thumb will be centered on the pointer. const int thumb_length = scrollbar->ThumbLength(); const float desired_thumb_origin = pointer_location - thumb_length / 2.f; const gfx::Rect thumb_rect(scrollbar->ComputeThumbQuadRect()); const float current_thumb_origin = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? thumb_rect.y() : thumb_rect.x(); const float delta = round(std::abs(desired_thumb_origin - current_thumb_origin)); return delta * GetScrollerToScrollbarRatio() * GetPageScaleFactorForScroll(); } float ScrollbarController::GetScrollDeltaForDragPosition( const gfx::PointF pointer_position_in_widget) const { const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); // Convert the move position to scrollbar layer relative for comparison with // |drag_state_| drag_origin. Ignore clipping as if we're within the region // that doesn't cause snapping back, we do want the delta in the appropriate // dimension to cause a scroll. bool clipped = false; const gfx::PointF scrollbar_relative_position( GetScrollbarRelativePosition(pointer_position_in_widget, &clipped)); float pointer_delta = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? scrollbar_relative_position.y() - drag_state_->drag_origin.y() : scrollbar_relative_position.x() - drag_state_->drag_origin.x(); const float new_offset = pointer_delta * GetScrollerToScrollbarRatio(); float scroll_delta = drag_state_->scroll_position_at_start_ + new_offset - scrollbar->current_pos(); // The scroll delta computed is layer relative. In order to scroll the // correct amount, we have to convert the delta to be unscaled (i.e. multiply // by the page scale factor), as GSU deltas are always unscaled. scroll_delta *= GetPageScaleFactorForScroll(); return scroll_delta; } // Performs hit test and prepares scroll deltas that will be used by GSU. InputHandlerPointerResult ScrollbarController::HandlePointerMove( const gfx::PointF position_in_widget) { last_known_pointer_position_ = position_in_widget; RecomputeAutoscrollStateIfNeeded(); InputHandlerPointerResult scroll_result; const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); if (!scrollbar || !drag_state_.has_value()) return scroll_result; // If the scrollbar thumb is being dragged, it qualifies as a kScrollbarScroll // (although the delta might still be zero). Setting the "type" to // kScrollbarScroll ensures that the correct event modifier (in // InputHandlerProxy) is set which in-turn tells the main thread to invalidate // the respective scrollbar parts. This needs to be done for all // pointermove(s) since they are not VSync aligned. scroll_result.type = PointerResultType::kScrollbarScroll; // If a GSU was already produced for a thumb drag in this frame, there's no // point in continuing on. Please see the header file for details. if (drag_processed_for_current_frame_) return scroll_result; if (SnapToDragOrigin(position_in_widget)) { const float delta = scrollbar->current_pos() - drag_state_->scroll_position_at_start_; scroll_result.scroll_units = ui::ScrollGranularity::kScrollByPrecisePixel; scroll_result.scroll_offset = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::ScrollOffset(0, -delta) : gfx::ScrollOffset(-delta, 0); drag_processed_for_current_frame_ = true; return scroll_result; } // When initiating a thumb drag, a pointerdown and a pointermove can both // arrive a the ScrollbarController in succession before a GSB would have // been dispatched. So, querying LayerTreeHostImpl::CurrentlyScrollingNode() // can potentially be null. Hence, a better way to look the target_node to be // scrolled is by using ScrollbarLayerImplBase::scroll_element_id(). const ScrollNode* target_node = layer_tree_host_impl_->active_tree() ->property_trees() ->scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id()); // If a scrollbar exists, it should always have an ElementId pointing to a // valid ScrollNode. DCHECK(target_node); float delta = GetScrollDeltaForDragPosition(position_in_widget); if (drag_state_->scroller_length_at_previous_move != scrollbar->scroll_layer_length()) { drag_state_->scroller_displacement = delta; drag_state_->scroller_length_at_previous_move = scrollbar->scroll_layer_length(); // This is done to ensure that, when the scroller length changes mid thumb // drag, the scroller shouldn't jump. We early out because the delta would // be zero in this case anyway (since drag_state_->scroller_displacement = // delta). So that means, in the worst case you'd miss 1 GSU every time the // scroller expands while a thumb drag is in progress. return scroll_result; } delta -= drag_state_->scroller_displacement; // If scroll_offset can't be consumed, there's no point in continuing on. const gfx::ScrollOffset scroll_offset(scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::ScrollOffset(0, delta) : gfx::ScrollOffset(delta, 0)); const gfx::Vector2dF clamped_scroll_offset = ComputeClampedDelta(*target_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::ScrollGranularity::kScrollByPrecisePixel; scroll_result.scroll_offset = gfx::ScrollOffset(clamped_scroll_offset); drag_processed_for_current_frame_ = true; return scroll_result; } gfx::Vector2dF ScrollbarController::ComputeClampedDelta( const ScrollNode& target_node, const gfx::Vector2dF& scroll_delta) const { DCHECK(!target_node.scrolls_inner_viewport); if (target_node.scrolls_outer_viewport) return layer_tree_host_impl_->viewport().ComputeClampedDelta(scroll_delta); // ComputeScrollDelta returns a delta accounting for the current page zoom // level. Since we're producing a delta for an injected GSU, we need to get // back to and unscaled delta (i.e. multiply by the page scale factor). gfx::Vector2dF clamped_delta = layer_tree_host_impl_->GetInputHandler().ComputeScrollDelta(target_node, scroll_delta); const float scale_factor = GetPageScaleFactorForScroll(); clamped_delta.Scale(scale_factor); return clamped_delta; } float ScrollbarController::GetScrollerToScrollbarRatio() const { // Calculating the delta by which the scroller layer should move when // dragging the thumb depends on the following factors: // - scrollbar_track_length // - scrollbar_thumb_length // - scroll_layer_length // - viewport_length // - position_in_widget // // When a thumb drag is in progress, for every pixel that the pointer moves, // the delta for the corresponding scroll_layer needs to be scaled by the // following ratio: // scaled_scroller_to_scrollbar_ratio = // (scroll_layer_length - viewport_length) / // (scrollbar_track_length - scrollbar_thumb_length) // // PS: Note that since this is a "ratio", it need not be scaled by the DSF. // // |<--------------------- scroll_layer_length -------------------------->| // // +------------------------------------------------+...................... // | | . // |<-------------- viewport_length --------------->| . // | | . // | | . // | | . // | | . // | | . // | | . // | | . // | | . // | | . // | |<------- scrollbar_track_length --------->| | . // | | . // +--+-----+----------------------------+-------+--+...................... // |<|| |############################| ||>| // +--+-----+----------------------------+-------+--+ // // |<- scrollbar_thumb_length ->| // layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); float scroll_layer_length = scrollbar->scroll_layer_length(); float scrollbar_track_length = scrollbar->TrackLength(); gfx::Rect thumb_rect(scrollbar->ComputeThumbQuadRect()); float scrollbar_thumb_length = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? thumb_rect.height() : thumb_rect.width(); float viewport_length = GetViewportLength(); return (scroll_layer_length - viewport_length) / (scrollbar_track_length - scrollbar_thumb_length); } void ScrollbarController::ResetState() { drag_processed_for_current_frame_ = false; drag_state_ = absl::nullopt; autoscroll_state_ = absl::nullopt; captured_scrollbar_metadata_ = absl::nullopt; if (cancelable_autoscroll_task_) { cancelable_autoscroll_task_->Cancel(); cancelable_autoscroll_task_.reset(); } } void ScrollbarController::DidUnregisterScrollbar( ElementId element_id, ScrollbarOrientation orientation) { if (captured_scrollbar_metadata_.has_value() && captured_scrollbar_metadata_->scroll_element_id == element_id && captured_scrollbar_metadata_->orientation == orientation) ResetState(); } void ScrollbarController::RecomputeAutoscrollStateIfNeeded() { if (!autoscroll_state_.has_value() || !captured_scrollbar_metadata_.has_value()) return; layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); bool clipped; gfx::PointF scroller_relative_position( GetScrollbarRelativePosition(last_known_pointer_position_, &clipped)); if (clipped) return; // Based on the orientation of the scrollbar and the direction of the // autoscroll, the code below makes a decision of whether the track autoscroll // should be canceled or not. int thumb_start = 0; int thumb_end = 0; int pointer_position = 0; const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const gfx::Rect thumb_quad = scrollbar->ComputeThumbQuadRect(); if (scrollbar->orientation() == ScrollbarOrientation::VERTICAL) { thumb_start = thumb_quad.y(); thumb_end = thumb_quad.y() + thumb_quad.height(); pointer_position = scroller_relative_position.y(); } else { thumb_start = thumb_quad.x(); thumb_end = thumb_quad.x() + thumb_quad.width(); pointer_position = scroller_relative_position.x(); } // If the thumb reaches the pointer while autoscrolling, abort. if ((autoscroll_state_->direction == AutoScrollDirection::AUTOSCROLL_FORWARD && thumb_end > pointer_position) || (autoscroll_state_->direction == AutoScrollDirection::AUTOSCROLL_BACKWARD && thumb_start < pointer_position)) layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); // When the scroller is autoscrolling forward, its dimensions need to be // monitored. If the length of the scroller layer increases, the old one needs // to be aborted and a new autoscroll animation needs to start. This needs to // be done only for the "autoscroll forward" case. Autoscrolling backward // always has a constant value to animate to (which is '0'. See the function // ScrollbarController::StartAutoScrollAnimation). if (autoscroll_state_->direction == AutoScrollDirection::AUTOSCROLL_FORWARD) { const float scroll_layer_length = scrollbar->scroll_layer_length(); if (autoscroll_state_->scroll_layer_length != scroll_layer_length) { layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); StartAutoScrollAnimation(autoscroll_state_->velocity, autoscroll_state_->pressed_scrollbar_part); } } // The animations need to be aborted/restarted based on the pointer location // (i.e leaving/entering the track/arrows, reaching the track end etc). The // autoscroll_state_ however, needs to be reset on pointer changes. const gfx::RectF scrollbar_part_rect( GetRectForScrollbarPart(autoscroll_state_->pressed_scrollbar_part)); if (!scrollbar_part_rect.Contains(scroller_relative_position)) { // Stop animating if pointer moves outside the rect bounds. layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); } else if (scrollbar_part_rect.Contains(scroller_relative_position) && !layer_tree_host_impl_->mutator_host()->IsElementAnimating( scrollbar->scroll_element_id())) { // Start animating if pointer re-enters the bounds. StartAutoScrollAnimation(autoscroll_state_->velocity, autoscroll_state_->pressed_scrollbar_part); } } // Helper to calculate the autoscroll velocity. float ScrollbarController::InitialDeltaToAutoscrollVelocity( gfx::ScrollOffset scroll_offset) const { DCHECK(captured_scrollbar_metadata_.has_value()); const float scroll_delta = ScrollbarLayer()->orientation() == ScrollbarOrientation::VERTICAL ? scroll_offset.y() : scroll_offset.x(); return scroll_delta * kAutoscrollMultiplier; } void ScrollbarController::StartAutoScrollAnimation( const float velocity, ScrollbarPart pressed_scrollbar_part) { // Autoscroll and thumb drag are mutually exclusive. Both can't be active at // the same time. DCHECK(!drag_state_.has_value()); DCHECK(captured_scrollbar_metadata_.has_value()); DCHECK_NE(velocity, 0); DCHECK(ScrollbarLayer()); // scroll_node is set up while handling GSB. If there's no node to scroll, we // don't need to create any animation for it. const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); ScrollTree& scroll_tree = layer_tree_host_impl_->active_tree()->property_trees()->scroll_tree; ScrollNode* scroll_node = scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id()); if (!(scroll_node && scrollbar_scroll_is_active_)) return; layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries(); float scroll_layer_length = scrollbar->scroll_layer_length(); gfx::ScrollOffset current_offset = scroll_tree.current_scroll_offset(scroll_node->element_id); // Determine the max offset for the scroll based on the scrolling direction. // Negative scroll velocity indicates backwards scrolling whereas a positive // value indicates forwards scrolling. const float target_offset = velocity < 0 ? 0 : scroll_layer_length; const gfx::Vector2dF target_offset_vector = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::Vector2dF(current_offset.x(), target_offset) : gfx::Vector2dF(target_offset, current_offset.y()); autoscroll_state_ = AutoScrollState(); autoscroll_state_->velocity = velocity; autoscroll_state_->scroll_layer_length = scroll_layer_length; autoscroll_state_->pressed_scrollbar_part = pressed_scrollbar_part; autoscroll_state_->direction = velocity < 0 ? AutoScrollDirection::AUTOSCROLL_BACKWARD : AutoScrollDirection::AUTOSCROLL_FORWARD; layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); layer_tree_host_impl_->AutoScrollAnimationCreate( *scroll_node, target_offset_vector, std::abs(velocity)); } // Performs hit test and prepares scroll deltas that will be used by GSE. InputHandlerPointerResult ScrollbarController::HandlePointerUp( const gfx::PointF position_in_widget) { InputHandlerPointerResult scroll_result; if (scrollbar_scroll_is_active_) { scrollbar_scroll_is_active_ = false; scroll_result.type = PointerResultType::kScrollbarScroll; } // TODO(arakeri): This needs to be moved to ScrollOffsetAnimationsImpl as it // has knowledge about what type of animation is running. crbug.com/976353 // Only abort the animation if it is an "autoscroll" animation. if (autoscroll_state_.has_value()) layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort(); ResetState(); return scroll_result; } // Returns the layer that is hit by the position_in_widget. LayerImpl* ScrollbarController::GetLayerHitByPoint( const gfx::PointF position_in_widget) const { LayerTreeImpl* active_tree = layer_tree_host_impl_->active_tree(); gfx::Point viewport_point(position_in_widget.x(), position_in_widget.y()); gfx::PointF device_viewport_point = gfx::ScalePoint( gfx::PointF(viewport_point), active_tree->device_scale_factor()); LayerImpl* layer_impl = active_tree->FindLayerThatIsHitByPoint(device_viewport_point); return layer_impl; } float ScrollbarController::GetViewportLength() const { const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const ScrollNode* scroll_node = layer_tree_host_impl_->active_tree() ->property_trees() ->scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id()); DCHECK(scroll_node); if (!scroll_node->scrolls_outer_viewport) { float length = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? scroll_node->container_bounds.height() : scroll_node->container_bounds.width(); return length; } gfx::SizeF viewport_size = layer_tree_host_impl_->viewport() .GetInnerViewportSizeExcludingScrollbars(); float length = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? viewport_size.height() : viewport_size.width(); return length / GetPageScaleFactorForScroll(); } float ScrollbarController::GetScrollDeltaForPercentBasedScroll() const { const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); const ScrollNode* scroll_node = layer_tree_host_impl_->active_tree() ->property_trees() ->scroll_tree.FindNodeFromElementId(scrollbar->scroll_element_id()); DCHECK(scroll_node); const gfx::Vector2dF scroll_delta = scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::Vector2dF(0, kPercentDeltaForDirectionalScroll) : gfx::Vector2dF(kPercentDeltaForDirectionalScroll, 0); const gfx::Vector2dF pixel_delta = layer_tree_host_impl_->GetInputHandler().ResolveScrollGranularityToPixels( *scroll_node, scroll_delta, ui::ScrollGranularity::kScrollByPercentage); return scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? pixel_delta.y() : pixel_delta.x(); } float ScrollbarController::GetPageScaleFactorForScroll() const { return layer_tree_host_impl_->active_tree()->page_scale_factor_for_scroll(); } float ScrollbarController::GetScrollDeltaForScrollbarPart( const ScrollbarPart scrollbar_part, const bool jump_key_modifier) const { float scroll_delta = 0; switch (scrollbar_part) { case ScrollbarPart::BACK_BUTTON: case ScrollbarPart::FORWARD_BUTTON: if (layer_tree_host_impl_->settings().percent_based_scrolling) { scroll_delta = GetScrollDeltaForPercentBasedScroll(); } else { scroll_delta = kPixelsPerLineStep * ScreenSpaceScaleFactor(); } break; case ScrollbarPart::BACK_TRACK: case ScrollbarPart::FORWARD_TRACK: { if (jump_key_modifier) { scroll_delta = GetScrollDeltaForAbsoluteJump(); break; } // TODO(savella) Use snapport length instead of viewport length to match // main thread behaviour. See https://crbug.com/1098383. scroll_delta = GetViewportLength() * kMinFractionToStepWhenPaging; break; } default: scroll_delta = 0; } return scroll_delta; } float ScrollbarController::ScreenSpaceScaleFactor() const { // TODO(arakeri): When crbug.com/716231 is fixed, this needs to be updated. // If use_zoom_for_dsf is false, the click deltas and thumb drag ratios // shouldn't be scaled. For example: On Mac, when the use_zoom_for_dsf is // false and the device_scale_factor is 2, the scroll delta for pointer clicks // on arrows would be incorrectly calculated as 80px instead of 40px. This is // also necessary to ensure that hit testing works as intended. return layer_tree_host_impl_->settings().use_zoom_for_dsf ? layer_tree_host_impl_->active_tree() ->painted_device_scale_factor() : 1.f; } gfx::PointF ScrollbarController::GetScrollbarRelativePosition( const gfx::PointF position_in_widget, bool* clipped) const { gfx::Transform inverse_screen_space_transform( gfx::Transform::kSkipInitialization); // If use_zoom_for_dsf is false, the ScreenSpaceTransform needs to be scaled // down by the DSF to ensure that position_in_widget is transformed correctly. const float scale = !layer_tree_host_impl_->settings().use_zoom_for_dsf ? 1.f / layer_tree_host_impl_->active_tree()->device_scale_factor() : 1.f; gfx::Transform scaled_screen_space_transform( ScrollbarLayer()->ScreenSpaceTransform()); scaled_screen_space_transform.PostScale(scale, scale); if (!scaled_screen_space_transform.GetInverse( &inverse_screen_space_transform)) return gfx::PointF(0, 0); return gfx::PointF(MathUtil::ProjectPoint(inverse_screen_space_transform, position_in_widget, clipped)); } // Determines the ScrollbarPart based on the position_in_widget. ScrollbarPart ScrollbarController::GetScrollbarPartFromPointerDown( const gfx::PointF position_in_widget) const { // position_in_widget needs to be transformed and made relative to the // scrollbar layer because hit testing assumes layer relative coordinates. const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); bool clipped = false; const gfx::PointF scroller_relative_position( GetScrollbarRelativePosition(position_in_widget, &clipped)); if (clipped) return ScrollbarPart::NO_PART; return scrollbar->IdentifyScrollbarPart(scroller_relative_position); } // Determines the corresponding rect for the given scrollbar part. gfx::Rect ScrollbarController::GetRectForScrollbarPart( const ScrollbarPart scrollbar_part) const { const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); if (scrollbar_part == ScrollbarPart::BACK_BUTTON) return scrollbar->BackButtonRect(); if (scrollbar_part == ScrollbarPart::FORWARD_BUTTON) return scrollbar->ForwardButtonRect(); if (scrollbar_part == ScrollbarPart::BACK_TRACK) return scrollbar->BackTrackRect(); if (scrollbar_part == ScrollbarPart::FORWARD_TRACK) return scrollbar->ForwardTrackRect(); return gfx::Rect(0, 0); } // Determines the scroll offsets based on the ScrollbarPart and the scrollbar // orientation. gfx::ScrollOffset ScrollbarController::GetScrollOffsetForScrollbarPart( const ScrollbarPart scrollbar_part, const bool jump_key_modifier) const { const ScrollbarLayerImplBase* scrollbar = ScrollbarLayer(); float scroll_delta = GetScrollDeltaForScrollbarPart(scrollbar_part, jump_key_modifier); // See CreateScrollStateForGesture for more information on how these values // will be interpreted. if (scrollbar_part == ScrollbarPart::BACK_BUTTON) { return scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::ScrollOffset(0, -scroll_delta) // Up arrow : gfx::ScrollOffset(-scroll_delta, 0); // Left arrow } else if (scrollbar_part == ScrollbarPart::FORWARD_BUTTON) { return scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::ScrollOffset(0, scroll_delta) // Down arrow : gfx::ScrollOffset(scroll_delta, 0); // Right arrow } else if (scrollbar_part == ScrollbarPart::BACK_TRACK) { return scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::ScrollOffset(0, -scroll_delta) // Track click up : gfx::ScrollOffset(-scroll_delta, 0); // Track click left } else if (scrollbar_part == ScrollbarPart::FORWARD_TRACK) { return scrollbar->orientation() == ScrollbarOrientation::VERTICAL ? gfx::ScrollOffset(0, scroll_delta) // Track click down : gfx::ScrollOffset(scroll_delta, 0); // Track click right } return gfx::ScrollOffset(0, 0); } } // namespace cc