| /* |
| * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights |
| * reserved. |
| * |
| * Portions are Copyright (C) 1998 Netscape Communications Corporation. |
| * |
| * Other contributors: |
| * Robert O'Callahan <roc+@cs.cmu.edu> |
| * David Baron <dbaron@dbaron.org> |
| * Christian Biesinger <cbiesinger@gmail.com> |
| * Randall Jesup <rjesup@wgate.com> |
| * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> |
| * Josh Soref <timeless@mac.com> |
| * Boris Zbarsky <bzbarsky@mit.edu> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
| * |
| * Alternatively, the contents of this file may be used under the terms |
| * of either the Mozilla Public License Version 1.1, found at |
| * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
| * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
| * (the "GPL"), in which case the provisions of the MPL or the GPL are |
| * applicable instead of those above. If you wish to allow use of your |
| * version of this file only under the terms of one of those two |
| * licenses (the MPL or the GPL) and not to allow others to use your |
| * version of this file under the LGPL, indicate your decision by |
| * deletingthe provisions above and replace them with the notice and |
| * other provisions required by the MPL or the GPL, as the case may be. |
| * If you do not delete the provisions above, a recipient may use your |
| * version of this file under any of the LGPL, the MPL or the GPL. |
| */ |
| |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| |
| #include <utility> |
| |
| #include "base/numerics/checked_math.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "cc/animation/animation_timeline.h" |
| #include "cc/input/main_thread_scrolling_reason.h" |
| #include "cc/input/snap_selection_strategy.h" |
| #include "cc/layers/picture_layer.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink.h" |
| #include "third_party/blink/public/mojom/scroll/scrollbar_mode.mojom-blink.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/platform/task_type.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" |
| #include "third_party/blink/renderer/core/animation/scroll_timeline.h" |
| #include "third_party/blink/renderer/core/content_capture/content_capture_manager.h" |
| #include "third_party/blink/renderer/core/css/color_scheme_flags.h" |
| #include "third_party/blink/renderer/core/css/style_request.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/node.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/editing/frame_selection.h" |
| #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/page_scale_constraints_set.h" |
| #include "third_party/blink/renderer/core/frame/root_frame_viewport.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/frame/visual_viewport.h" |
| #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" |
| #include "third_party/blink/renderer/core/html/forms/text_control_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/inspector/inspector_trace_events.h" |
| #include "third_party/blink/renderer/core/layout/custom_scrollbar.h" |
| #include "third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_theme.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/ng/legacy_layout_tree_walking.h" |
| #include "third_party/blink/renderer/core/loader/document_loader.h" |
| #include "third_party/blink/renderer/core/page/chrome_client.h" |
| #include "third_party/blink/renderer/core/page/focus_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/fragment_anchor.h" |
| #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h" |
| #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/paint/compositing/compositing_reason_finder.h" |
| #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" |
| #include "third_party/blink/renderer/core/paint/paint_invalidator.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_fragment.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_alignment.h" |
| #include "third_party/blink/renderer/core/scroll/scroll_animator_base.h" |
| #include "third_party/blink/renderer/core/scroll/scrollable_area.h" |
| #include "third_party/blink/renderer/core/scroll/scrollbar_theme.h" |
| #include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h" |
| #include "third_party/blink/renderer/core/view_transition/view_transition.h" |
| #include "third_party/blink/renderer/core/view_transition/view_transition_utils.h" |
| #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h" |
| #include "third_party/blink/renderer/platform/heap/garbage_collected.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| |
| namespace blink { |
| |
| PaintLayerScrollableAreaRareData::PaintLayerScrollableAreaRareData() = default; |
| |
| void PaintLayerScrollableAreaRareData::Trace(Visitor* visitor) const { |
| visitor->Trace(sticky_layers_); |
| } |
| |
| const int kResizerControlExpandRatioForTouch = 2; |
| |
| PaintLayerScrollableArea::PaintLayerScrollableArea(PaintLayer& layer) |
| : ScrollableArea(layer.GetLayoutBox() |
| ->GetDocument() |
| .GetPage() |
| ->GetAgentGroupScheduler() |
| .CompositorTaskRunner()), |
| layer_(&layer), |
| in_resize_mode_(false), |
| scrolls_overflow_(false), |
| needs_composited_scrolling_(false), |
| needs_scroll_offset_clamp_(false), |
| needs_relayout_(false), |
| had_horizontal_scrollbar_before_relayout_(false), |
| had_vertical_scrollbar_before_relayout_(false), |
| had_resizer_before_relayout_(false), |
| scroll_origin_changed_(false), |
| is_scrollbar_freeze_root_(false), |
| is_horizontal_scrollbar_frozen_(false), |
| is_vertical_scrollbar_frozen_(false), |
| should_scroll_on_main_thread_(true), |
| scrollbar_manager_(*this), |
| has_last_committed_scroll_offset_(false), |
| scroll_corner_(nullptr), |
| resizer_(nullptr), |
| scroll_anchor_(this), |
| non_composited_main_thread_scrolling_reasons_(0) { |
| if (auto* element = DynamicTo<Element>(GetLayoutBox()->GetNode())) { |
| // We save and restore only the scrollOffset as the other scroll values are |
| // recalculated. |
| scroll_offset_ = element->SavedLayerScrollOffset(); |
| if (!scroll_offset_.IsZero()) |
| GetScrollAnimator().SetCurrentOffset(scroll_offset_); |
| element->SetSavedLayerScrollOffset(ScrollOffset()); |
| } |
| |
| GetLayoutBox()->GetDocument().GetSnapCoordinator().AddSnapContainer( |
| *GetLayoutBox()); |
| } |
| |
| PaintLayerScrollableArea::~PaintLayerScrollableArea() { |
| CHECK(HasBeenDisposed()); |
| } |
| |
| PaintLayerScrollableArea* PaintLayerScrollableArea::FromNode(const Node& node) { |
| const LayoutBox* box = node.GetLayoutBox(); |
| return box ? box->GetScrollableArea() : nullptr; |
| } |
| |
| void PaintLayerScrollableArea::DidCompositorScroll( |
| const gfx::PointF& position) { |
| ScrollableArea::DidCompositorScroll(position); |
| // This should be alive if it receives composited scroll callbacks. |
| CHECK(!HasBeenDisposed()); |
| } |
| |
| void PaintLayerScrollableArea::DisposeImpl() { |
| rare_data_.Clear(); |
| |
| GetLayoutBox()->GetDocument().GetSnapCoordinator().RemoveSnapContainer( |
| *GetLayoutBox()); |
| |
| if (InResizeMode() && !GetLayoutBox()->DocumentBeingDestroyed()) { |
| if (LocalFrame* frame = GetLayoutBox()->GetFrame()) |
| frame->GetEventHandler().ResizeScrollableAreaDestroyed(); |
| } |
| |
| if (LocalFrame* frame = GetLayoutBox()->GetFrame()) { |
| if (LocalFrameView* frame_view = frame->View()) { |
| frame_view->RemoveScrollAnchoringScrollableArea(this); |
| frame_view->RemoveUserScrollableArea(this); |
| frame_view->RemoveAnimatingScrollableArea(this); |
| } |
| } |
| |
| non_composited_main_thread_scrolling_reasons_ = 0; |
| |
| if (!GetLayoutBox()->DocumentBeingDestroyed()) { |
| if (auto* element = DynamicTo<Element>(GetLayoutBox()->GetNode())) |
| element->SetSavedLayerScrollOffset(scroll_offset_); |
| } |
| |
| // Note: it is not safe to call ScrollAnchor::clear if the document is being |
| // destroyed, because LayoutObjectChildList::removeChildNode skips the call to |
| // willBeRemovedFromTree, |
| // leaving the ScrollAnchor with a stale LayoutObject pointer. |
| scroll_anchor_.Dispose(); |
| |
| GetLayoutBox() |
| ->GetDocument() |
| .GetPage() |
| ->GlobalRootScrollerController() |
| .DidDisposeScrollableArea(*this); |
| |
| scrollbar_manager_.Dispose(); |
| |
| if (scroll_corner_) |
| scroll_corner_->Destroy(); |
| if (resizer_) |
| resizer_->Destroy(); |
| |
| ClearScrollableArea(); |
| |
| if (SmoothScrollSequencer* sequencer = GetSmoothScrollSequencer()) |
| sequencer->DidDisposeScrollableArea(*this); |
| |
| RunScrollCompleteCallbacks(ScrollableArea::ScrollCompletionMode::kFinished); |
| |
| layer_ = nullptr; |
| } |
| |
| void PaintLayerScrollableArea::ApplyPendingHistoryRestoreScrollOffset() { |
| if (!pending_view_state_) |
| return; |
| |
| // TODO(pnoland): attempt to restore the anchor in more places than this. |
| // Anchor-based restore should allow for earlier restoration. |
| bool did_restore = RestoreScrollAnchor( |
| {pending_view_state_->scroll_anchor_data_.selector_, |
| LayoutPoint(pending_view_state_->scroll_anchor_data_.offset_), |
| pending_view_state_->scroll_anchor_data_.simhash_}); |
| if (!did_restore) { |
| SetScrollOffset(pending_view_state_->scroll_offset_, |
| mojom::blink::ScrollType::kProgrammatic, |
| mojom::blink::ScrollBehavior::kAuto); |
| } |
| |
| pending_view_state_.reset(); |
| } |
| |
| void PaintLayerScrollableArea::SetTickmarksOverride( |
| Vector<gfx::Rect> tickmarks) { |
| EnsureRareData().tickmarks_override_ = std::move(tickmarks); |
| } |
| |
| void PaintLayerScrollableArea::Trace(Visitor* visitor) const { |
| visitor->Trace(scrollbar_manager_); |
| visitor->Trace(scroll_corner_); |
| visitor->Trace(resizer_); |
| visitor->Trace(scroll_anchor_); |
| visitor->Trace(scrolling_background_display_item_client_); |
| visitor->Trace(scroll_corner_display_item_client_); |
| visitor->Trace(layer_); |
| visitor->Trace(rare_data_); |
| ScrollableArea::Trace(visitor); |
| } |
| |
| bool PaintLayerScrollableArea::IsThrottled() const { |
| return GetLayoutBox()->GetFrame()->ShouldThrottleRendering(); |
| } |
| |
| ChromeClient* PaintLayerScrollableArea::GetChromeClient() const { |
| if (HasBeenDisposed()) |
| return nullptr; |
| if (Page* page = GetLayoutBox()->GetFrame()->GetPage()) |
| return &page->GetChromeClient(); |
| return nullptr; |
| } |
| |
| SmoothScrollSequencer* PaintLayerScrollableArea::GetSmoothScrollSequencer() |
| const { |
| if (HasBeenDisposed()) |
| return nullptr; |
| |
| return &GetLayoutBox()->GetFrame()->GetSmoothScrollSequencer(); |
| } |
| |
| bool PaintLayerScrollableArea::IsActive() const { |
| Page* page = GetLayoutBox()->GetFrame()->GetPage(); |
| return page && page->GetFocusController().IsActive(); |
| } |
| |
| bool PaintLayerScrollableArea::IsScrollCornerVisible() const { |
| return !ScrollCornerRect().IsEmpty(); |
| } |
| |
| static int CornerStart(const LayoutBox& box, |
| int min_x, |
| int max_x, |
| int thickness) { |
| if (box.ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| return min_x + box.StyleRef().BorderLeftWidth().ToFloat(); |
| return max_x - thickness - box.StyleRef().BorderRightWidth().ToFloat(); |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::CornerRect() const { |
| int horizontal_thickness; |
| int vertical_thickness; |
| if (!VerticalScrollbar() && !HorizontalScrollbar()) { |
| // We need to know the thickness of custom scrollbars even when they don't |
| // exist in order to set the resizer square size properly. |
| horizontal_thickness = GetPageScrollbarTheme().ScrollbarThickness( |
| ScaleFromDIP(), EScrollbarWidth::kAuto); |
| vertical_thickness = horizontal_thickness; |
| } else if (VerticalScrollbar() && !HorizontalScrollbar()) { |
| horizontal_thickness = VerticalScrollbar()->ScrollbarThickness(); |
| vertical_thickness = horizontal_thickness; |
| } else if (HorizontalScrollbar() && !VerticalScrollbar()) { |
| vertical_thickness = HorizontalScrollbar()->ScrollbarThickness(); |
| horizontal_thickness = vertical_thickness; |
| } else { |
| horizontal_thickness = VerticalScrollbar()->ScrollbarThickness(); |
| vertical_thickness = HorizontalScrollbar()->ScrollbarThickness(); |
| } |
| gfx::Size border_box_size = PixelSnappedBorderBoxSize(); |
| return gfx::Rect(CornerStart(*GetLayoutBox(), 0, border_box_size.width(), |
| horizontal_thickness), |
| border_box_size.height() - vertical_thickness - |
| GetLayoutBox()->StyleRef().BorderBottomWidth().ToFloat(), |
| horizontal_thickness, vertical_thickness); |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::ScrollCornerRect() const { |
| // We have a scrollbar corner when a scrollbar is visible and not filling the |
| // entire length of the box. |
| // This happens when: |
| // (a) A resizer is present and at least one scrollbar is present |
| // (b) Both scrollbars are present. |
| bool has_horizontal_bar = HorizontalScrollbar(); |
| bool has_vertical_bar = VerticalScrollbar(); |
| bool has_resizer = GetLayoutBox()->CanResize(); |
| if ((has_horizontal_bar && has_vertical_bar) || |
| (has_resizer && (has_horizontal_bar || has_vertical_bar))) { |
| return CornerRect(); |
| } |
| return gfx::Rect(); |
| } |
| |
| void PaintLayerScrollableArea::SetScrollCornerNeedsPaintInvalidation() { |
| ScrollableArea::SetScrollCornerNeedsPaintInvalidation(); |
| } |
| |
| gfx::Rect |
| PaintLayerScrollableArea::ConvertFromScrollbarToContainingEmbeddedContentView( |
| const Scrollbar& scrollbar, |
| const gfx::Rect& scrollbar_rect) const { |
| LayoutView* view = GetLayoutBox()->View(); |
| if (!view) |
| return scrollbar_rect; |
| |
| gfx::Rect rect = scrollbar_rect; |
| rect.Offset(ScrollbarOffset(scrollbar)); |
| return ToPixelSnappedRect( |
| GetLayoutBox()->LocalToAbsoluteRect(PhysicalRect(rect))); |
| } |
| |
| gfx::Point |
| PaintLayerScrollableArea::ConvertFromScrollbarToContainingEmbeddedContentView( |
| const Scrollbar& scrollbar, |
| const gfx::Point& scrollbar_point) const { |
| LayoutView* view = GetLayoutBox()->View(); |
| if (!view) |
| return scrollbar_point; |
| |
| gfx::Point point = scrollbar_point + ScrollbarOffset(scrollbar); |
| return ToRoundedPoint( |
| GetLayoutBox()->LocalToAbsolutePoint(PhysicalOffset(point))); |
| } |
| |
| gfx::Point |
| PaintLayerScrollableArea::ConvertFromContainingEmbeddedContentViewToScrollbar( |
| const Scrollbar& scrollbar, |
| const gfx::Point& parent_point) const { |
| LayoutView* view = GetLayoutBox()->View(); |
| if (!view) |
| return parent_point; |
| |
| gfx::Point point = ToRoundedPoint( |
| GetLayoutBox()->AbsoluteToLocalPoint(PhysicalOffset(parent_point))); |
| point -= ScrollbarOffset(scrollbar); |
| return point; |
| } |
| |
| gfx::Point PaintLayerScrollableArea::ConvertFromRootFrame( |
| const gfx::Point& point_in_root_frame) const { |
| LayoutView* view = GetLayoutBox()->View(); |
| if (!view) |
| return point_in_root_frame; |
| |
| return view->GetFrameView()->ConvertFromRootFrame(point_in_root_frame); |
| } |
| |
| gfx::Point PaintLayerScrollableArea::ConvertFromRootFrameToVisualViewport( |
| const gfx::Point& point_in_root_frame) const { |
| LocalFrameView* frame_view = GetLayoutBox()->GetFrameView(); |
| DCHECK(frame_view); |
| const auto* page = frame_view->GetPage(); |
| const auto& viewport = page->GetVisualViewport(); |
| return viewport.RootFrameToViewport(point_in_root_frame); |
| } |
| |
| int PaintLayerScrollableArea::ScrollSize( |
| ScrollbarOrientation orientation) const { |
| gfx::Vector2d scroll_dimensions = |
| MaximumScrollOffsetInt() - MinimumScrollOffsetInt(); |
| return (orientation == kHorizontalScrollbar) ? scroll_dimensions.x() |
| : scroll_dimensions.y(); |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollOffset( |
| const ScrollOffset& new_offset, |
| mojom::blink::ScrollType scroll_type) { |
| if (HasBeenDisposed() || GetScrollOffset() == new_offset) |
| return; |
| |
| TRACE_EVENT2("blink", "PaintLayerScrollableArea::UpdateScrollOffset", "x", |
| new_offset.x(), "y", new_offset.y()); |
| TRACE_EVENT_INSTANT1("blink", "Type", TRACE_EVENT_SCOPE_THREAD, "type", |
| scroll_type); |
| |
| scroll_offset_ = new_offset; |
| |
| LocalFrame* frame = GetLayoutBox()->GetFrame(); |
| DCHECK(frame); |
| |
| LocalFrameView* frame_view = GetLayoutBox()->GetFrameView(); |
| bool is_root_layer = Layer()->IsRootLayer(); |
| |
| DEVTOOLS_TIMELINE_TRACE_EVENT( |
| "ScrollLayer", inspector_scroll_layer_event::Data, GetLayoutBox()); |
| |
| // Update the positions of our child layers (if needed as only fixed layers |
| // should be impacted by a scroll). |
| if (!frame_view->IsInPerformLayout()) { |
| // Update regions, scrolling may change the clip of a particular region. |
| frame_view->UpdateDocumentAnnotatedRegions(); |
| |
| // As a performance optimization, the scroll offset of the root layer is |
| // not included in EmbeddedContentView's stored frame rect, so there is no |
| // reason to mark the FrameView as needing a geometry update here. |
| if (is_root_layer) |
| frame_view->SetRootLayerDidScroll(); |
| else |
| frame_view->SetNeedsUpdateGeometries(); |
| } |
| |
| if (auto* scrolling_coordinator = GetScrollingCoordinator()) { |
| if (!scrolling_coordinator->UpdateCompositorScrollOffset(*frame, *this)) { |
| GetLayoutBox()->GetFrameView()->SetPaintArtifactCompositorNeedsUpdate(); |
| } |
| } |
| |
| // The ScrollOffsetTranslation paint property depends on the scroll offset. |
| // (see: PaintPropertyTreeBuilder::UpdateScrollAndScrollTranslation). |
| GetLayoutBox()->SetNeedsPaintPropertyUpdatePreservingCachedRects(); |
| |
| if (scroll_type == mojom::blink::ScrollType::kUser || |
| scroll_type == mojom::blink::ScrollType::kCompositor) { |
| Page* page = frame->GetPage(); |
| if (page) |
| page->GetChromeClient().ClearToolTip(*frame); |
| } |
| |
| InvalidatePaintForScrollOffsetChange(); |
| |
| // Don't enqueue a scroll event yet for scroll reasons that are not about |
| // explicit changes to scroll. Instead, only do so at the time of the next |
| // lifecycle update, to avoid scroll events that are out of date or don't |
| // result in an actual scroll that is visible to the user. These scroll events |
| // will then be dispatched at the *subsequent* animation frame, because |
| // they happen after layout and therefore the next opportunity to fire the |
| // events is at the next lifecycle update (*). |
| // |
| // (*) https://html.spec.whatwg.org/C/#update-the-rendering steps |
| if (scroll_type == mojom::blink::ScrollType::kClamping || |
| scroll_type == mojom::blink::ScrollType::kAnchoring) { |
| if (GetLayoutBox()->GetNode()) |
| frame_view->SetNeedsEnqueueScrollEvent(this); |
| } else { |
| EnqueueScrollEventIfNeeded(); |
| } |
| |
| GetLayoutBox()->View()->ClearHitTestCache(); |
| |
| // Inform the FrameLoader of the new scroll position, so it can be restored |
| // when navigating back. |
| if (is_root_layer) { |
| frame_view->GetFrame().Loader().SaveScrollState(); |
| frame_view->DidChangeScrollOffset(); |
| if (scroll_type == mojom::blink::ScrollType::kCompositor || |
| scroll_type == mojom::blink::ScrollType::kUser) { |
| if (DocumentLoader* document_loader = frame->Loader().GetDocumentLoader()) |
| document_loader->GetInitialScrollState().was_scrolled_by_user = true; |
| } |
| } |
| |
| if (FragmentAnchor* anchor = frame_view->GetFragmentAnchor()) |
| anchor->DidScroll(scroll_type); |
| |
| if (IsExplicitScrollType(scroll_type)) { |
| // We don't need to show scrollbars for kCompositor scrolls unless the |
| // scrollbar is non-composited (!NeedsCompositorScrolling). See |
| // PaintLayerScrollableArea::ShouldDirectlyCompositeScrollbar. |
| if (scroll_type != mojom::blink::ScrollType::kCompositor || |
| !NeedsCompositedScrolling()) { |
| ShowNonMacOverlayScrollbars(); |
| } |
| GetScrollAnchor()->Clear(); |
| } |
| if (ContentCaptureManager* manager = frame_view->GetFrame() |
| .LocalFrameRoot() |
| .GetOrResetContentCaptureManager()) { |
| manager->OnScrollPositionChanged(); |
| } |
| if (AXObjectCache* cache = |
| GetLayoutBox()->GetDocument().ExistingAXObjectCache()) |
| cache->HandleScrollPositionChanged(GetLayoutBox()); |
| } |
| |
| void PaintLayerScrollableArea::InvalidatePaintForScrollOffsetChange() { |
| InvalidatePaintForStickyDescendants(); |
| |
| auto* box = GetLayoutBox(); |
| auto* frame_view = box->GetFrameView(); |
| frame_view->InvalidateBackgroundAttachmentFixedDescendantsOnScroll(*box); |
| if (!box->BackgroundNeedsFullPaintInvalidation() && |
| BackgroundNeedsRepaintOnScroll()) { |
| box->SetBackgroundNeedsFullPaintInvalidation(); |
| } |
| } |
| |
| // See the comment in .h about background-attachment:fixed. |
| bool PaintLayerScrollableArea::BackgroundNeedsRepaintOnScroll() const { |
| const auto* box = GetLayoutBox(); |
| auto background_paint_location = box->GetBackgroundPaintLocation(); |
| bool background_paint_in_border_box = |
| background_paint_location & kBackgroundPaintInBorderBoxSpace; |
| bool background_paint_in_scrolling_contents = |
| background_paint_location & kBackgroundPaintInContentsSpace; |
| |
| const auto& background_layers = box->StyleRef().BackgroundLayers(); |
| if (background_layers.AnyLayerHasLocalAttachmentImage() && |
| background_paint_in_border_box) { |
| // Local-attachment background image scrolls, so needs invalidation if it |
| // paints in non-scrolling space. |
| return true; |
| } |
| if (background_layers.AnyLayerHasDefaultAttachmentImage() && |
| background_paint_in_scrolling_contents) { |
| // Normal attachment background image doesn't scroll, so needs |
| // invalidation if it paints in scrolling contents. |
| return true; |
| } |
| if (background_layers.AnyLayerHasLocalAttachment() && |
| background_layers.AnyLayerUsesContentBox() && |
| background_paint_in_border_box && |
| (box->PaddingLeft() || box->PaddingTop() || box->PaddingRight() || |
| box->PaddingBottom())) { |
| // Local attachment content box background needs invalidation if there is |
| // padding because the content area can change on scroll (e.g. the top |
| // padding can disappear when the box scrolls to the bottom). |
| return true; |
| } |
| return false; |
| } |
| |
| gfx::Vector2d PaintLayerScrollableArea::ScrollOffsetInt() const { |
| return gfx::ToFlooredVector2d(scroll_offset_); |
| } |
| |
| ScrollOffset PaintLayerScrollableArea::GetScrollOffset() const { |
| return scroll_offset_; |
| } |
| |
| void PaintLayerScrollableArea::EnqueueScrollEventIfNeeded() { |
| if (scroll_offset_ == last_committed_scroll_offset_ && |
| has_last_committed_scroll_offset_) |
| return; |
| last_committed_scroll_offset_ = scroll_offset_; |
| has_last_committed_scroll_offset_ = true; |
| if (HasBeenDisposed()) |
| return; |
| // Schedule the scroll DOM event. |
| if (auto* node = EventTargetNode()) |
| node->GetDocument().EnqueueScrollEventForNode(node); |
| } |
| |
| gfx::Vector2d PaintLayerScrollableArea::MinimumScrollOffsetInt() const { |
| return -ScrollOrigin().OffsetFromOrigin(); |
| } |
| |
| gfx::Vector2d PaintLayerScrollableArea::MaximumScrollOffsetInt() const { |
| if (!GetLayoutBox() || !GetLayoutBox()->IsScrollContainer()) |
| return -ScrollOrigin().OffsetFromOrigin(); |
| |
| gfx::Size content_size = ContentsSize(); |
| |
| Page* page = GetLayoutBox()->GetDocument().GetPage(); |
| DCHECK(page); |
| TopDocumentRootScrollerController& controller = |
| page->GlobalRootScrollerController(); |
| |
| // The global root scroller should be clipped by the top LocalFrameView rather |
| // than it's overflow clipping box. This is to ensure that content exposed by |
| // hiding the URL bar at the bottom of the screen is visible. |
| gfx::Size visible_size; |
| if (this == controller.RootScrollerArea()) { |
| visible_size = controller.RootScrollerVisibleArea(); |
| } else { |
| visible_size = ToPixelSnappedRect(GetLayoutBox()->OverflowClipRect( |
| GetLayoutBox()->Location(), |
| kIgnoreOverlayScrollbarSize)) |
| .size(); |
| } |
| |
| // TODO(skobes): We should really ASSERT that contentSize >= visibleSize |
| // when we are not the root layer, but we can't because contentSize is |
| // based on stale layout overflow data (http://crbug.com/576933). |
| content_size.SetToMax(visible_size); |
| |
| return -ScrollOrigin().OffsetFromOrigin() + |
| gfx::Vector2d(content_size.width() - visible_size.width(), |
| content_size.height() - visible_size.height()); |
| } |
| |
| void PaintLayerScrollableArea::VisibleSizeChanged() { |
| ShowNonMacOverlayScrollbars(); |
| } |
| |
| PhysicalRect PaintLayerScrollableArea::LayoutContentRect( |
| IncludeScrollbarsInRect scrollbar_inclusion) const { |
| // LayoutContentRect is conceptually the same as the box's client rect. |
| LayoutSize layer_size = Size(); |
| LayoutUnit border_width = GetLayoutBox()->BorderWidth(); |
| LayoutUnit border_height = GetLayoutBox()->BorderHeight(); |
| NGPhysicalBoxStrut scrollbars; |
| if (scrollbar_inclusion == kExcludeScrollbars) |
| scrollbars = GetLayoutBox()->ComputeScrollbars(); |
| |
| PhysicalSize size( |
| layer_size.Width() - border_width - scrollbars.HorizontalSum(), |
| layer_size.Height() - border_height - scrollbars.VerticalSum()); |
| size.ClampNegativeToZero(); |
| return PhysicalRect(PhysicalOffset::FromPointFRound(ScrollPosition()), size); |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::VisibleContentRect( |
| IncludeScrollbarsInRect scrollbar_inclusion) const { |
| PhysicalRect layout_content_rect(LayoutContentRect(scrollbar_inclusion)); |
| // TODO(szager): It's not clear that Floor() is the right thing to do here; |
| // what is the correct behavior for fractional scroll offsets? |
| return gfx::Rect(ToFlooredPoint(layout_content_rect.offset), |
| ToPixelSnappedSize(layout_content_rect.size.ToLayoutSize(), |
| GetLayoutBox()->Location())); |
| } |
| |
| PhysicalRect PaintLayerScrollableArea::VisibleScrollSnapportRect( |
| IncludeScrollbarsInRect scrollbar_inclusion) const { |
| const ComputedStyle* style = GetLayoutBox()->Style(); |
| PhysicalRect layout_content_rect(LayoutContentRect(scrollbar_inclusion)); |
| layout_content_rect.Move(PhysicalOffset(-ScrollOrigin().OffsetFromOrigin())); |
| NGPhysicalBoxStrut padding( |
| MinimumValueForLength(style->ScrollPaddingTop(), |
| layout_content_rect.Height()), |
| MinimumValueForLength(style->ScrollPaddingRight(), |
| layout_content_rect.Width()), |
| MinimumValueForLength(style->ScrollPaddingBottom(), |
| layout_content_rect.Height()), |
| MinimumValueForLength(style->ScrollPaddingLeft(), |
| layout_content_rect.Width())); |
| layout_content_rect.Contract(padding); |
| return layout_content_rect; |
| } |
| |
| gfx::Size PaintLayerScrollableArea::ContentsSize() const { |
| LayoutPoint location = GetLayoutBox()->Location(); |
| PhysicalOffset offset(GetLayoutBox()->ClientLeft() + location.X(), |
| GetLayoutBox()->ClientTop() + location.Y()); |
| // TODO(crbug.com/962299): The pixel snapping is incorrect in some cases. |
| return PixelSnappedContentsSize(offset); |
| } |
| |
| gfx::Size PaintLayerScrollableArea::PixelSnappedContentsSize( |
| const PhysicalOffset& paint_offset) const { |
| PhysicalSize size = overflow_rect_.size; |
| |
| // If we're capturing a transition snapshot, ensure the content size is |
| // considered at least as large as the container. Otherwise, the snapshot |
| // will be clipped by PendingLayer to the content size. |
| if (IsA<LayoutView>(GetLayoutBox())) { |
| if (auto* transition = ViewTransitionUtils::GetActiveTransition( |
| GetLayoutBox()->GetDocument()); |
| transition && transition->IsRootTransitioning()) { |
| PhysicalSize container_size(transition->GetSnapshotRootSize()); |
| size.width = std::max(container_size.width, size.width); |
| size.height = std::max(container_size.height, size.height); |
| } |
| } |
| |
| return ToPixelSnappedRect(PhysicalRect(paint_offset, size)).size(); |
| } |
| |
| void PaintLayerScrollableArea::ContentsResized() { |
| ScrollableArea::ContentsResized(); |
| // Need to update the bounds of the scroll property. |
| GetLayoutBox()->SetNeedsPaintPropertyUpdate(); |
| Layer()->SetNeedsCompositingInputsUpdate(); |
| } |
| |
| gfx::Point PaintLayerScrollableArea::LastKnownMousePosition() const { |
| return GetLayoutBox()->GetFrame() |
| ? gfx::ToFlooredPoint(GetLayoutBox() |
| ->GetFrame() |
| ->GetEventHandler() |
| .LastKnownMousePositionInRootFrame()) |
| : gfx::Point(); |
| } |
| |
| bool PaintLayerScrollableArea::ScrollAnimatorEnabled() const { |
| if (HasBeenDisposed()) |
| return false; |
| if (Settings* settings = GetLayoutBox()->GetFrame()->GetSettings()) |
| return settings->GetScrollAnimatorEnabled(); |
| return false; |
| } |
| |
| bool PaintLayerScrollableArea::ShouldSuspendScrollAnimations() const { |
| if (HasBeenDisposed()) |
| return true; |
| LayoutView* view = GetLayoutBox()->View(); |
| if (!view) |
| return true; |
| return !GetLayoutBox()->GetDocument().LoadEventFinished(); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarVisibilityChanged() { |
| UpdateScrollbarEnabledState(); |
| |
| // Paint properties need to be updated, because clip rects |
| // are affected by overlay scrollbars. |
| layer_->GetLayoutObject().SetNeedsPaintPropertyUpdate(); |
| |
| if (LayoutView* view = GetLayoutBox()->View()) |
| view->ClearHitTestCache(); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarFrameRectChanged() { |
| // TODO(crbug.com/1020913): This should be called only from layout once the |
| // bug is fixed. |
| |
| // Size of non-overlay scrollbar affects overflow clip rect. size of overlay |
| // scrollbar effects hit testing rect excluding overlay scrollbars. |
| if (GetDocument()->Lifecycle().GetState() == DocumentLifecycle::kInPrePaint) { |
| // In pre-paint we avoid marking the ancestor chain as this might cause |
| // problems, see https://crbug.com/1377634. Note that we do not have |
| // automated test case for this, so if you when modifying this code, please |
| // verify that the test cases on the bug do not crash. |
| GetLayoutBox() |
| ->GetMutableForPainting() |
| .SetOnlyThisNeedsPaintPropertyUpdate(); |
| return; |
| } |
| |
| GetLayoutBox()->SetNeedsPaintPropertyUpdate(); |
| } |
| |
| bool PaintLayerScrollableArea::ScrollbarsCanBeActive() const { |
| LayoutView* view = GetLayoutBox()->View(); |
| if (!view) |
| return false; |
| |
| // TODO(szager): This conditional is weird and likely obsolete. Originally |
| // added in commit eb0d49caaee2b275ff524d3945a74e8d9180eb7d. |
| LocalFrameView* frame_view = view->GetFrameView(); |
| if (frame_view != frame_view->GetFrame().View()) |
| return false; |
| |
| return !!frame_view->GetFrame().GetDocument(); |
| } |
| |
| void PaintLayerScrollableArea::RegisterForAnimation() { |
| if (HasBeenDisposed()) |
| return; |
| if (LocalFrame* frame = GetLayoutBox()->GetFrame()) { |
| if (LocalFrameView* frame_view = frame->View()) |
| frame_view->AddAnimatingScrollableArea(this); |
| } |
| } |
| |
| void PaintLayerScrollableArea::DeregisterForAnimation() { |
| if (HasBeenDisposed()) |
| return; |
| if (LocalFrame* frame = GetLayoutBox()->GetFrame()) { |
| if (LocalFrameView* frame_view = frame->View()) |
| frame_view->RemoveAnimatingScrollableArea(this); |
| } |
| } |
| |
| bool PaintLayerScrollableArea::UserInputScrollable( |
| ScrollbarOrientation orientation) const { |
| if (orientation == kVerticalScrollbar && |
| GetLayoutBox()->GetDocument().IsVerticalScrollEnforced()) { |
| return false; |
| } |
| |
| if (GetLayoutBox()->IsIntrinsicallyScrollable(orientation)) |
| return true; |
| |
| if (IsA<LayoutView>(GetLayoutBox())) { |
| Document& document = GetLayoutBox()->GetDocument(); |
| Element* fullscreen_element = Fullscreen::FullscreenElementFrom(document); |
| if (fullscreen_element && fullscreen_element != document.documentElement()) |
| return false; |
| |
| mojom::blink::ScrollbarMode h_mode; |
| mojom::blink::ScrollbarMode v_mode; |
| To<LayoutView>(GetLayoutBox())->CalculateScrollbarModes(h_mode, v_mode); |
| mojom::blink::ScrollbarMode mode = |
| (orientation == kHorizontalScrollbar) ? h_mode : v_mode; |
| return mode == mojom::blink::ScrollbarMode::kAuto || |
| mode == mojom::blink::ScrollbarMode::kAlwaysOn; |
| } |
| |
| EOverflow overflow_style = (orientation == kHorizontalScrollbar) |
| ? GetLayoutBox()->StyleRef().OverflowX() |
| : GetLayoutBox()->StyleRef().OverflowY(); |
| return (overflow_style == EOverflow::kScroll || |
| overflow_style == EOverflow::kAuto || |
| overflow_style == EOverflow::kOverlay); |
| } |
| |
| bool PaintLayerScrollableArea::ShouldPlaceVerticalScrollbarOnLeft() const { |
| return GetLayoutBox()->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft(); |
| } |
| |
| int PaintLayerScrollableArea::PageStep(ScrollbarOrientation orientation) const { |
| // Paging scroll operations should take scroll-padding into account [1]. So we |
| // use the snapport rect to calculate the page step instead of the visible |
| // rect. |
| // [1] https://drafts.csswg.org/css-scroll-snap/#scroll-padding |
| gfx::Size snapport_size = VisibleScrollSnapportRect().PixelSnappedSize(); |
| int length = (orientation == kHorizontalScrollbar) ? snapport_size.width() |
| : snapport_size.height(); |
| int min_page_step = static_cast<float>(length) * |
| ScrollableArea::MinFractionToStepWhenPaging(); |
| int page_step = max(min_page_step, length - MaxOverlapBetweenPages()); |
| return max(page_step, 1); |
| } |
| |
| bool PaintLayerScrollableArea::IsRootFrameLayoutViewport() const { |
| LocalFrame* frame = GetLayoutBox()->GetFrame(); |
| if (!frame || !frame->View()) |
| return false; |
| |
| RootFrameViewport* root_frame_viewport = |
| frame->View()->GetRootFrameViewport(); |
| if (!root_frame_viewport) |
| return false; |
| |
| return &root_frame_viewport->LayoutViewport() == this; |
| } |
| |
| LayoutBox* PaintLayerScrollableArea::GetLayoutBox() const { |
| return layer_ ? layer_->GetLayoutBox() : nullptr; |
| } |
| |
| PaintLayer* PaintLayerScrollableArea::Layer() const { |
| return layer_; |
| } |
| |
| LayoutSize PaintLayerScrollableArea::Size() const { |
| return layer_->IsRootLayer() |
| ? LayoutSize(GetLayoutBox()->GetFrameView()->Size()) |
| : GetLayoutBox()->Size(); |
| } |
| |
| LayoutUnit PaintLayerScrollableArea::ScrollWidth() const { |
| return overflow_rect_.Width(); |
| } |
| |
| LayoutUnit PaintLayerScrollableArea::ScrollHeight() const { |
| return overflow_rect_.Height(); |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollOrigin() { |
| // This should do nothing prior to first layout; the if-clause will catch |
| // that. |
| if (overflow_rect_.IsEmpty()) |
| return; |
| PhysicalRect scrollable_overflow = overflow_rect_; |
| scrollable_overflow.Move(-PhysicalOffset(GetLayoutBox()->BorderLeft(), |
| GetLayoutBox()->BorderTop())); |
| gfx::Point new_origin = ToFlooredPoint(-scrollable_overflow.offset) + |
| GetLayoutBox()->OriginAdjustmentForScrollbars(); |
| if (new_origin != scroll_origin_) { |
| scroll_origin_changed_ = true; |
| // ScrollOrigin affects paint offsets of the scrolling contents. |
| GetLayoutBox()->SetSubtreeShouldCheckForPaintInvalidation(); |
| } |
| scroll_origin_ = new_origin; |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollDimensions() { |
| PhysicalRect new_overflow_rect = GetLayoutBox()->PhysicalLayoutOverflowRect(); |
| |
| // The layout viewport can be larger than the document's layout overflow when |
| // top controls are hidden. Expand the overflow here to ensure that our |
| // contents size >= visible size. |
| new_overflow_rect.Unite(PhysicalRect( |
| new_overflow_rect.offset, LayoutContentRect(kExcludeScrollbars).size)); |
| |
| bool resized = overflow_rect_.size != new_overflow_rect.size; |
| overflow_rect_ = new_overflow_rect; |
| if (resized) |
| ContentsResized(); |
| UpdateScrollOrigin(); |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollbarEnabledState( |
| bool is_horizontal_scrollbar_frozen, |
| bool is_vertical_scrollbar_frozen) { |
| bool force_disable = |
| GetPageScrollbarTheme().ShouldDisableInvisibleScrollbars() && |
| ScrollbarsHiddenIfOverlay(); |
| |
| // Don't update the enabled state of a custom scrollbar if that scrollbar |
| // is frozen. Otherwise re-running the style cascade with the change in |
| // :disabled pseudo state matching for custom scrollbars can cause infinite |
| // loops in layout. |
| if (Scrollbar* horizontal_scrollbar = HorizontalScrollbar()) { |
| if (!horizontal_scrollbar->IsCustomScrollbar() || |
| !is_horizontal_scrollbar_frozen) { |
| horizontal_scrollbar->SetEnabled(HasHorizontalOverflow() && |
| !force_disable); |
| } |
| } |
| |
| if (Scrollbar* vertical_scrollbar = VerticalScrollbar()) { |
| if (!vertical_scrollbar->IsCustomScrollbar() || |
| !is_vertical_scrollbar_frozen) { |
| vertical_scrollbar->SetEnabled(HasVerticalOverflow() && !force_disable); |
| } |
| } |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollbarProportions() { |
| if (Scrollbar* horizontal_scrollbar = HorizontalScrollbar()) |
| horizontal_scrollbar->SetProportion(VisibleWidth(), ContentsSize().width()); |
| if (Scrollbar* vertical_scrollbar = VerticalScrollbar()) |
| vertical_scrollbar->SetProportion(VisibleHeight(), ContentsSize().height()); |
| } |
| |
| void PaintLayerScrollableArea::SetScrollOffsetUnconditionally( |
| const ScrollOffset& offset, |
| mojom::blink::ScrollType scroll_type) { |
| CancelScrollAnimation(); |
| ScrollOffsetChanged(offset, scroll_type); |
| } |
| |
| void PaintLayerScrollableArea::UpdateAfterLayout() { |
| InvalidateAllStickyConstraints(); |
| |
| bool is_horizontal_scrollbar_frozen = IsHorizontalScrollbarFrozen(); |
| bool is_vertical_scrollbar_frozen = IsVerticalScrollbarFrozen(); |
| |
| if (NeedsScrollbarReconstruction()) { |
| RemoveScrollbarsForReconstruction(); |
| // In case that DelayScrollOffsetClampScope prevented destruction of the |
| // scrollbars. |
| scrollbar_manager_.DestroyDetachedScrollbars(); |
| } |
| |
| UpdateScrollDimensions(); |
| |
| bool has_resizer = GetLayoutBox()->CanResize(); |
| bool resizer_will_change = had_resizer_before_relayout_ != has_resizer; |
| had_resizer_before_relayout_ = has_resizer; |
| |
| bool had_horizontal_scrollbar = HasHorizontalScrollbar(); |
| bool had_vertical_scrollbar = HasVerticalScrollbar(); |
| |
| bool needs_horizontal_scrollbar; |
| bool needs_vertical_scrollbar; |
| ComputeScrollbarExistence(kLayout, needs_horizontal_scrollbar, |
| needs_vertical_scrollbar); |
| |
| if (!is_horizontal_scrollbar_frozen && !is_vertical_scrollbar_frozen && |
| TryRemovingAutoScrollbars(needs_horizontal_scrollbar, |
| needs_vertical_scrollbar)) { |
| needs_horizontal_scrollbar = needs_vertical_scrollbar = false; |
| } |
| |
| bool horizontal_scrollbar_should_change = |
| needs_horizontal_scrollbar != had_horizontal_scrollbar; |
| bool vertical_scrollbar_should_change = |
| needs_vertical_scrollbar != had_vertical_scrollbar; |
| |
| bool scrollbars_will_change = |
| (horizontal_scrollbar_should_change && !is_horizontal_scrollbar_frozen) || |
| (vertical_scrollbar_should_change && !is_vertical_scrollbar_frozen); |
| if (scrollbars_will_change) { |
| SetHasHorizontalScrollbar(needs_horizontal_scrollbar); |
| SetHasVerticalScrollbar(needs_vertical_scrollbar); |
| |
| // If we change scrollbars on the layout viewport, the visual viewport |
| // needs to update paint properties to account for the correct |
| // scrollbounds. |
| if (LocalFrameView* frame_view = GetLayoutBox()->GetFrameView()) { |
| VisualViewport& visual_viewport = |
| GetLayoutBox()->GetFrame()->GetPage()->GetVisualViewport(); |
| if (this == frame_view->LayoutViewport() && |
| visual_viewport.IsActiveViewport()) { |
| visual_viewport.SetNeedsPaintPropertyUpdate(); |
| } |
| } |
| |
| UpdateScrollCornerStyle(); |
| |
| Layer()->UpdateSelfPaintingLayer(); |
| |
| // Force an update since we know the scrollbars have changed things. |
| if (GetLayoutBox()->GetDocument().HasAnnotatedRegions()) |
| GetLayoutBox()->GetDocument().SetAnnotatedRegionsDirty(true); |
| |
| // Our proprietary overflow: overlay value doesn't trigger a layout. |
| if (((horizontal_scrollbar_should_change && |
| GetLayoutBox()->StyleRef().OverflowX() != EOverflow::kOverlay) || |
| (vertical_scrollbar_should_change && |
| GetLayoutBox()->StyleRef().OverflowY() != EOverflow::kOverlay))) { |
| if ((vertical_scrollbar_should_change && |
| GetLayoutBox()->IsHorizontalWritingMode()) || |
| (horizontal_scrollbar_should_change && |
| !GetLayoutBox()->IsHorizontalWritingMode())) { |
| GetLayoutBox()->SetIntrinsicLogicalWidthsDirty(); |
| } |
| // Just update the rectangles, in case scrollbars were added or |
| // removed. The calling code on the layout side has its own scrollbar |
| // change detection mechanism. |
| UpdateScrollDimensions(); |
| } |
| } else if (!HasScrollbar() && resizer_will_change) { |
| Layer()->DirtyStackingContextZOrderLists(); |
| } |
| // The snap container data will be updated at the end of the layout update. If |
| // the data changes, then this will try to re-snap. |
| SetSnapContainerDataNeedsUpdate(true); |
| { |
| UpdateScrollbarEnabledState(is_horizontal_scrollbar_frozen, |
| is_vertical_scrollbar_frozen); |
| |
| UpdateScrollbarProportions(); |
| } |
| |
| hypothetical_horizontal_scrollbar_thickness_ = 0; |
| if (NeedsHypotheticalScrollbarThickness(kHorizontalScrollbar)) { |
| hypothetical_horizontal_scrollbar_thickness_ = |
| ComputeHypotheticalScrollbarThickness(kHorizontalScrollbar, true); |
| } |
| hypothetical_vertical_scrollbar_thickness_ = 0; |
| if (NeedsHypotheticalScrollbarThickness(kVerticalScrollbar)) { |
| hypothetical_vertical_scrollbar_thickness_ = |
| ComputeHypotheticalScrollbarThickness(kVerticalScrollbar, true); |
| } |
| |
| DelayableClampScrollOffsetAfterOverflowChange(); |
| |
| if (!is_horizontal_scrollbar_frozen || !is_vertical_scrollbar_frozen) |
| UpdateScrollableAreaSet(); |
| |
| PositionOverflowControls(); |
| } |
| |
| void PaintLayerScrollableArea::DelayableClampScrollOffsetAfterOverflowChange() { |
| if (HasBeenDisposed()) |
| return; |
| if (DelayScrollOffsetClampScope::ClampingIsDelayed()) { |
| DelayScrollOffsetClampScope::SetNeedsClamp(this); |
| return; |
| } |
| ClampScrollOffsetAfterOverflowChangeInternal(); |
| } |
| |
| void PaintLayerScrollableArea::ClampScrollOffsetAfterOverflowChange() { |
| ClampScrollOffsetAfterOverflowChangeInternal(); |
| } |
| |
| void PaintLayerScrollableArea::ClampScrollOffsetAfterOverflowChangeInternal() { |
| if (HasBeenDisposed()) |
| return; |
| |
| // If a vertical scrollbar was removed, the min/max scroll offsets may have |
| // changed, so the scroll offsets needs to be clamped. If the scroll offset |
| // did not change, but the scroll origin *did* change, we still need to notify |
| // the scrollbars to update their dimensions. |
| |
| const Document& document = GetLayoutBox()->GetDocument(); |
| if (document.IsPrintingOrPaintingPreview()) { |
| // Scrollable elements may change size when generating layout for printing, |
| // which may require them to change the scroll position in order to keep the |
| // same content within view. In vertical-rl writing-mode, even the root |
| // frame may be attempted scrolled, because a viewport size change may |
| // affect scroll origin. Save all scroll offsets before clamping, so that |
| // everything can be restored the way it was after printing. |
| if (Node* node = EventTargetNode()) |
| document.GetFrame()->EnsureSaveScrollOffset(*node); |
| } |
| |
| UpdateScrollDimensions(); |
| if (ScrollOriginChanged()) { |
| SetScrollOffsetUnconditionally(ClampScrollOffset(GetScrollOffset())); |
| } else { |
| ScrollableArea::SetScrollOffset(GetScrollOffset(), |
| mojom::blink::ScrollType::kClamping); |
| } |
| |
| SetNeedsScrollOffsetClamp(false); |
| ResetScrollOriginChanged(); |
| scrollbar_manager_.DestroyDetachedScrollbars(); |
| } |
| |
| void PaintLayerScrollableArea::DidChangeGlobalRootScroller() { |
| // Being the global root scroller will affect clipping size due to browser |
| // controls behavior so we need to update compositing based on updated clip |
| // geometry. |
| Layer()->SetNeedsCompositingInputsUpdate(); |
| GetLayoutBox()->SetNeedsPaintPropertyUpdate(); |
| |
| // On Android, where the VisualViewport supplies scrollbars, we need to |
| // remove the PLSA's scrollbars if we become the global root scroller. |
| // In general, this would be problematic as that can cause layout but this |
| // should only ever apply with overlay scrollbars. |
| if (GetLayoutBox()->GetFrame()->GetSettings() && |
| GetLayoutBox()->GetFrame()->GetSettings()->GetViewportEnabled()) { |
| bool needs_horizontal_scrollbar; |
| bool needs_vertical_scrollbar; |
| ComputeScrollbarExistence(kRootScrollerChange, needs_horizontal_scrollbar, |
| needs_vertical_scrollbar); |
| SetHasHorizontalScrollbar(needs_horizontal_scrollbar); |
| SetHasVerticalScrollbar(needs_vertical_scrollbar); |
| } |
| |
| // Recalculate the snap container data since the scrolling behaviour for this |
| // layout box changed (i.e. it either became the layout viewport or it |
| // is no longer the layout viewport). |
| SetSnapContainerDataNeedsUpdate(true); |
| } |
| |
| bool PaintLayerScrollableArea::ShouldPerformScrollAnchoring() const { |
| return scroll_anchor_.HasScroller() && GetLayoutBox() && |
| GetLayoutBox()->StyleRef().OverflowAnchor() != |
| EOverflowAnchor::kNone && |
| !GetLayoutBox()->GetDocument().FinishingOrIsPrinting(); |
| } |
| |
| bool PaintLayerScrollableArea::RestoreScrollAnchor( |
| const SerializedAnchor& serialized_anchor) { |
| return ShouldPerformScrollAnchoring() && |
| scroll_anchor_.RestoreAnchor(serialized_anchor); |
| } |
| |
| gfx::QuadF PaintLayerScrollableArea::LocalToVisibleContentQuad( |
| const gfx::QuadF& quad, |
| const LayoutObject* local_object, |
| MapCoordinatesFlags flags) const { |
| LayoutBox* box = GetLayoutBox(); |
| if (!box) |
| return quad; |
| DCHECK(local_object); |
| return local_object->LocalToAncestorQuad(quad, box, flags); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> |
| PaintLayerScrollableArea::GetTimerTaskRunner() const { |
| return GetLayoutBox()->GetFrame()->GetTaskRunner(TaskType::kInternalDefault); |
| } |
| |
| mojom::blink::ScrollBehavior PaintLayerScrollableArea::ScrollBehaviorStyle() |
| const { |
| return GetLayoutBox()->StyleRef().GetScrollBehavior(); |
| } |
| |
| mojom::blink::ColorScheme PaintLayerScrollableArea::UsedColorSchemeScrollbars() |
| const { |
| if (RuntimeEnabledFeatures::UsedColorSchemeRootScrollbarsEnabled() && |
| GetLayoutBox()->IsGlobalRootScroller() && |
| !GetPageScrollbarTheme().UsesOverlayScrollbars()) { |
| const Document& document = GetLayoutBox()->GetDocument(); |
| if (document.documentElement() && |
| document.documentElement()->GetComputedStyle() && |
| document.documentElement()->GetComputedStyle()->ColorScheme().empty() && |
| document.GetStyleEngine().GetPageColorSchemes() == |
| static_cast<ColorSchemeFlags>(ColorSchemeFlag::kNormal) && |
| document.GetPreferredColorScheme() == |
| mojom::blink::PreferredColorScheme::kDark) { |
| return mojom::blink::ColorScheme::kDark; |
| } |
| } |
| |
| return GetLayoutBox()->StyleRef().UsedColorScheme(); |
| } |
| |
| bool PaintLayerScrollableArea::HasHorizontalOverflow() const { |
| // TODO(szager): Make the algorithm for adding/subtracting overflow:auto |
| // scrollbars memoryless (crbug.com/625300). This client_width hack will |
| // prevent the spurious horizontal scrollbar, but it can cause a converse |
| // problem: it can leave a sliver of horizontal overflow hidden behind the |
| // vertical scrollbar without creating a horizontal scrollbar. This |
| // converse problem seems to happen much less frequently in practice, so we |
| // bias the logic towards preventing unwanted horizontal scrollbars, which |
| // are more common and annoying. |
| LayoutUnit client_width = LayoutContentRect(kIncludeScrollbars).Width() - |
| VerticalScrollbarWidth(kIgnoreOverlayScrollbarSize); |
| if (NeedsRelayout() && !HadVerticalScrollbarBeforeRelayout()) |
| client_width += VerticalScrollbarWidth(); |
| LayoutUnit scroll_width(ScrollWidth()); |
| LayoutUnit box_x = GetLayoutBox()->Location().X(); |
| return SnapSizeToPixel(scroll_width, box_x) > |
| SnapSizeToPixel(client_width, box_x); |
| } |
| |
| bool PaintLayerScrollableArea::HasVerticalOverflow() const { |
| LayoutUnit client_height = |
| LayoutContentRect(kIncludeScrollbars).Height() - |
| HorizontalScrollbarHeight(kIgnoreOverlayScrollbarSize); |
| LayoutUnit scroll_height(ScrollHeight()); |
| LayoutUnit box_y = GetLayoutBox()->Location().Y(); |
| return SnapSizeToPixel(scroll_height, box_y) > |
| SnapSizeToPixel(client_height, box_y); |
| } |
| |
| // This function returns true if the given box requires overflow scrollbars (as |
| // opposed to the viewport scrollbars managed by VisualViewport). |
| static bool CanHaveOverflowScrollbars(const LayoutBox& box) { |
| return box.GetDocument().ViewportDefiningElement() != box.GetNode(); |
| } |
| |
| void PaintLayerScrollableArea::UpdateAfterStyleChange( |
| const ComputedStyle* old_style) { |
| // Don't do this on first style recalc, before layout has ever happened. |
| if (!overflow_rect_.size.IsZero()) |
| UpdateScrollableAreaSet(); |
| |
| UpdateResizerStyle(old_style); |
| |
| // The scrollbar overlay color theme depends on styles such as the background |
| // color and the used color scheme. |
| RecalculateScrollbarOverlayColorTheme(); |
| |
| if (NeedsScrollbarReconstruction()) { |
| RemoveScrollbarsForReconstruction(); |
| return; |
| } |
| |
| bool needs_horizontal_scrollbar; |
| bool needs_vertical_scrollbar; |
| ComputeScrollbarExistence(kStyleChange, needs_horizontal_scrollbar, |
| needs_vertical_scrollbar, kOverflowIndependent); |
| |
| // Avoid some unnecessary computation if there were and will be no scrollbars. |
| if (!HasScrollbar() && !needs_horizontal_scrollbar && |
| !needs_vertical_scrollbar) |
| return; |
| |
| SetHasHorizontalScrollbar(needs_horizontal_scrollbar); |
| SetHasVerticalScrollbar(needs_vertical_scrollbar); |
| |
| if (HorizontalScrollbar()) |
| HorizontalScrollbar()->StyleChanged(); |
| if (VerticalScrollbar()) |
| VerticalScrollbar()->StyleChanged(); |
| |
| UpdateScrollCornerStyle(); |
| |
| if (!old_style || |
| old_style->UsedColorScheme() != UsedColorSchemeScrollbars() || |
| old_style->ScrollbarWidth() != |
| GetLayoutBox()->StyleRef().ScrollbarWidth()) { |
| SetScrollControlsNeedFullPaintInvalidation(); |
| } |
| } |
| |
| void PaintLayerScrollableArea::UpdateAfterOverflowRecalc() { |
| UpdateScrollDimensions(); |
| UpdateScrollbarProportions(); |
| UpdateScrollbarEnabledState(); |
| |
| bool needs_horizontal_scrollbar; |
| bool needs_vertical_scrollbar; |
| ComputeScrollbarExistence(kOverflowRecalc, needs_horizontal_scrollbar, |
| needs_vertical_scrollbar); |
| |
| bool horizontal_scrollbar_should_change = |
| needs_horizontal_scrollbar != HasHorizontalScrollbar(); |
| bool vertical_scrollbar_should_change = |
| needs_vertical_scrollbar != HasVerticalScrollbar(); |
| |
| if ((GetLayoutBox()->HasAutoHorizontalScrollbar() && |
| horizontal_scrollbar_should_change) || |
| (GetLayoutBox()->HasAutoVerticalScrollbar() && |
| vertical_scrollbar_should_change)) { |
| GetLayoutBox()->SetNeedsLayoutAndFullPaintInvalidation( |
| layout_invalidation_reason::kUnknown); |
| } |
| |
| ClampScrollOffsetAfterOverflowChange(); |
| UpdateScrollableAreaSet(); |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::RectForHorizontalScrollbar() const { |
| if (!HasHorizontalScrollbar()) |
| return gfx::Rect(); |
| |
| const gfx::Rect& scroll_corner = ScrollCornerRect(); |
| gfx::Size border_box_size = PixelSnappedBorderBoxSize(); |
| return gfx::Rect( |
| HorizontalScrollbarStart(), |
| border_box_size.height() - GetLayoutBox()->BorderBottom().ToInt() - |
| HorizontalScrollbar()->ScrollbarThickness(), |
| border_box_size.width() - |
| (GetLayoutBox()->BorderLeft() + GetLayoutBox()->BorderRight()) |
| .ToInt() - |
| scroll_corner.width(), |
| HorizontalScrollbar()->ScrollbarThickness()); |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::RectForVerticalScrollbar() const { |
| if (!HasVerticalScrollbar()) |
| return gfx::Rect(); |
| |
| const gfx::Rect& scroll_corner = ScrollCornerRect(); |
| return gfx::Rect( |
| VerticalScrollbarStart(), GetLayoutBox()->BorderTop().ToInt(), |
| VerticalScrollbar()->ScrollbarThickness(), |
| PixelSnappedBorderBoxSize().height() - |
| (GetLayoutBox()->BorderTop() + GetLayoutBox()->BorderBottom()) |
| .ToInt() - |
| scroll_corner.height()); |
| } |
| |
| int PaintLayerScrollableArea::VerticalScrollbarStart() const { |
| if (GetLayoutBox()->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| return GetLayoutBox()->BorderLeft().ToInt(); |
| return PixelSnappedBorderBoxSize().width() - |
| GetLayoutBox()->BorderRight().ToInt() - |
| VerticalScrollbar()->ScrollbarThickness(); |
| } |
| |
| int PaintLayerScrollableArea::HorizontalScrollbarStart() const { |
| int x = GetLayoutBox()->BorderLeft().ToInt(); |
| if (GetLayoutBox()->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { |
| x += HasVerticalScrollbar() ? VerticalScrollbar()->ScrollbarThickness() |
| : ResizerCornerRect(kResizerForPointer).width(); |
| } |
| return x; |
| } |
| |
| gfx::Vector2d PaintLayerScrollableArea::ScrollbarOffset( |
| const Scrollbar& scrollbar) const { |
| // TODO(szager): Factor out vertical offset calculation into other methods, |
| // for symmetry with *ScrollbarStart methods for horizontal offset. |
| if (&scrollbar == VerticalScrollbar()) { |
| return gfx::Vector2d(VerticalScrollbarStart(), |
| GetLayoutBox()->BorderTop().ToInt()); |
| } |
| |
| if (&scrollbar == HorizontalScrollbar()) { |
| return gfx::Vector2d(HorizontalScrollbarStart(), |
| GetLayoutBox()->BorderTop().ToInt() + |
| VisibleContentRect(kIncludeScrollbars).height() - |
| HorizontalScrollbar()->ScrollbarThickness()); |
| } |
| |
| NOTREACHED(); |
| return gfx::Vector2d(); |
| } |
| |
| static inline const LayoutObject& ScrollbarStyleSource( |
| const LayoutBox& layout_box) { |
| if (IsA<LayoutView>(layout_box)) { |
| Document& doc = layout_box.GetDocument(); |
| |
| // If scrollbar properties have been set on the root element, they will be |
| // propagated to the viewport. |
| Element* doc_element = doc.documentElement(); |
| if (doc_element && doc_element->GetLayoutObject() && |
| doc_element->GetLayoutObject()->StyleRef().ScrollbarWidth() != |
| EScrollbarWidth::kAuto) |
| return *doc_element->GetLayoutObject(); |
| |
| if (Settings* settings = doc.GetSettings()) { |
| LocalFrame* frame = layout_box.GetFrame(); |
| DCHECK(frame); |
| DCHECK(frame->GetPage()); |
| |
| VisualViewport& viewport = frame->GetPage()->GetVisualViewport(); |
| if (!settings->GetAllowCustomScrollbarInMainFrame() && |
| frame->IsMainFrame() && viewport.IsActiveViewport()) { |
| return layout_box; |
| } |
| } |
| |
| // Try the <body> element as a scrollbar source, but only if the body |
| // can scroll. |
| Element* body = doc.body(); |
| if (body && body->GetLayoutObject() && body->GetLayoutObject()->IsBox() && |
| body->GetLayoutObject()->StyleRef().HasCustomScrollbarStyle()) |
| return *body->GetLayoutObject(); |
| |
| // If the <body> didn't have a custom style, then the root element might. |
| if (doc_element && doc_element->GetLayoutObject() && |
| doc_element->GetLayoutObject()->StyleRef().HasCustomScrollbarStyle()) |
| return *doc_element->GetLayoutObject(); |
| } else if (!layout_box.GetNode() && layout_box.Parent()) { |
| return *layout_box.Parent(); |
| } |
| |
| return layout_box; |
| } |
| |
| int PaintLayerScrollableArea::HypotheticalScrollbarThickness( |
| ScrollbarOrientation orientation, |
| bool should_include_overlay_thickness) const { |
| DCHECK(NeedsHypotheticalScrollbarThickness(orientation)); |
| // The cached values are updated after layout, use them if we're layout clean. |
| if (should_include_overlay_thickness && |
| GetLayoutBox()->GetDocument().Lifecycle().GetState() >= |
| DocumentLifecycle::kLayoutClean) { |
| return orientation == kHorizontalScrollbar |
| ? hypothetical_horizontal_scrollbar_thickness_ |
| : hypothetical_vertical_scrollbar_thickness_; |
| } |
| return ComputeHypotheticalScrollbarThickness( |
| orientation, should_include_overlay_thickness); |
| } |
| |
| // Hypothetical scrollbar thickness is computed and cached during layout, but |
| // only as needed to avoid a performance penalty. It is needed for every |
| // LayoutView, to support frame view auto-sizing; and it's needed whenever CSS |
| // scrollbar-gutter requires it. |
| bool PaintLayerScrollableArea::NeedsHypotheticalScrollbarThickness( |
| ScrollbarOrientation orientation) const { |
| return GetLayoutBox()->IsLayoutView() || |
| GetLayoutBox()->HasScrollbarGutters(orientation); |
| } |
| |
| int PaintLayerScrollableArea::ComputeHypotheticalScrollbarThickness( |
| ScrollbarOrientation orientation, |
| bool should_include_overlay_thickness) const { |
| Scrollbar* scrollbar = orientation == kHorizontalScrollbar |
| ? HorizontalScrollbar() |
| : VerticalScrollbar(); |
| if (scrollbar) |
| return scrollbar->ScrollbarThickness(); |
| |
| const LayoutObject& style_source = ScrollbarStyleSource(*GetLayoutBox()); |
| if (style_source.StyleRef().HasCustomScrollbarStyle()) { |
| return CustomScrollbar::HypotheticalScrollbarThickness( |
| this, orientation, To<Element>(style_source.GetNode())); |
| } |
| |
| ScrollbarTheme& theme = GetPageScrollbarTheme(); |
| if (theme.UsesOverlayScrollbars() && !should_include_overlay_thickness) |
| return 0; |
| return theme.ScrollbarThickness(ScaleFromDIP(), |
| style_source.StyleRef().ScrollbarWidth()); |
| } |
| |
| bool PaintLayerScrollableArea::NeedsScrollbarReconstruction() const { |
| if (!HasScrollbar()) |
| return false; |
| |
| const LayoutObject& style_source = ScrollbarStyleSource(*GetLayoutBox()); |
| bool needs_custom = |
| style_source.IsBox() && style_source.StyleRef().HasCustomScrollbarStyle(); |
| |
| Scrollbar* scrollbars[] = {HorizontalScrollbar(), VerticalScrollbar()}; |
| |
| for (Scrollbar* scrollbar : scrollbars) { |
| if (!scrollbar) |
| continue; |
| |
| // We have a native scrollbar that should be custom, or vice versa. |
| if (scrollbar->IsCustomScrollbar() != needs_custom) |
| return true; |
| |
| // We have a scrollbar with a stale style source. |
| if (scrollbar->StyleSource() && |
| scrollbar->StyleSource()->GetLayoutObject() != style_source) { |
| return true; |
| } |
| |
| if (needs_custom) { |
| // Should use custom scrollbar and nothing should change. |
| continue; |
| } |
| |
| // Check if native scrollbar should change. |
| Page* page = GetLayoutBox()->GetFrame()->LocalFrameRoot().GetPage(); |
| DCHECK(page); |
| ScrollbarTheme* current_theme = &page->GetScrollbarTheme(); |
| |
| if (current_theme != &scrollbar->GetTheme()) |
| return true; |
| } |
| return false; |
| } |
| |
| void PaintLayerScrollableArea::ComputeScrollbarExistence( |
| ComputeScrollbarExistenceReason reason, |
| bool& needs_horizontal_scrollbar, |
| bool& needs_vertical_scrollbar, |
| ComputeScrollbarExistenceOption option) const { |
| // Scrollbars may be hidden or provided by visual viewport or frame instead. |
| DCHECK(GetLayoutBox()->GetFrame()->GetSettings()); |
| if (VisualViewportSuppliesScrollbars() || |
| !CanHaveOverflowScrollbars(*GetLayoutBox()) || |
| GetLayoutBox()->GetFrame()->GetSettings()->GetHideScrollbars() || |
| GetLayoutBox()->IsFieldset() || GetLayoutBox()->IsFrameSet() || |
| GetLayoutBox()->StyleRef().ScrollbarWidth() == EScrollbarWidth::kNone) { |
| needs_horizontal_scrollbar = false; |
| needs_vertical_scrollbar = false; |
| TraceComputeScrollbarExistence( |
| reason, needs_horizontal_scrollbar, needs_vertical_scrollbar, option, |
| true /* early_exit */, mojom::blink::ScrollbarMode::kAuto, |
| mojom::blink::ScrollbarMode::kAuto); |
| return; |
| } |
| |
| mojom::blink::ScrollbarMode h_mode = mojom::blink::ScrollbarMode::kAuto; |
| mojom::blink::ScrollbarMode v_mode = mojom::blink::ScrollbarMode::kAuto; |
| |
| // First, determine what behavior the scrollbars say they should have. |
| { |
| if (auto* layout_view = DynamicTo<LayoutView>(GetLayoutBox())) { |
| // LayoutView is special as there's various quirks and settings that |
| // style doesn't account for. |
| layout_view->CalculateScrollbarModes(h_mode, v_mode); |
| } else { |
| auto overflow_x = GetLayoutBox()->StyleRef().OverflowX(); |
| if (overflow_x == EOverflow::kScroll) { |
| h_mode = mojom::blink::ScrollbarMode::kAlwaysOn; |
| } else if (overflow_x == EOverflow::kHidden || |
| overflow_x == EOverflow::kVisible) { |
| h_mode = mojom::blink::ScrollbarMode::kAlwaysOff; |
| } |
| |
| auto overflow_y = GetLayoutBox()->StyleRef().OverflowY(); |
| if (overflow_y == EOverflow::kScroll) { |
| v_mode = mojom::blink::ScrollbarMode::kAlwaysOn; |
| } else if (overflow_y == EOverflow::kHidden || |
| overflow_y == EOverflow::kVisible) { |
| v_mode = mojom::blink::ScrollbarMode::kAlwaysOff; |
| } |
| } |
| |
| // Since overlay scrollbars (the fade-in/out kind, not overflow: overlay) |
| // only appear when scrolling, we don't create them if there isn't overflow |
| // to scroll. Thus, overlay scrollbars can't be "always on". i.e. |
| // |overlay:scroll| behaves like |overlay:auto|. |
| bool has_custom_scrollbar_style = ScrollbarStyleSource(*GetLayoutBox()) |
| .StyleRef() |
| .HasCustomScrollbarStyle(); |
| bool will_be_overlay = GetPageScrollbarTheme().UsesOverlayScrollbars() && |
| !has_custom_scrollbar_style; |
| if (will_be_overlay) { |
| if (h_mode == mojom::blink::ScrollbarMode::kAlwaysOn) |
| h_mode = mojom::blink::ScrollbarMode::kAuto; |
| if (v_mode == mojom::blink::ScrollbarMode::kAlwaysOn) |
| v_mode = mojom::blink::ScrollbarMode::kAuto; |
| } |
| } |
| |
| // By default, don't make any changes. |
| needs_horizontal_scrollbar = HasHorizontalScrollbar(); |
| needs_vertical_scrollbar = HasVerticalScrollbar(); |
| |
| // If the behavior doesn't depend on overflow or any other information, we |
| // can set it now. |
| { |
| if (h_mode == mojom::blink::ScrollbarMode::kAlwaysOn) |
| needs_horizontal_scrollbar = true; |
| else if (h_mode == mojom::blink::ScrollbarMode::kAlwaysOff) |
| needs_horizontal_scrollbar = false; |
| |
| if (v_mode == mojom::blink::ScrollbarMode::kAlwaysOn) |
| needs_vertical_scrollbar = true; |
| else if (v_mode == mojom::blink::ScrollbarMode::kAlwaysOff) |
| needs_vertical_scrollbar = false; |
| } |
| |
| // If this is being performed before layout, we want to only update scrollbar |
| // existence if its based on purely style based reasons. |
| if (option == kOverflowIndependent) { |
| TraceComputeScrollbarExistence(reason, needs_horizontal_scrollbar, |
| needs_vertical_scrollbar, option, |
| false /* early_exit */, h_mode, v_mode); |
| return; |
| } |
| |
| // If we have clean layout, we can make a decision on any scrollbars that |
| // depend on overflow. |
| { |
| if (h_mode == mojom::blink::ScrollbarMode::kAuto) { |
| // Don't add auto scrollbars if the box contents aren't visible. |
| needs_horizontal_scrollbar = |
| GetLayoutBox()->IsRooted() && HasHorizontalOverflow() && |
| VisibleContentRect(kIncludeScrollbars).height(); |
| } |
| if (v_mode == mojom::blink::ScrollbarMode::kAuto) { |
| needs_vertical_scrollbar = GetLayoutBox()->IsRooted() && |
| HasVerticalOverflow() && |
| VisibleContentRect(kIncludeScrollbars).width(); |
| } |
| } |
| TraceComputeScrollbarExistence(reason, needs_horizontal_scrollbar, |
| needs_vertical_scrollbar, option, |
| false /* early_exit */, h_mode, v_mode); |
| } |
| |
| bool PaintLayerScrollableArea::TryRemovingAutoScrollbars( |
| const bool& needs_horizontal_scrollbar, |
| const bool& needs_vertical_scrollbar) { |
| if (!needs_horizontal_scrollbar && !needs_vertical_scrollbar) |
| return false; |
| |
| if (auto* layout_view = DynamicTo<LayoutView>(GetLayoutBox())) { |
| mojom::blink::ScrollbarMode h_mode; |
| mojom::blink::ScrollbarMode v_mode; |
| layout_view->CalculateScrollbarModes(h_mode, v_mode); |
| if (h_mode != mojom::blink::ScrollbarMode::kAuto || |
| v_mode != mojom::blink::ScrollbarMode::kAuto) |
| return false; |
| |
| gfx::Size visible_size_with_scrollbars = |
| VisibleContentRect(kIncludeScrollbars).size(); |
| if (ScrollWidth() <= visible_size_with_scrollbars.width() && |
| ScrollHeight() <= visible_size_with_scrollbars.height()) { |
| return true; |
| } |
| } else { |
| if (!GetLayoutBox()->HasAutoVerticalScrollbar() || |
| !GetLayoutBox()->HasAutoHorizontalScrollbar()) |
| return false; |
| |
| PhysicalSize client_size_with_scrollbars = |
| LayoutContentRect(kIncludeScrollbars).size; |
| if (ScrollWidth() <= client_size_with_scrollbars.width && |
| ScrollHeight() <= client_size_with_scrollbars.height) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void PaintLayerScrollableArea::RemoveScrollbarsForReconstruction() { |
| if (!HasHorizontalScrollbar() && !HasVerticalScrollbar()) |
| return; |
| if (HasHorizontalScrollbar()) { |
| SetScrollbarNeedsPaintInvalidation(kHorizontalScrollbar); |
| scrollbar_manager_.SetHasHorizontalScrollbar(false); |
| } |
| if (HasVerticalScrollbar()) { |
| SetScrollbarNeedsPaintInvalidation(kVerticalScrollbar); |
| scrollbar_manager_.SetHasVerticalScrollbar(false); |
| } |
| UpdateScrollCornerStyle(); |
| UpdateScrollOrigin(); |
| |
| // Force an update since we know the scrollbars have changed things. |
| if (GetLayoutBox()->GetDocument().HasAnnotatedRegions()) |
| GetLayoutBox()->GetDocument().SetAnnotatedRegionsDirty(true); |
| } |
| |
| CompositorElementId PaintLayerScrollableArea::GetScrollCornerElementId() const { |
| CompositorElementId scrollable_element_id = GetScrollElementId(); |
| DCHECK(scrollable_element_id); |
| return CompositorElementIdWithNamespace( |
| scrollable_element_id, CompositorElementIdNamespace::kScrollCorner); |
| } |
| |
| void PaintLayerScrollableArea::SetHasHorizontalScrollbar(bool has_scrollbar) { |
| if (IsHorizontalScrollbarFrozen()) |
| return; |
| |
| if (has_scrollbar == HasHorizontalScrollbar()) |
| return; |
| |
| // when scrollbar-width is "none", the scrollbar will not be displayed |
| if (GetLayoutBox()->StyleRef().ScrollbarWidth() == EScrollbarWidth::kNone) |
| return; |
| |
| SetScrollbarNeedsPaintInvalidation(kHorizontalScrollbar); |
| |
| scrollbar_manager_.SetHasHorizontalScrollbar(has_scrollbar); |
| |
| UpdateScrollOrigin(); |
| |
| // Destroying or creating one bar can cause our scrollbar corner to come and |
| // go. We need to update the opposite scrollbar's style. |
| if (HasHorizontalScrollbar()) |
| HorizontalScrollbar()->StyleChanged(); |
| if (HasVerticalScrollbar()) |
| VerticalScrollbar()->StyleChanged(); |
| |
| SetScrollCornerNeedsPaintInvalidation(); |
| |
| // Force an update since we know the scrollbars have changed things. |
| if (GetLayoutBox()->GetDocument().HasAnnotatedRegions()) |
| GetLayoutBox()->GetDocument().SetAnnotatedRegionsDirty(true); |
| } |
| |
| void PaintLayerScrollableArea::SetHasVerticalScrollbar(bool has_scrollbar) { |
| if (IsVerticalScrollbarFrozen()) |
| return; |
| |
| if (GetLayoutBox()->GetDocument().IsVerticalScrollEnforced()) { |
| // When the policy is enforced the contents of document cannot be scrolled. |
| // This would make rendering a scrollbar look strange |
| // (https://crbug.com/898151). |
| return; |
| } |
| |
| if (has_scrollbar == HasVerticalScrollbar()) |
| return; |
| |
| // when scrollbar-width is "none", the scrollbar will not be displayed |
| if (GetLayoutBox()->StyleRef().ScrollbarWidth() == EScrollbarWidth::kNone) |
| return; |
| |
| SetScrollbarNeedsPaintInvalidation(kVerticalScrollbar); |
| |
| scrollbar_manager_.SetHasVerticalScrollbar(has_scrollbar); |
| |
| UpdateScrollOrigin(); |
| |
| // Destroying or creating one bar can cause our scrollbar corner to come and |
| // go. We need to update the opposite scrollbar's style. |
| if (HasHorizontalScrollbar()) |
| HorizontalScrollbar()->StyleChanged(); |
| if (HasVerticalScrollbar()) |
| VerticalScrollbar()->StyleChanged(); |
| |
| SetScrollCornerNeedsPaintInvalidation(); |
| |
| // Force an update since we know the scrollbars have changed things. |
| if (GetLayoutBox()->GetDocument().HasAnnotatedRegions()) |
| GetLayoutBox()->GetDocument().SetAnnotatedRegionsDirty(true); |
| } |
| |
| int PaintLayerScrollableArea::VerticalScrollbarWidth( |
| OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const { |
| if (!HasVerticalScrollbar()) |
| return 0; |
| if (overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize && |
| GetLayoutBox()->StyleRef().OverflowY() == EOverflow::kOverlay) { |
| return 0; |
| } |
| if ((overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize || |
| !VerticalScrollbar()->ShouldParticipateInHitTesting()) && |
| VerticalScrollbar()->IsOverlayScrollbar()) { |
| return 0; |
| } |
| return VerticalScrollbar()->ScrollbarThickness(); |
| } |
| |
| int PaintLayerScrollableArea::HorizontalScrollbarHeight( |
| OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const { |
| if (!HasHorizontalScrollbar()) |
| return 0; |
| if (overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize && |
| GetLayoutBox()->StyleRef().OverflowX() == EOverflow::kOverlay) { |
| return 0; |
| } |
| if ((overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize || |
| !HorizontalScrollbar()->ShouldParticipateInHitTesting()) && |
| HorizontalScrollbar()->IsOverlayScrollbar()) { |
| return 0; |
| } |
| return HorizontalScrollbar()->ScrollbarThickness(); |
| } |
| |
| const cc::SnapContainerData* PaintLayerScrollableArea::GetSnapContainerData() |
| const { |
| return RareData() && RareData()->snap_container_data_ |
| ? &RareData()->snap_container_data_.value() |
| : nullptr; |
| } |
| |
| void PaintLayerScrollableArea::SetSnapContainerData( |
| absl::optional<cc::SnapContainerData> data) { |
| EnsureRareData().snap_container_data_ = data; |
| } |
| |
| bool PaintLayerScrollableArea::SetTargetSnapAreaElementIds( |
| cc::TargetSnapAreaElementIds snap_target_ids) { |
| if (!RareData() || !RareData()->snap_container_data_) |
| return false; |
| if (RareData()->snap_container_data_.value().SetTargetSnapAreaElementIds( |
| snap_target_ids)) { |
| GetLayoutBox()->SetNeedsPaintPropertyUpdate(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool PaintLayerScrollableArea::SnapContainerDataNeedsUpdate() const { |
| return RareData() ? RareData()->snap_container_data_needs_update_ : false; |
| } |
| |
| void PaintLayerScrollableArea::SetSnapContainerDataNeedsUpdate( |
| bool needs_update) { |
| EnsureRareData().snap_container_data_needs_update_ = needs_update; |
| if (!needs_update) |
| return; |
| GetLayoutBox() |
| ->GetDocument() |
| .GetSnapCoordinator() |
| .SetAnySnapContainerDataNeedsUpdate(true); |
| } |
| |
| bool PaintLayerScrollableArea::NeedsResnap() const { |
| return RareData() ? RareData()->needs_resnap_ : false; |
| } |
| |
| void PaintLayerScrollableArea::SetNeedsResnap(bool needs_resnap) { |
| EnsureRareData().needs_resnap_ = needs_resnap; |
| } |
| |
| absl::optional<gfx::PointF> |
| PaintLayerScrollableArea::GetSnapPositionAndSetTarget( |
| const cc::SnapSelectionStrategy& strategy) { |
| if (!RareData() || !RareData()->snap_container_data_) |
| return absl::nullopt; |
| |
| cc::SnapContainerData& data = RareData()->snap_container_data_.value(); |
| if (!data.size()) |
| return absl::nullopt; |
| |
| // If the document has a focused element that is coincident with the snap |
| // target, update the snap target to point to the focused element. This |
| // ensures that we stay snapped to the focused element after a relayout. |
| // TODO(crbug.com/1199911): If the focused element is not a snap target but |
| // has an ancestor that is, perhaps the rule should be applied for the |
| // ancestor element. |
| CompositorElementId active_element_id = CompositorElementId(); |
| if (auto* active_element = GetDocument()->ActiveElement()) { |
| active_element_id = |
| CompositorElementIdFromDOMNodeId(DOMNodeIds::IdForNode(active_element)); |
| } |
| |
| cc::TargetSnapAreaElementIds snap_targets; |
| gfx::PointF snap_position; |
| absl::optional<gfx::PointF> snap_point; |
| if (data.FindSnapPosition(strategy, &snap_position, &snap_targets, |
| active_element_id)) { |
| snap_point = gfx::PointF(snap_position.x(), snap_position.y()); |
| } |
| |
| if (data.SetTargetSnapAreaElementIds(snap_targets)) |
| GetLayoutBox()->SetNeedsPaintPropertyUpdate(); |
| |
| return snap_point; |
| } |
| |
| bool PaintLayerScrollableArea::HasOverflowControls() const { |
| // We do not need to check for ScrollCorner because it only exists iff there |
| // are scrollbars, see: |ScrollCornerRect| and |UpdateScrollCornerStyle|. |
| DCHECK(!ScrollCorner() || HasScrollbar()); |
| return HasScrollbar() || GetLayoutBox()->CanResize(); |
| } |
| |
| bool PaintLayerScrollableArea::HasOverlayOverflowControls() const { |
| if (HasOverlayScrollbars()) |
| return true; |
| if (!HasScrollbar() && GetLayoutBox()->CanResize()) |
| return true; |
| if (GetLayoutBox()->StyleRef().OverflowX() == EOverflow::kOverlay || |
| GetLayoutBox()->StyleRef().OverflowY() == EOverflow::kOverlay) |
| return true; |
| return false; |
| } |
| |
| bool PaintLayerScrollableArea::NeedsScrollCorner() const { |
| // This is one of the differences between platform overlay scrollbars and |
| // overflow:overlay scrollbars: the former don't need scroll corner, while |
| // the latter do. HasOverlayScrollbars doesn't include overflow:overlay. |
| return HasScrollbar() && !HasOverlayScrollbars(); |
| } |
| |
| bool PaintLayerScrollableArea::ShouldOverflowControlsPaintAsOverlay() const { |
| if (HasOverlayOverflowControls()) |
| return true; |
| |
| // Frame and global root scroller (which can be a non-frame) scrollbars and |
| // corner also paint as overlay so that they appear on top of all content |
| // within their viewport. This is important for global root scrollers since |
| // these scrollbars' transform state is |
| // VisualViewport::TransformNodeForViewportScrollbars(). |
| return layer_->IsRootLayer() || |
| (GetLayoutBox() && GetLayoutBox()->IsGlobalRootScroller()); |
| } |
| |
| void PaintLayerScrollableArea::PositionOverflowControls() { |
| if (!HasOverflowControls()) |
| return; |
| |
| if (Scrollbar* vertical_scrollbar = VerticalScrollbar()) { |
| vertical_scrollbar->SetFrameRect(RectForVerticalScrollbar()); |
| if (auto* custom_scrollbar = DynamicTo<CustomScrollbar>(vertical_scrollbar)) |
| custom_scrollbar->PositionScrollbarParts(); |
| } |
| |
| if (Scrollbar* horizontal_scrollbar = HorizontalScrollbar()) { |
| horizontal_scrollbar->SetFrameRect(RectForHorizontalScrollbar()); |
| if (auto* custom_scrollbar = |
| DynamicTo<CustomScrollbar>(horizontal_scrollbar)) |
| custom_scrollbar->PositionScrollbarParts(); |
| } |
| |
| if (scroll_corner_) { |
| LayoutRect rect(ScrollCornerRect()); |
| scroll_corner_->SetOverriddenFrameRect(rect); |
| // TODO(crbug.com/1020913): This should be part of PaintPropertyTreeBuilder |
| // when we support subpixel layout of overflow controls. |
| scroll_corner_->GetMutableForPainting().FirstFragment().SetPaintOffset( |
| PhysicalOffset(rect.Location())); |
| } |
| |
| if (resizer_) { |
| LayoutRect rect(ResizerCornerRect(kResizerForPointer)); |
| resizer_->SetOverriddenFrameRect(rect); |
| // TODO(crbug.com/1020913): This should be part of PaintPropertyTreeBuilder |
| // when we support subpixel layout of overflow controls. |
| resizer_->GetMutableForPainting().FirstFragment().SetPaintOffset( |
| PhysicalOffset(rect.Location())); |
| } |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollCornerStyle() { |
| if (!NeedsScrollCorner()) { |
| if (scroll_corner_) { |
| scroll_corner_->Destroy(); |
| scroll_corner_ = nullptr; |
| } |
| return; |
| } |
| const LayoutObject& style_source = ScrollbarStyleSource(*GetLayoutBox()); |
| scoped_refptr<const ComputedStyle> corner = |
| GetLayoutBox()->IsScrollContainer() |
| ? style_source.GetUncachedPseudoElementStyle( |
| StyleRequest(kPseudoIdScrollbarCorner, style_source.Style())) |
| : scoped_refptr<ComputedStyle>(nullptr); |
| if (corner) { |
| if (!scroll_corner_) { |
| scroll_corner_ = LayoutCustomScrollbarPart::CreateAnonymous( |
| &GetLayoutBox()->GetDocument(), this); |
| } |
| scroll_corner_->SetStyle(std::move(corner)); |
| } else if (scroll_corner_) { |
| scroll_corner_->Destroy(); |
| scroll_corner_ = nullptr; |
| } |
| } |
| |
| bool PaintLayerScrollableArea::HitTestOverflowControls( |
| HitTestResult& result, |
| const gfx::Point& local_point) { |
| if (!HasOverflowControls()) |
| return false; |
| |
| gfx::Rect resize_control_rect; |
| if (GetLayoutBox()->CanResize()) { |
| resize_control_rect = ResizerCornerRect(kResizerForPointer); |
| if (resize_control_rect.Contains(local_point)) { |
| result.SetIsOverResizer(true); |
| return true; |
| } |
| } |
| int resize_control_size = max(resize_control_rect.height(), 0); |
| |
| gfx::Rect visible_rect = VisibleContentRect(kIncludeScrollbars); |
| |
| if (HasVerticalScrollbar() && |
| VerticalScrollbar()->ShouldParticipateInHitTesting()) { |
| LayoutRect v_bar_rect(VerticalScrollbarStart(), |
| GetLayoutBox()->BorderTop().ToInt(), |
| VerticalScrollbar()->ScrollbarThickness(), |
| visible_rect.height() - |
| (HasHorizontalScrollbar() |
| ? HorizontalScrollbar()->ScrollbarThickness() |
| : resize_control_size)); |
| if (v_bar_rect.Contains(LayoutPoint(local_point))) { |
| result.SetScrollbar(VerticalScrollbar()); |
| return true; |
| } |
| } |
| |
| resize_control_size = max(resize_control_rect.width(), 0); |
| if (HasHorizontalScrollbar() && |
| HorizontalScrollbar()->ShouldParticipateInHitTesting()) { |
| // TODO(crbug.com/638981): Are the conversions to int intentional? |
| int h_scrollbar_thickness = HorizontalScrollbar()->ScrollbarThickness(); |
| LayoutRect h_bar_rect( |
| HorizontalScrollbarStart(), |
| GetLayoutBox()->BorderTop().ToInt() + visible_rect.height() - |
| h_scrollbar_thickness, |
| visible_rect.width() - (HasVerticalScrollbar() |
| ? VerticalScrollbar()->ScrollbarThickness() |
| : resize_control_size), |
| h_scrollbar_thickness); |
| if (h_bar_rect.Contains(LayoutPoint(local_point))) { |
| result.SetScrollbar(HorizontalScrollbar()); |
| return true; |
| } |
| } |
| |
| // FIXME: We should hit test the m_scrollCorner and pass it back through the |
| // result. |
| |
| return false; |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::ResizerCornerRect( |
| ResizerHitTestType resizer_hit_test_type) const { |
| if (!GetLayoutBox()->CanResize()) |
| return gfx::Rect(); |
| gfx::Rect corner = CornerRect(); |
| |
| if (resizer_hit_test_type == kResizerForTouch) { |
| // We make the resizer virtually larger for touch hit testing. With the |
| // expanding ratio k = ResizerControlExpandRatioForTouch, we first move |
| // the resizer rect (of width w & height h), by (-w * (k-1), -h * (k-1)), |
| // then expand the rect by new_w/h = w/h * k. |
| corner.Offset(-corner.width() * (kResizerControlExpandRatioForTouch - 1), |
| -corner.height() * (kResizerControlExpandRatioForTouch - 1)); |
| corner.set_size( |
| gfx::Size(corner.width() * kResizerControlExpandRatioForTouch, |
| corner.height() * kResizerControlExpandRatioForTouch)); |
| } |
| |
| return corner; |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::ScrollCornerAndResizerRect() const { |
| gfx::Rect scroll_corner_and_resizer = ScrollCornerRect(); |
| if (scroll_corner_and_resizer.IsEmpty()) |
| return ResizerCornerRect(kResizerForPointer); |
| return scroll_corner_and_resizer; |
| } |
| |
| bool PaintLayerScrollableArea::IsAbsolutePointInResizeControl( |
| const gfx::Point& absolute_point, |
| ResizerHitTestType resizer_hit_test_type) const { |
| if (GetLayoutBox()->StyleRef().Visibility() != EVisibility::kVisible || |
| !GetLayoutBox()->CanResize()) |
| return false; |
| |
| gfx::Point local_point = ToRoundedPoint( |
| GetLayoutBox()->AbsoluteToLocalPoint(PhysicalOffset(absolute_point))); |
| return ResizerCornerRect(resizer_hit_test_type).Contains(local_point); |
| } |
| |
| bool PaintLayerScrollableArea::IsLocalPointInResizeControl( |
| const gfx::Point& local_point, |
| ResizerHitTestType resizer_hit_test_type) const { |
| if (GetLayoutBox()->StyleRef().Visibility() != EVisibility::kVisible || |
| !GetLayoutBox()->CanResize()) |
| return false; |
| |
| return ResizerCornerRect(resizer_hit_test_type).Contains(local_point); |
| } |
| |
| void PaintLayerScrollableArea::UpdateResizerStyle( |
| const ComputedStyle* old_style) { |
| // Change of resizer status affects HasOverlayOverflowControls(). Invalid |
| // z-order lists to refresh overflow control painting order. |
| bool had_resizer = old_style && old_style->HasResize(); |
| bool needs_resizer = GetLayoutBox()->CanResize(); |
| if (had_resizer != needs_resizer) |
| layer_->DirtyStackingContextZOrderLists(); |
| |
| if (!resizer_ && !needs_resizer) |
| return; |
| |
| // Update custom resizer style. |
| const LayoutObject& style_source = ScrollbarStyleSource(*GetLayoutBox()); |
| scoped_refptr<const ComputedStyle> resizer = |
| GetLayoutBox()->IsScrollContainer() |
| ? style_source.GetUncachedPseudoElementStyle( |
| StyleRequest(kPseudoIdResizer, style_source.Style())) |
| : scoped_refptr<ComputedStyle>(nullptr); |
| if (resizer) { |
| if (!resizer_) { |
| resizer_ = LayoutCustomScrollbarPart::CreateAnonymous( |
| &GetLayoutBox()->GetDocument(), this); |
| } |
| resizer_->SetStyle(std::move(resizer)); |
| } else if (resizer_) { |
| resizer_->Destroy(); |
| resizer_ = nullptr; |
| } |
| } |
| |
| void PaintLayerScrollableArea::AddStickyLayer(PaintLayer* layer) { |
| UseCounter::Count(GetLayoutBox()->GetDocument(), WebFeature::kPositionSticky); |
| EnsureRareData().sticky_layers_.insert(layer); |
| } |
| |
| void PaintLayerScrollableArea::InvalidateAllStickyConstraints() { |
| // Don't clear StickyConstraints for each LayoutObject of each layer in |
| // sticky_layers_ because sticky_layers_ may contain stale pointers. |
| // LayoutBoxModelObject::UpdateStickyPositionConstraints() will check both |
| // HasStickyLayer() of its containing scrollable area and its |
| // StickyConstraints() to see if its sticky constraints need update. |
| if (rare_data_) |
| rare_data_->sticky_layers_.clear(); |
| } |
| |
| void PaintLayerScrollableArea::InvalidatePaintForStickyDescendants() { |
| // If this is called during layout, sticky_layers_ may contain stale pointers. |
| // Return because we'll InvalidateAllStickyConstraints(), and we'll |
| // SetNeedsPaintPropertyUpdate() when updating sticky constraints. |
| if (GetLayoutBox()->NeedsLayout()) |
| return; |
| |
| if (PaintLayerScrollableAreaRareData* d = RareData()) { |
| for (PaintLayer* sticky_layer : d->sticky_layers_) { |
| auto& object = sticky_layer->GetLayoutObject(); |
| object.SetNeedsPaintPropertyUpdate(); |
| DCHECK(object.StickyConstraints()); |
| object.StickyConstraints()->ComputeStickyOffset(ScrollPosition()); |
| } |
| } |
| } |
| |
| gfx::Vector2d PaintLayerScrollableArea::OffsetFromResizeCorner( |
| const gfx::Point& absolute_point) const { |
| // Currently the resize corner is either the bottom right corner or the bottom |
| // left corner. |
| // FIXME: This assumes the location is 0, 0. Is this guaranteed to always be |
| // the case? |
| gfx::Size element_size = PixelSnappedBorderBoxSize(); |
| if (GetLayoutBox()->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| element_size.set_width(0); |
| gfx::Point local_point = ToRoundedPoint( |
| GetLayoutBox()->AbsoluteToLocalPoint(PhysicalOffset(absolute_point))); |
| return gfx::Vector2d(local_point.x() - element_size.width(), |
| local_point.y() - element_size.height()); |
| } |
| |
| void PaintLayerScrollableArea::Resize(const gfx::Point& pos, |
| const LayoutSize& old_offset) { |
| // FIXME: This should be possible on generated content but is not right now. |
| if (!InResizeMode() || !GetLayoutBox()->CanResize() || |
| !GetLayoutBox()->GetNode()) |
| return; |
| |
| DCHECK(GetLayoutBox()->GetNode()->IsElementNode()); |
| auto* element = To<Element>(GetLayoutBox()->GetNode()); |
| |
| Document& document = element->GetDocument(); |
| |
| float zoom_factor = GetLayoutBox()->StyleRef().EffectiveZoom(); |
| |
| gfx::Vector2d new_offset = |
| OffsetFromResizeCorner(document.View()->ConvertFromRootFrame(pos)); |
| new_offset.set_x(new_offset.x() / zoom_factor); |
| new_offset.set_y(new_offset.y() / zoom_factor); |
| |
| LayoutSize current_size = GetLayoutBox()->Size(); |
| current_size.Scale(1 / zoom_factor); |
| |
| LayoutSize adjusted_old_offset = old_offset * (1.f / zoom_factor); |
| if (GetLayoutBox()->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { |
| new_offset.set_x(-new_offset.x()); |
| adjusted_old_offset.SetWidth(-adjusted_old_offset.Width()); |
| } |
| |
| LayoutSize new_size(current_size + LayoutSize(new_offset) - |
| adjusted_old_offset); |
| |
| // Ensure the new size is at least as large as the resize corner. |
| gfx::SizeF corner_rect(CornerRect().size()); |
| corner_rect.InvScale(zoom_factor); |
| new_size.ClampToMinimumSize(LayoutSize(corner_rect)); |
| |
| LayoutSize difference(new_size - current_size); |
| |
| bool is_box_sizing_border = |
| GetLayoutBox()->StyleRef().BoxSizing() == EBoxSizing::kBorderBox; |
| |
| EResize resize = GetLayoutBox()->StyleRef().Resize( |
| GetLayoutBox()->ContainingBlock()->StyleRef()); |
| if (resize != EResize::kVertical && difference.Width()) { |
| LayoutUnit base_width = |
| GetLayoutBox()->Size().Width() - |
| (is_box_sizing_border ? LayoutUnit() |
| : GetLayoutBox()->BorderAndPaddingWidth()); |
| base_width = LayoutUnit(base_width / zoom_factor); |
| element->SetInlineStyleProperty(CSSPropertyID::kWidth, |
| RoundToInt(base_width + difference.Width()), |
| CSSPrimitiveValue::UnitType::kPixels); |
| } |
| |
| if (resize != EResize::kHorizontal && difference.Height()) { |
| LayoutUnit base_height = |
| GetLayoutBox()->Size().Height() - |
| (is_box_sizing_border ? LayoutUnit() |
| : GetLayoutBox()->BorderAndPaddingHeight()); |
| base_height = LayoutUnit(base_height / zoom_factor); |
| element->SetInlineStyleProperty( |
| CSSPropertyID::kHeight, RoundToInt(base_height + difference.Height()), |
| CSSPrimitiveValue::UnitType::kPixels); |
| } |
| |
| document.UpdateStyleAndLayout(DocumentUpdateReason::kSizeChange); |
| |
| // FIXME: We should also autoscroll the window as necessary to |
| // keep the point under the cursor in view. |
| } |
| |
| PhysicalRect PaintLayerScrollableArea::ScrollIntoView( |
| const PhysicalRect& absolute_rect, |
| const mojom::blink::ScrollIntoViewParamsPtr& params) { |
| // Ignore sticky position offsets for the purposes of scrolling elements into |
| // view. See https://www.w3.org/TR/css-position-3/#stickypos-scroll for |
| // details |
| const MapCoordinatesFlags flag = |
| (RuntimeEnabledFeatures::CSSPositionStickyStaticScrollPositionEnabled()) |
| ? kIgnoreStickyOffset |
| : 0; |
| |
| PhysicalRect local_expose_rect = |
| GetLayoutBox()->AbsoluteToLocalRect(absolute_rect, flag); |
| PhysicalOffset border_origin_to_scroll_origin(-GetLayoutBox()->BorderLeft(), |
| -GetLayoutBox()->BorderTop()); |
| // There might be scroll bar between border_origin and scroll_origin. |
| gfx::Vector2d scroll_bar_adjustment = |
| GetLayoutBox()->OriginAdjustmentForScrollbars(); |
| border_origin_to_scroll_origin.left -= scroll_bar_adjustment.x(); |
| border_origin_to_scroll_origin.top -= scroll_bar_adjustment.y(); |
| border_origin_to_scroll_origin += |
| PhysicalOffset::FromVector2dFFloor(GetScrollOffset()); |
| // Represent the rect in the container's scroll-origin coordinate. |
| local_expose_rect.Move(border_origin_to_scroll_origin); |
| PhysicalRect scroll_snapport_rect = VisibleScrollSnapportRect(); |
| |
| ScrollOffset target_offset = ScrollAlignment::GetScrollOffsetToExpose( |
| scroll_snapport_rect, local_expose_rect, *params->align_x.get(), |
| *params->align_y.get(), GetScrollOffset()); |
| ScrollOffset new_scroll_offset( |
| ClampScrollOffset(gfx::ToRoundedVector2d(target_offset))); |
| |
| ScrollOffset old_scroll_offset = GetScrollOffset(); |
| if (params->type == mojom::blink::ScrollType::kUser) { |
| if (!UserInputScrollable(kHorizontalScrollbar)) |
| new_scroll_offset.set_x(old_scroll_offset.x()); |
| if (!UserInputScrollable(kVerticalScrollbar)) |
| new_scroll_offset.set_y(old_scroll_offset.y()); |
| } |
| |
| gfx::PointF end_point = ScrollOffsetToPosition(new_scroll_offset); |
| std::unique_ptr<cc::SnapSelectionStrategy> strategy = |
| cc::SnapSelectionStrategy::CreateForEndPosition(end_point, true, true); |
| end_point = GetSnapPositionAndSetTarget(*strategy).value_or(end_point); |
| new_scroll_offset = ScrollPositionToOffset(end_point); |
| |
| if (params->is_for_scroll_sequence) { |
| DCHECK(params->type == mojom::blink::ScrollType::kProgrammatic || |
| params->type == mojom::blink::ScrollType::kUser); |
| mojom::blink::ScrollBehavior behavior = DetermineScrollBehavior( |
| params->behavior, GetLayoutBox()->StyleRef().GetScrollBehavior()); |
| GetSmoothScrollSequencer()->QueueAnimation(this, new_scroll_offset, |
| behavior); |
| } else { |
| SetScrollOffset(new_scroll_offset, params->type, |
| mojom::blink::ScrollBehavior::kInstant); |
| } |
| |
| ScrollOffset scroll_offset_difference = new_scroll_offset - old_scroll_offset; |
| // The container hasn't performed the scroll yet if it's for scroll sequence. |
| // To calculate the result from the scroll, we move the |local_expose_rect| to |
| // the will-be-scrolled location. |
| local_expose_rect.Move( |
| -PhysicalOffset::FromVector2dFRound(scroll_offset_difference)); |
| |
| // Represent the rects in the container's border-box coordinate. |
| local_expose_rect.Move(-border_origin_to_scroll_origin); |
| scroll_snapport_rect.Move(-border_origin_to_scroll_origin); |
| PhysicalRect intersect = |
| Intersection(scroll_snapport_rect, local_expose_rect); |
| |
| if (intersect.IsEmpty() && !scroll_snapport_rect.IsEmpty() && |
| !local_expose_rect.IsEmpty()) { |
| return GetLayoutBox()->LocalToAbsoluteRect(local_expose_rect, flag); |
| } |
| intersect = GetLayoutBox()->LocalToAbsoluteRect(intersect, flag); |
| |
| return intersect; |
| } |
| |
| void PaintLayerScrollableArea::UpdateScrollableAreaSet() { |
| LocalFrame* frame = GetLayoutBox()->GetFrame(); |
| if (!frame) |
| return; |
| |
| LocalFrameView* frame_view = frame->View(); |
| if (!frame_view) |
| return; |
| |
| const bool has_horizontal_overflow = HasHorizontalOverflow(); |
| const bool has_vertical_overflow = HasVerticalOverflow(); |
| bool has_overflow = |
| !GetLayoutBox()->Size().IsZero() && |
| ((has_horizontal_overflow && GetLayoutBox()->ScrollsOverflowX()) || |
| (has_vertical_overflow && GetLayoutBox()->ScrollsOverflowY())); |
| |
| bool overflows_in_block_direction = GetLayoutBox()->IsHorizontalWritingMode() |
| ? has_vertical_overflow |
| : has_horizontal_overflow; |
| |
| if (overflows_in_block_direction) { |
| DCHECK(CanHaveOverflowScrollbars(*GetLayoutBox())); |
| frame_view->AddScrollAnchoringScrollableArea(this); |
| } else { |
| frame_view->RemoveScrollAnchoringScrollableArea(this); |
| } |
| |
| bool is_visible = |
| GetLayoutBox()->StyleRef().Visibility() == EVisibility::kVisible; |
| bool did_scroll_overflow = scrolls_overflow_; |
| if (auto* layout_view = DynamicTo<LayoutView>(GetLayoutBox())) { |
| mojom::blink::ScrollbarMode h_mode; |
| mojom::blink::ScrollbarMode v_mode; |
| layout_view->CalculateScrollbarModes(h_mode, v_mode); |
| if (h_mode == mojom::blink::ScrollbarMode::kAlwaysOff && |
| v_mode == mojom::blink::ScrollbarMode::kAlwaysOff) |
| has_overflow = false; |
| } |
| |
| scrolls_overflow_ = has_overflow && is_visible; |
| if (did_scroll_overflow == ScrollsOverflow()) |
| return; |
| |
| // Change of scrolls_overflow may affect whether we create ScrollTranslation |
| // which is referenced from ScrollDisplayItem. Invalidate scrollbars (but not |
| // their parts) to repaint the display item. |
| if (auto* scrollbar = HorizontalScrollbar()) |
| scrollbar->SetNeedsPaintInvalidation(kNoPart); |
| if (auto* scrollbar = VerticalScrollbar()) |
| scrollbar->SetNeedsPaintInvalidation(kNoPart); |
| |
| if (RuntimeEnabledFeatures::ImplicitRootScrollerEnabled() && |
| scrolls_overflow_) { |
| if (IsA<LayoutView>(GetLayoutBox())) { |
| if (Element* owner = GetLayoutBox()->GetDocument().LocalOwner()) { |
| owner->GetDocument().GetRootScrollerController().ConsiderForImplicit( |
| *owner); |
| } |
| } else { |
| // In some cases, the LayoutBox may not be associated with a Node (e.g. |
| // <input> and <fieldset> can generate anonymous LayoutBoxes for their |
| // scrollers). We don't care about those cases for root scroller so |
| // simply avoid these. https://crbug.com/1125621. |
| if (GetLayoutBox()->GetNode()) { |
| GetLayoutBox() |
| ->GetDocument() |
| .GetRootScrollerController() |
| .ConsiderForImplicit(*GetLayoutBox()->GetNode()); |
| } |
| } |
| } |
| |
| // The scroll and scroll offset properties depend on |scrollsOverflow| (see: |
| // PaintPropertyTreeBuilder::updateScrollAndScrollTranslation). |
| GetLayoutBox()->SetNeedsPaintPropertyUpdate(); |
| |
| // Scroll hit test data depend on whether the box scrolls overflow. |
| // They are painted in the background phase |
| // (see: BoxPainter::PaintBoxDecorationBackground). |
| GetLayoutBox()->SetBackgroundNeedsFullPaintInvalidation(); |
| |
| if (scrolls_overflow_) { |
| DCHECK(CanHaveOverflowScrollbars(*GetLayoutBox())); |
| frame_view->AddUserScrollableArea(this); |
| } else { |
| frame_view->RemoveUserScrollableArea(this); |
| } |
| |
| layer_->DidUpdateScrollsOverflow(); |
| } |
| |
| ScrollingCoordinator* PaintLayerScrollableArea::GetScrollingCoordinator() |
| const { |
| LocalFrame* frame = GetLayoutBox()->GetFrame(); |
| if (!frame) |
| return nullptr; |
| |
| Page* page = frame->GetPage(); |
| if (!page) |
| return nullptr; |
| |
| return page->GetScrollingCoordinator(); |
| } |
| |
| bool PaintLayerScrollableArea::ShouldScrollOnMainThread() const { |
| DCHECK_GE(GetDocument()->Lifecycle().GetState(), |
| RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled() |
| ? DocumentLifecycle::kPaintClean |
| : DocumentLifecycle::kInPrePaint); |
| return HasBeenDisposed() || should_scroll_on_main_thread_; |
| } |
| |
| void PaintLayerScrollableArea::SetShouldScrollOnMainThread( |
| bool scroll_on_main_thread) { |
| DCHECK_EQ(GetDocument()->Lifecycle().GetState(), |
| RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled() |
| ? DocumentLifecycle::kPaintClean |
| : DocumentLifecycle::kInPrePaint); |
| if (scroll_on_main_thread != should_scroll_on_main_thread_) { |
| should_scroll_on_main_thread_ = scroll_on_main_thread; |
| MainThreadScrollingDidChange(); |
| } |
| } |
| |
| bool PaintLayerScrollableArea::PrefersNonCompositedScrolling() const { |
| if (RuntimeEnabledFeatures::PreferNonCompositedScrollingEnabled()) { |
| return true; |
| } |
| if (Node* node = GetLayoutBox()->GetNode()) { |
| if (IsA<HTMLSelectElement>(node)) { |
| return true; |
| } |
| if (TextControlElement* text_control = EnclosingTextControl(node)) { |
| if (IsA<HTMLInputElement>(text_control)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool PaintLayerScrollableArea::ComputeNeedsCompositedScrolling( |
| bool force_prefer_compositing_to_lcd_text) { |
| const auto* box = GetLayoutBox(); |
| auto new_background_paint_location = |
| box->ComputeBackgroundPaintLocationIfComposited(); |
| bool needs_composited_scrolling = false; |
| if (!RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled()) { |
| needs_composited_scrolling = ComputeNeedsCompositedScrollingInternal( |
| new_background_paint_location, force_prefer_compositing_to_lcd_text); |
| if (!needs_composited_scrolling) { |
| new_background_paint_location = kBackgroundPaintInBorderBoxSpace; |
| } |
| DCHECK(!(non_composited_main_thread_scrolling_reasons_ & |
| ~cc::MainThreadScrollingReason::kNonCompositedReasons)); |
| } |
| box->GetMutableForPainting().SetBackgroundPaintLocation( |
| new_background_paint_location); |
| |
| return needs_composited_scrolling; |
| } |
| |
| bool PaintLayerScrollableArea::ComputeNeedsCompositedScrollingInternal( |
| BackgroundPaintLocation background_paint_location_if_composited, |
| bool force_prefer_compositing_to_lcd_text) { |
| DCHECK(!RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled()); |
| DCHECK_EQ(background_paint_location_if_composited, |
| GetLayoutBox()->ComputeBackgroundPaintLocationIfComposited()); |
| |
| non_composited_main_thread_scrolling_reasons_ = 0; |
| |
| const auto* box = GetLayoutBox(); |
| if (CompositingReasonFinder::RequiresCompositingForRootScroller(*box)) { |
| return true; |
| } |
| if (!ScrollsOverflow()) { |
| return false; |
| } |
| if (force_prefer_compositing_to_lcd_text) { |
| return true; |
| } |
| if (PrefersNonCompositedScrolling()) { |
| non_composited_main_thread_scrolling_reasons_ = |
| cc::MainThreadScrollingReason::kPreferNonCompositedScrolling; |
| return false; |
| } |
| |
| bool needs_composited_scrolling = true; |
| if (box->GetDocument().GetSettings()->GetLCDTextPreference() == |
| LCDTextPreference::kStronglyPreferred) { |
| if (!box->TextIsKnownToBeOnOpaqueBackground()) { |
| non_composited_main_thread_scrolling_reasons_ |= |
| cc::MainThreadScrollingReason::kNotOpaqueForTextAndLCDText; |
| needs_composited_scrolling = false; |
| } |
| if (!(background_paint_location_if_composited & |
| kBackgroundPaintInContentsSpace) && |
| box->StyleRef().HasBackground()) { |
| non_composited_main_thread_scrolling_reasons_ |= cc:: |
| MainThreadScrollingReason::kCantPaintScrollingBackgroundAndLCDText; |
| needs_composited_scrolling = false; |
| } |
| } |
| |
| return needs_composited_scrolling; |
| } |
| |
| bool PaintLayerScrollableArea::UsesCompositedScrolling() const { |
| return GetLayoutBox()->UsesCompositedScrolling(); |
| } |
| |
| void PaintLayerScrollableArea::UpdateNeedsCompositedScrolling( |
| bool force_prefer_compositing_to_lcd_text) { |
| DCHECK_EQ(DocumentLifecycle::kInPrePaint, |
| GetDocument()->Lifecycle().GetState()); |
| |
| bool new_needs_composited_scrolling = |
| ComputeNeedsCompositedScrolling(force_prefer_compositing_to_lcd_text); |
| if (new_needs_composited_scrolling == needs_composited_scrolling_) |
| return; |
| |
| needs_composited_scrolling_ = new_needs_composited_scrolling; |
| GetLayoutBox()->SetShouldCheckForPaintInvalidation(); |
| } |
| |
| bool PaintLayerScrollableArea::VisualViewportSuppliesScrollbars() const { |
| LocalFrame* frame = GetLayoutBox()->GetFrame(); |
| if (!frame || !frame->GetSettings()) |
| return false; |
| |
| // On desktop, we always use the layout viewport's scrollbars. |
| if (!frame->GetSettings()->GetViewportEnabled()) |
| return false; |
| |
| const TopDocumentRootScrollerController& controller = |
| GetLayoutBox()->GetDocument().GetPage()->GlobalRootScrollerController(); |
| return controller.RootScrollerArea() == this; |
| } |
| |
| bool PaintLayerScrollableArea::ScheduleAnimation() { |
| if (ChromeClient* client = |
| GetLayoutBox()->GetFrameView()->GetChromeClient()) { |
| client->ScheduleAnimation(GetLayoutBox()->GetFrameView()); |
| return true; |
| } |
| return false; |
| } |
| |
| cc::AnimationHost* PaintLayerScrollableArea::GetCompositorAnimationHost() |
| const { |
| return layer_->GetLayoutObject().GetFrameView()->GetCompositorAnimationHost(); |
| } |
| |
| cc::AnimationTimeline* |
| PaintLayerScrollableArea::GetCompositorAnimationTimeline() const { |
| return layer_->GetLayoutObject().GetFrameView()->GetScrollAnimationTimeline(); |
| } |
| |
| bool PaintLayerScrollableArea::HasTickmarks() const { |
| if (RareData() && !RareData()->tickmarks_override_.empty()) |
| return true; |
| return layer_->IsRootLayer() && |
| To<LayoutView>(GetLayoutBox())->HasTickmarks(); |
| } |
| |
| Vector<gfx::Rect> PaintLayerScrollableArea::GetTickmarks() const { |
| if (RareData() && !RareData()->tickmarks_override_.empty()) |
| return RareData()->tickmarks_override_; |
| if (layer_->IsRootLayer()) |
| return To<LayoutView>(GetLayoutBox())->GetTickmarks(); |
| return Vector<gfx::Rect>(); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::SetHasHorizontalScrollbar( |
| bool has_scrollbar) { |
| if (has_scrollbar) { |
| if (!h_bar_) { |
| h_bar_ = CreateScrollbar(kHorizontalScrollbar); |
| h_bar_is_attached_ = 1; |
| if (!h_bar_->IsCustomScrollbar()) |
| ScrollableArea()->DidAddScrollbar(*h_bar_, kHorizontalScrollbar); |
| } else { |
| h_bar_is_attached_ = 1; |
| } |
| } else { |
| h_bar_is_attached_ = 0; |
| if (!DelayScrollOffsetClampScope::ClampingIsDelayed()) |
| DestroyScrollbar(kHorizontalScrollbar); |
| } |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::SetHasVerticalScrollbar( |
| bool has_scrollbar) { |
| if (has_scrollbar) { |
| if (!v_bar_) { |
| v_bar_ = CreateScrollbar(kVerticalScrollbar); |
| v_bar_is_attached_ = 1; |
| if (!v_bar_->IsCustomScrollbar()) |
| ScrollableArea()->DidAddScrollbar(*v_bar_, kVerticalScrollbar); |
| } else { |
| v_bar_is_attached_ = 1; |
| } |
| } else { |
| v_bar_is_attached_ = 0; |
| if (!DelayScrollOffsetClampScope::ClampingIsDelayed()) |
| DestroyScrollbar(kVerticalScrollbar); |
| } |
| } |
| |
| Scrollbar* PaintLayerScrollableArea::ScrollbarManager::CreateScrollbar( |
| ScrollbarOrientation orientation) { |
| DCHECK(orientation == kHorizontalScrollbar ? !h_bar_is_attached_ |
| : !v_bar_is_attached_); |
| Scrollbar* scrollbar = nullptr; |
| const LayoutObject& style_source = |
| ScrollbarStyleSource(*ScrollableArea()->GetLayoutBox()); |
| if (style_source.StyleRef().HasCustomScrollbarStyle()) { |
| DCHECK(style_source.GetNode() && style_source.GetNode()->IsElementNode()); |
| scrollbar = MakeGarbageCollected<CustomScrollbar>( |
| ScrollableArea(), orientation, To<Element>(style_source.GetNode())); |
| } else { |
| Element* style_source_element = nullptr; |
| style_source_element = DynamicTo<Element>(style_source.GetNode()); |
| scrollbar = MakeGarbageCollected<Scrollbar>(ScrollableArea(), orientation, |
| style_source_element); |
| } |
| ScrollableArea()->GetLayoutBox()->GetDocument().View()->AddScrollbar( |
| scrollbar); |
| return scrollbar; |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::DestroyScrollbar( |
| ScrollbarOrientation orientation) { |
| Member<Scrollbar>& scrollbar = |
| orientation == kHorizontalScrollbar ? h_bar_ : v_bar_; |
| DCHECK(orientation == kHorizontalScrollbar ? !h_bar_is_attached_ |
| : !v_bar_is_attached_); |
| if (!scrollbar) |
| return; |
| |
| ScrollableArea()->SetScrollbarNeedsPaintInvalidation(orientation); |
| |
| if (!scrollbar->IsCustomScrollbar()) |
| ScrollableArea()->WillRemoveScrollbar(*scrollbar, orientation); |
| |
| ScrollableArea()->GetLayoutBox()->GetDocument().View()->RemoveScrollbar( |
| scrollbar); |
| scrollbar->DisconnectFromScrollableArea(); |
| scrollbar = nullptr; |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::DestroyDetachedScrollbars() { |
| DCHECK(!h_bar_is_attached_ || h_bar_); |
| DCHECK(!v_bar_is_attached_ || v_bar_); |
| if (h_bar_ && !h_bar_is_attached_) |
| DestroyScrollbar(kHorizontalScrollbar); |
| if (v_bar_ && !v_bar_is_attached_) |
| DestroyScrollbar(kVerticalScrollbar); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::Dispose() { |
| h_bar_is_attached_ = v_bar_is_attached_ = 0; |
| DestroyScrollbar(kHorizontalScrollbar); |
| DestroyScrollbar(kVerticalScrollbar); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::Trace( |
| blink::Visitor* visitor) const { |
| visitor->Trace(scrollable_area_); |
| visitor->Trace(h_bar_); |
| visitor->Trace(v_bar_); |
| } |
| |
| int PaintLayerScrollableArea::FreezeScrollbarsScope::count_ = 0; |
| |
| PaintLayerScrollableArea::FreezeScrollbarsRootScope::FreezeScrollbarsRootScope( |
| const LayoutBox& box, |
| bool freeze_horizontal, |
| bool freeze_vertical) |
| : scrollable_area_(box.GetScrollableArea()) { |
| if (scrollable_area_ && !FreezeScrollbarsScope::ScrollbarsAreFrozen() && |
| (freeze_horizontal || freeze_vertical)) { |
| scrollable_area_->EstablishScrollbarRoot(freeze_horizontal, |
| freeze_vertical); |
| freezer_.emplace(); |
| } |
| } |
| |
| PaintLayerScrollableArea::FreezeScrollbarsRootScope:: |
| ~FreezeScrollbarsRootScope() { |
| if (scrollable_area_) |
| scrollable_area_->ClearScrollbarRoot(); |
| } |
| |
| int PaintLayerScrollableArea::DelayScrollOffsetClampScope::count_ = 0; |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope:: |
| DelayScrollOffsetClampScope() { |
| DCHECK(count_ > 0 || NeedsClampList().empty()); |
| count_++; |
| } |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope:: |
| ~DelayScrollOffsetClampScope() { |
| if (--count_ == 0) |
| DelayScrollOffsetClampScope::ClampScrollableAreas(); |
| } |
| |
| void PaintLayerScrollableArea::DelayScrollOffsetClampScope::SetNeedsClamp( |
| PaintLayerScrollableArea* scrollable_area) { |
| if (!scrollable_area->NeedsScrollOffsetClamp()) { |
| scrollable_area->SetNeedsScrollOffsetClamp(true); |
| NeedsClampList().push_back(scrollable_area); |
| } |
| } |
| |
| void PaintLayerScrollableArea::DelayScrollOffsetClampScope:: |
| ClampScrollableAreas() { |
| for (auto& scrollable_area : NeedsClampList()) |
| scrollable_area->ClampScrollOffsetAfterOverflowChange(); |
| NeedsClampList().clear(); |
| } |
| |
| HeapVector<Member<PaintLayerScrollableArea>>& |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope::NeedsClampList() { |
| DEFINE_STATIC_LOCAL( |
| Persistent<HeapVector<Member<PaintLayerScrollableArea>>>, |
| needs_clamp_list, |
| (MakeGarbageCollected<HeapVector<Member<PaintLayerScrollableArea>>>())); |
| return *needs_clamp_list; |
| } |
| |
| ScrollbarTheme& PaintLayerScrollableArea::GetPageScrollbarTheme() const { |
| // If PaintLayer is destructed before PaintLayerScrollable area, we can not |
| // get the page scrollbar theme setting. |
| DCHECK(!HasBeenDisposed()); |
| |
| Page* page = GetLayoutBox()->GetFrame()->GetPage(); |
| DCHECK(page); |
| |
| return page->GetScrollbarTheme(); |
| } |
| |
| void PaintLayerScrollableArea::DidAddScrollbar( |
| Scrollbar& scrollbar, |
| ScrollbarOrientation orientation) { |
| if (HasOverlayOverflowControls() || |
| layer_->NeedsReorderOverlayOverflowControls()) { |
| // Z-order of existing or new recordered overflow controls is updated along |
| // with the z-order lists. |
| layer_->DirtyStackingContextZOrderLists(); |
| } |
| ScrollableArea::DidAddScrollbar(scrollbar, orientation); |
| } |
| |
| void PaintLayerScrollableArea::WillRemoveScrollbar( |
| Scrollbar& scrollbar, |
| ScrollbarOrientation orientation) { |
| if (layer_->NeedsReorderOverlayOverflowControls()) { |
| // Z-order of recordered overflow controls is updated along with the z-order |
| // lists. |
| layer_->DirtyStackingContextZOrderLists(); |
| } |
| |
| if (!scrollbar.IsCustomScrollbar()) { |
| ObjectPaintInvalidator(*GetLayoutBox()) |
| .SlowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient( |
| scrollbar, PaintInvalidationReason::kScrollControl); |
| } |
| |
| ScrollableArea::WillRemoveScrollbar(scrollbar, orientation); |
| } |
| |
| // Returns true if the scroll control is invalidated. |
| static bool ScrollControlNeedsPaintInvalidation( |
| const gfx::Rect& new_visual_rect, |
| const gfx::Rect& previous_visual_rect, |
| bool needs_paint_invalidation) { |
| if (new_visual_rect != previous_visual_rect) |
| return true; |
| if (previous_visual_rect.IsEmpty()) { |
| DCHECK(new_visual_rect.IsEmpty()); |
| // Do not issue an empty invalidation. |
| return false; |
| } |
| |
| return needs_paint_invalidation; |
| } |
| |
| bool PaintLayerScrollableArea::ShouldDirectlyCompositeScrollbar( |
| const Scrollbar& scrollbar) const { |
| // Don't composite non-scrollable scrollbars. |
| // TODO(crbug.com/1020913): !ScrollsOverflow() should imply |
| // !scrollbar.Maximum(), but currently that isn't always true due to |
| // different or incorrect rounding methods for scroll geometries. |
| if (!ScrollsOverflow() || !scrollbar.Maximum()) { |
| return false; |
| } |
| if (scrollbar.IsCustomScrollbar()) { |
| return false; |
| } |
| if (RuntimeEnabledFeatures::CompositeScrollAfterPaintEnabled()) { |
| // In CompositeScrollAfterPaint, compositing of scrollbar is decided |
| // in PaintArtifactCompositor. We assume compositing here so that paint |
| // invalidation will be skipped here. We'll invalidate raster if needed |
| // after paint, without paint invalidation. |
| return true; |
| } |
| return NeedsCompositedScrolling(); |
| } |
| |
| void PaintLayerScrollableArea::EstablishScrollbarRoot(bool freeze_horizontal, |
| bool freeze_vertical) { |
| DCHECK(!FreezeScrollbarsScope::ScrollbarsAreFrozen()); |
| is_scrollbar_freeze_root_ = true; |
| is_horizontal_scrollbar_frozen_ = freeze_horizontal; |
| is_vertical_scrollbar_frozen_ = freeze_vertical; |
| } |
| |
| void PaintLayerScrollableArea::ClearScrollbarRoot() { |
| is_scrollbar_freeze_root_ = false; |
| is_horizontal_scrollbar_frozen_ = false; |
| is_vertical_scrollbar_frozen_ = false; |
| } |
| |
| void PaintLayerScrollableArea::InvalidatePaintOfScrollbarIfNeeded( |
| const PaintInvalidatorContext& context, |
| bool needs_paint_invalidation, |
| Scrollbar* scrollbar, |
| bool& previously_was_overlay, |
| bool& previously_was_directly_composited, |
| gfx::Rect& visual_rect) { |
| bool is_overlay = scrollbar && scrollbar->IsOverlayScrollbar(); |
| |
| gfx::Rect new_visual_rect; |
| if (scrollbar) { |
| new_visual_rect = scrollbar->FrameRect(); |
| // TODO(crbug.com/1020913): We should not round paint_offset but should |
| // consider subpixel accumulation when painting scrollbars. |
| new_visual_rect.Offset( |
| ToRoundedVector2d(context.fragment_data->PaintOffset())); |
| } |
| |
| // Invalidate the box's display item client if the box's padding box size is |
| // affected by change of the non-overlay scrollbar width. We detect change of |
| // visual rect size instead of change of scrollbar width, which may have some |
| // false-positives (e.g. the scrollbar changed length but not width) but won't |
| // invalidate more than expected because in the false-positive case the box |
| // must have changed size and have been invalidated. |
| gfx::Size new_scrollbar_used_space_in_box; |
| if (!is_overlay) |
| new_scrollbar_used_space_in_box = new_visual_rect.size(); |
| gfx::Size previous_scrollbar_used_space_in_box; |
| if (!previously_was_overlay) |
| previous_scrollbar_used_space_in_box = visual_rect.size(); |
| |
| // The IsEmpty() check avoids invalidaiton in cases when the visual rect |
| // changes from (0,0 0x0) to (0,0 0x100). |
| if (!(new_scrollbar_used_space_in_box.IsEmpty() && |
| previous_scrollbar_used_space_in_box.IsEmpty()) && |
| new_scrollbar_used_space_in_box != previous_scrollbar_used_space_in_box) { |
| context.painting_layer->SetNeedsRepaint(); |
| const auto& box = *GetLayoutBox(); |
| ObjectPaintInvalidator(box).InvalidateDisplayItemClient( |
| box, PaintInvalidationReason::kLayout); |
| } |
| |
| previously_was_overlay = is_overlay; |
| |
| if (scrollbar) { |
| bool directly_composited = ShouldDirectlyCompositeScrollbar(*scrollbar); |
| if (directly_composited != previously_was_directly_composited) { |
| needs_paint_invalidation = true; |
| previously_was_directly_composited = directly_composited; |
| } else if (directly_composited) { |
| // Don't invalidate directly composited scrollbar if the change is only |
| // inside of the scrollbar. ScrollbarDisplayItem will handle such change. |
| needs_paint_invalidation = false; |
| } |
| } |
| |
| if (scrollbar && |
| ScrollControlNeedsPaintInvalidation(new_visual_rect, visual_rect, |
| needs_paint_invalidation)) { |
| context.painting_layer->SetNeedsRepaint(); |
| scrollbar->Invalidate(PaintInvalidationReason::kScrollControl); |
| if (auto* custom_scrollbar = DynamicTo<CustomScrollbar>(scrollbar)) |
| custom_scrollbar->InvalidateDisplayItemClientsOfScrollbarParts(); |
| } |
| |
| visual_rect = new_visual_rect; |
| } |
| |
| void PaintLayerScrollableArea::InvalidatePaintOfScrollControlsIfNeeded( |
| const PaintInvalidatorContext& context) { |
| if (context.subtree_flags & PaintInvalidatorContext::kSubtreeFullInvalidation) |
| SetScrollControlsNeedFullPaintInvalidation(); |
| |
| InvalidatePaintOfScrollbarIfNeeded( |
| context, HorizontalScrollbarNeedsPaintInvalidation(), |
| HorizontalScrollbar(), horizontal_scrollbar_previously_was_overlay_, |
| horizontal_scrollbar_previously_was_directly_composited_, |
| horizontal_scrollbar_visual_rect_); |
| InvalidatePaintOfScrollbarIfNeeded( |
| context, VerticalScrollbarNeedsPaintInvalidation(), VerticalScrollbar(), |
| vertical_scrollbar_previously_was_overlay_, |
| vertical_scrollbar_previously_was_directly_composited_, |
| vertical_scrollbar_visual_rect_); |
| |
| gfx::Rect new_scroll_corner_and_resizer_visual_rect = |
| ScrollCornerAndResizerRect(); |
| // TODO(crbug.com/1020913): We should not round paint_offset but should |
| // consider subpixel accumulation when painting scrollbars. |
| new_scroll_corner_and_resizer_visual_rect.Offset( |
| ToRoundedVector2d(context.fragment_data->PaintOffset())); |
| if (ScrollControlNeedsPaintInvalidation( |
| new_scroll_corner_and_resizer_visual_rect, |
| scroll_corner_and_resizer_visual_rect_, |
| ScrollCornerNeedsPaintInvalidation())) { |
| scroll_corner_and_resizer_visual_rect_ = |
| new_scroll_corner_and_resizer_visual_rect; |
| if (LayoutCustomScrollbarPart* scroll_corner = ScrollCorner()) { |
| DCHECK(!scroll_corner->PaintingLayer()); |
| ObjectPaintInvalidator(*scroll_corner) |
| .InvalidateDisplayItemClient(*scroll_corner, |
| PaintInvalidationReason::kScrollControl); |
| } |
| if (LayoutCustomScrollbarPart* resizer = Resizer()) { |
| DCHECK(!resizer->PaintingLayer()); |
| ObjectPaintInvalidator(*resizer).InvalidateDisplayItemClient( |
| *resizer, PaintInvalidationReason::kScrollControl); |
| } |
| |
| context.painting_layer->SetNeedsRepaint(); |
| ObjectPaintInvalidator(*GetLayoutBox()) |
| .InvalidateDisplayItemClient(GetScrollCornerDisplayItemClient(), |
| PaintInvalidationReason::kLayout); |
| } |
| |
| ClearNeedsPaintInvalidationForScrollControls(); |
| } |
| |
| void PaintLayerScrollableArea::ScrollControlWasSetNeedsPaintInvalidation() { |
| GetLayoutBox()->SetShouldCheckForPaintInvalidation(); |
| } |
| |
| void PaintLayerScrollableArea::DidScrollWithScrollbar( |
| ScrollbarPart part, |
| ScrollbarOrientation orientation, |
| WebInputEvent::Type type) { |
| WebFeature scrollbar_use_uma; |
| switch (part) { |
| case kBackButtonEndPart: |
| case kForwardButtonStartPart: |
| UseCounter::Count( |
| GetLayoutBox()->GetDocument(), |
| WebFeature::kScrollbarUseScrollbarButtonReversedDirection); |
| U_FALLTHROUGH; |
| case kBackButtonStartPart: |
| case kForwardButtonEndPart: |
| scrollbar_use_uma = |
| (orientation == kVerticalScrollbar |
| ? WebFeature::kScrollbarUseVerticalScrollbarButton |
| : WebFeature::kScrollbarUseHorizontalScrollbarButton); |
| break; |
| case kThumbPart: |
| if (orientation == kVerticalScrollbar) { |
| scrollbar_use_uma = |
| (WebInputEvent::IsMouseEventType(type) |
| ? WebFeature::kVerticalScrollbarThumbScrollingWithMouse |
| : WebFeature::kVerticalScrollbarThumbScrollingWithTouch); |
| } else { |
| scrollbar_use_uma = |
| (WebInputEvent::IsMouseEventType(type) |
| ? WebFeature::kHorizontalScrollbarThumbScrollingWithMouse |
| : WebFeature::kHorizontalScrollbarThumbScrollingWithTouch); |
| } |
| break; |
| case kBackTrackPart: |
| case kForwardTrackPart: |
| scrollbar_use_uma = |
| (orientation == kVerticalScrollbar |
| ? WebFeature::kScrollbarUseVerticalScrollbarTrack |
| : WebFeature::kScrollbarUseHorizontalScrollbarTrack); |
| break; |
| default: |
| return; |
| } |
| |
| Document& document = GetLayoutBox()->GetDocument(); |
| |
| UseCounter::Count(document, scrollbar_use_uma); |
| } |
| |
| CompositorElementId PaintLayerScrollableArea::GetScrollElementId() const { |
| return CompositorElementIdFromUniqueObjectId( |
| GetLayoutBox()->UniqueId(), CompositorElementIdNamespace::kScroll); |
| } |
| |
| gfx::Size PaintLayerScrollableArea::PixelSnappedBorderBoxSize() const { |
| // TODO(crbug.com/1020913): We use this method during |
| // PositionOverflowControls() even before the paint offset is updated. |
| // This can be fixed only after we support subpixels in overflow control |
| // geometry. For now we ensure correct pixel snapping of overflow controls by |
| // calling PositionOverflowControls() again when paint offset is updated. |
| return GetLayoutBox()->PixelSnappedBorderBoxSize( |
| GetLayoutBox()->FirstFragment().PaintOffset()); |
| } |
| |
| gfx::Rect PaintLayerScrollableArea::ScrollingBackgroundVisualRect( |
| const PhysicalOffset& paint_offset) const { |
| const auto* box = GetLayoutBox(); |
| auto clip_rect = box->OverflowClipRect(paint_offset); |
| auto overflow_clip_rect = ToPixelSnappedRect(clip_rect); |
| auto scroll_size = PixelSnappedContentsSize(clip_rect.offset); |
| // Ensure scrolling contents are at least as large as the scroll clip |
| scroll_size.SetToMax(overflow_clip_rect.size()); |
| gfx::Rect result(overflow_clip_rect.origin(), scroll_size); |
| |
| // The HTML element of a document is special, in that it can have a transform, |
| // but the bounds of the painted area of the element still extends beyond |
| // its actual size to encompass the entire viewport canvas. This is |
| // accomplished in ViewPainter by starting with a rect in viewport canvas |
| // space that is equal to the size of the viewport canvas, then mapping it |
| // into the local border box space of the HTML element, and painting a rect |
| // equal to the bounding box of the result. We need to add in that mapped rect |
| // in such cases. |
| const Document& document = box->GetDocument(); |
| if (IsA<LayoutView>(box) && |
| (document.IsXMLDocument() || document.IsHTMLDocument())) { |
| if (const auto* document_element = document.documentElement()) { |
| if (const auto* document_element_object = |
| document_element->GetLayoutObject()) { |
| const auto& document_element_state = |
| document_element_object->FirstFragment().LocalBorderBoxProperties(); |
| const auto& view_contents_state = |
| box->FirstFragment().ContentsProperties(); |
| gfx::Rect result_in_view = result; |
| GeometryMapper::SourceToDestinationRect( |
| view_contents_state.Transform(), document_element_state.Transform(), |
| result_in_view); |
| result.Union(result_in_view); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| String |
| PaintLayerScrollableArea::ScrollingBackgroundDisplayItemClient::DebugName() |
| const { |
| return "Scrolling background of " + |
| scrollable_area_->GetLayoutBox()->DebugName(); |
| } |
| |
| DOMNodeId |
| PaintLayerScrollableArea::ScrollingBackgroundDisplayItemClient::OwnerNodeId() |
| const { |
| return static_cast<const DisplayItemClient*>(scrollable_area_->GetLayoutBox()) |
| ->OwnerNodeId(); |
| } |
| |
| String PaintLayerScrollableArea::ScrollCornerDisplayItemClient::DebugName() |
| const { |
| return "Scroll corner of " + scrollable_area_->GetLayoutBox()->DebugName(); |
| } |
| |
| DOMNodeId PaintLayerScrollableArea::ScrollCornerDisplayItemClient::OwnerNodeId() |
| const { |
| return static_cast<const DisplayItemClient*>(scrollable_area_->GetLayoutBox()) |
| ->OwnerNodeId(); |
| } |
| |
| void PaintLayerScrollableArea::TraceComputeScrollbarExistence( |
| ComputeScrollbarExistenceReason reason, |
| bool needs_horizontal_scrollbar, |
| bool needs_vertical_scrollbar, |
| ComputeScrollbarExistenceOption option, |
| bool early_exit, |
| mojom::blink::ScrollbarMode h_mode, |
| mojom::blink::ScrollbarMode v_mode) const { |
| TRACE_EVENT_INSTANT( |
| TRACE_DISABLED_BY_DEFAULT("blink.debug.layout.scrollbars"), |
| "ComputeScrollbarExistence", [&](perfetto::EventContext ctx) { |
| ctx.AddDebugAnnotation("reason", reason); |
| ctx.AddDebugAnnotation("needs_horizontal", needs_horizontal_scrollbar); |
| ctx.AddDebugAnnotation("needs_vertical", needs_vertical_scrollbar); |
| ctx.AddDebugAnnotation("option", option); |
| ctx.AddDebugAnnotation("early_exit", early_exit); |
| ctx.AddDebugAnnotation("h_mode", static_cast<int>(h_mode)); |
| ctx.AddDebugAnnotation("v_mode", static_cast<int>(v_mode)); |
| ctx.AddDebugAnnotation("layer_size", Size().ToString()); |
| ctx.AddDebugAnnotation("overflow_rect", overflow_rect_.ToString()); |
| ctx.AddDebugAnnotation("is_root", Layer()->IsRootLayer()); |
| ctx.AddDebugAnnotation("is_main_frame", |
| GetLayoutBox()->GetFrame()->IsMainFrame()); |
| }); |
| } |
| |
| } // namespace blink |