| /* |
| * 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@fas.harvard.edu> |
| * 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 "core/paint/PaintLayerScrollableArea.h" |
| |
| #include "core/css/PseudoStyleRequest.h" |
| #include "core/dom/AXObjectCache.h" |
| #include "core/dom/DOMNodeIds.h" |
| #include "core/dom/Node.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/PageScaleConstraintsSet.h" |
| #include "core/frame/RootFrameViewport.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/VisualViewport.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/input/EventHandler.h" |
| #include "core/layout/LayoutFlexibleBox.h" |
| #include "core/layout/LayoutPart.h" |
| #include "core/layout/LayoutScrollbar.h" |
| #include "core/layout/LayoutScrollbarPart.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LayoutBoxItem.h" |
| #include "core/layout/compositing/CompositedLayerMapping.h" |
| #include "core/layout/compositing/PaintLayerCompositor.h" |
| #include "core/loader/FrameLoaderClient.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/FocusController.h" |
| #include "core/page/Page.h" |
| #include "core/page/scrolling/RootScrollerController.h" |
| #include "core/page/scrolling/RootScrollerUtil.h" |
| #include "core/page/scrolling/ScrollingCoordinator.h" |
| #include "core/page/scrolling/TopDocumentRootScrollerController.h" |
| #include "core/paint/PaintLayerFragment.h" |
| #include "platform/PlatformMouseEvent.h" |
| #include "platform/graphics/CompositorMutableProperties.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| #include "platform/graphics/paint/DrawingRecorder.h" |
| #include "platform/scroll/ScrollAnimatorBase.h" |
| #include "platform/scroll/ScrollbarTheme.h" |
| #include "public/platform/Platform.h" |
| |
| namespace blink { |
| |
| static LayoutRect localToAbsolute(LayoutBox& offset, LayoutRect rect) { |
| return LayoutRect( |
| offset.localToAbsoluteQuad(FloatQuad(FloatRect(rect)), UseTransforms) |
| .boundingBox()); |
| } |
| |
| PaintLayerScrollableAreaRareData::PaintLayerScrollableAreaRareData() {} |
| |
| const int ResizerControlExpandRatioForTouch = 2; |
| |
| PaintLayerScrollableArea::PaintLayerScrollableArea(PaintLayer& layer) |
| : m_layer(layer), |
| m_nextTopmostScrollChild(0), |
| m_topmostScrollChild(0), |
| m_inResizeMode(false), |
| m_scrollsOverflow(false), |
| m_inOverflowRelayout(false), |
| m_needsCompositedScrolling(false), |
| m_rebuildHorizontalScrollbarLayer(false), |
| m_rebuildVerticalScrollbarLayer(false), |
| m_needsScrollOffsetClamp(false), |
| m_needsRelayout(false), |
| m_hadHorizontalScrollbarBeforeRelayout(false), |
| m_hadVerticalScrollbarBeforeRelayout(false), |
| m_scrollbarManager(*this), |
| m_scrollCorner(nullptr), |
| m_resizer(nullptr), |
| m_scrollAnchor(this), |
| m_reasons(0) |
| #if DCHECK_IS_ON() |
| , |
| m_hasBeenDisposed(false) |
| #endif |
| { |
| Node* node = box().node(); |
| if (node && node->isElementNode()) { |
| // We save and restore only the scrollOffset as the other scroll values are |
| // recalculated. |
| Element* element = toElement(node); |
| m_scrollOffset = element->savedLayerScrollOffset(); |
| if (!m_scrollOffset.isZero()) |
| scrollAnimator().setCurrentOffset(m_scrollOffset); |
| element->setSavedLayerScrollOffset(ScrollOffset()); |
| } |
| updateResizerAreaSet(); |
| } |
| |
| PaintLayerScrollableArea::~PaintLayerScrollableArea() { |
| #if DCHECK_IS_ON() |
| DCHECK(m_hasBeenDisposed); |
| #endif |
| } |
| |
| void PaintLayerScrollableArea::dispose() { |
| if (inResizeMode() && !box().documentBeingDestroyed()) { |
| if (LocalFrame* frame = box().frame()) |
| frame->eventHandler().resizeScrollableAreaDestroyed(); |
| } |
| |
| if (LocalFrame* frame = box().frame()) { |
| if (FrameView* frameView = frame->view()) { |
| frameView->removeScrollableArea(this); |
| frameView->removeAnimatingScrollableArea(this); |
| } |
| } |
| |
| removeStyleRelatedMainThreadScrollingReasons(); |
| |
| if (ScrollingCoordinator* scrollingCoordinator = getScrollingCoordinator()) |
| scrollingCoordinator->willDestroyScrollableArea(this); |
| |
| if (!box().documentBeingDestroyed()) { |
| Node* node = box().node(); |
| // FIXME: Make setSavedLayerScrollOffset take DoubleSize. crbug.com/414283. |
| if (node && node->isElementNode()) |
| toElement(node)->setSavedLayerScrollOffset(m_scrollOffset); |
| } |
| |
| if (LocalFrame* frame = box().frame()) { |
| if (FrameView* frameView = frame->view()) |
| frameView->removeResizerArea(box()); |
| } |
| |
| box() |
| .document() |
| .frameHost() |
| ->globalRootScrollerController() |
| .didDisposeScrollableArea(*this); |
| |
| m_scrollbarManager.dispose(); |
| |
| if (m_scrollCorner) |
| m_scrollCorner->destroy(); |
| if (m_resizer) |
| m_resizer->destroy(); |
| |
| clearScrollableArea(); |
| |
| // 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. |
| if (RuntimeEnabledFeatures::scrollAnchoringEnabled() && |
| !box().documentBeingDestroyed()) |
| m_scrollAnchor.clearSelf(); |
| |
| #if DCHECK_IS_ON() |
| m_hasBeenDisposed = true; |
| #endif |
| } |
| |
| DEFINE_TRACE(PaintLayerScrollableArea) { |
| visitor->trace(m_scrollbarManager); |
| visitor->trace(m_scrollAnchor); |
| ScrollableArea::trace(visitor); |
| } |
| |
| HostWindow* PaintLayerScrollableArea::getHostWindow() const { |
| if (Page* page = box().frame()->page()) |
| return &page->chromeClient(); |
| return nullptr; |
| } |
| |
| GraphicsLayer* PaintLayerScrollableArea::layerForScrolling() const { |
| return layer()->hasCompositedLayerMapping() |
| ? layer()->compositedLayerMapping()->scrollingContentsLayer() |
| : 0; |
| } |
| |
| GraphicsLayer* PaintLayerScrollableArea::layerForHorizontalScrollbar() const { |
| // See crbug.com/343132. |
| DisableCompositingQueryAsserts disabler; |
| |
| return layer()->hasCompositedLayerMapping() |
| ? layer()->compositedLayerMapping()->layerForHorizontalScrollbar() |
| : 0; |
| } |
| |
| GraphicsLayer* PaintLayerScrollableArea::layerForVerticalScrollbar() const { |
| // See crbug.com/343132. |
| DisableCompositingQueryAsserts disabler; |
| |
| return layer()->hasCompositedLayerMapping() |
| ? layer()->compositedLayerMapping()->layerForVerticalScrollbar() |
| : 0; |
| } |
| |
| GraphicsLayer* PaintLayerScrollableArea::layerForScrollCorner() const { |
| // See crbug.com/343132. |
| DisableCompositingQueryAsserts disabler; |
| |
| return layer()->hasCompositedLayerMapping() |
| ? layer()->compositedLayerMapping()->layerForScrollCorner() |
| : 0; |
| } |
| |
| bool PaintLayerScrollableArea::shouldUseIntegerScrollOffset() const { |
| Frame* frame = box().frame(); |
| if (frame->settings() && |
| !frame->settings()->getPreferCompositingToLCDTextEnabled()) |
| return true; |
| |
| return ScrollableArea::shouldUseIntegerScrollOffset(); |
| } |
| |
| bool PaintLayerScrollableArea::isActive() const { |
| Page* page = box().frame()->page(); |
| return page && page->focusController().isActive(); |
| } |
| |
| bool PaintLayerScrollableArea::isScrollCornerVisible() const { |
| return !scrollCornerRect().isEmpty(); |
| } |
| |
| static int cornerStart(const LayoutBox& box, |
| int minX, |
| int maxX, |
| int thickness) { |
| if (box.shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| return minX + box.styleRef().borderLeftWidth(); |
| return maxX - thickness - box.styleRef().borderRightWidth(); |
| } |
| |
| static IntRect cornerRect(const LayoutBox& box, |
| const Scrollbar* horizontalScrollbar, |
| const Scrollbar* verticalScrollbar, |
| const IntRect& bounds) { |
| int horizontalThickness; |
| int verticalThickness; |
| if (!verticalScrollbar && !horizontalScrollbar) { |
| // FIXME: This isn't right. We need to know the thickness of custom |
| // scrollbars even when they don't exist in order to set the resizer square |
| // size properly. |
| horizontalThickness = ScrollbarTheme::theme().scrollbarThickness(); |
| verticalThickness = horizontalThickness; |
| } else if (verticalScrollbar && !horizontalScrollbar) { |
| horizontalThickness = verticalScrollbar->scrollbarThickness(); |
| verticalThickness = horizontalThickness; |
| } else if (horizontalScrollbar && !verticalScrollbar) { |
| verticalThickness = horizontalScrollbar->scrollbarThickness(); |
| horizontalThickness = verticalThickness; |
| } else { |
| horizontalThickness = verticalScrollbar->scrollbarThickness(); |
| verticalThickness = horizontalScrollbar->scrollbarThickness(); |
| } |
| return IntRect( |
| cornerStart(box, bounds.x(), bounds.maxX(), horizontalThickness), |
| bounds.maxY() - verticalThickness - box.styleRef().borderBottomWidth(), |
| horizontalThickness, verticalThickness); |
| } |
| |
| IntRect 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 hasHorizontalBar = horizontalScrollbar(); |
| bool hasVerticalBar = verticalScrollbar(); |
| bool hasResizer = box().style()->resize() != RESIZE_NONE; |
| if ((hasHorizontalBar && hasVerticalBar) || |
| (hasResizer && (hasHorizontalBar || hasVerticalBar))) |
| return cornerRect(box(), horizontalScrollbar(), verticalScrollbar(), |
| box().pixelSnappedBorderBoxRect()); |
| return IntRect(); |
| } |
| |
| IntRect PaintLayerScrollableArea::convertFromScrollbarToContainingWidget( |
| const Scrollbar& scrollbar, |
| const IntRect& scrollbarRect) const { |
| LayoutView* view = box().view(); |
| if (!view) |
| return scrollbarRect; |
| |
| IntRect rect = scrollbarRect; |
| rect.move(scrollbarOffset(scrollbar)); |
| |
| return view->frameView()->convertFromLayoutItem(LayoutBoxItem(&box()), rect); |
| } |
| |
| IntRect PaintLayerScrollableArea::convertFromContainingWidgetToScrollbar( |
| const Scrollbar& scrollbar, |
| const IntRect& parentRect) const { |
| LayoutView* view = box().view(); |
| if (!view) |
| return parentRect; |
| |
| IntRect rect = |
| view->frameView()->convertToLayoutItem(LayoutBoxItem(&box()), parentRect); |
| rect.move(-scrollbarOffset(scrollbar)); |
| return rect; |
| } |
| |
| IntPoint PaintLayerScrollableArea::convertFromScrollbarToContainingWidget( |
| const Scrollbar& scrollbar, |
| const IntPoint& scrollbarPoint) const { |
| LayoutView* view = box().view(); |
| if (!view) |
| return scrollbarPoint; |
| |
| IntPoint point = scrollbarPoint; |
| point.move(scrollbarOffset(scrollbar)); |
| return view->frameView()->convertFromLayoutItem(LayoutBoxItem(&box()), point); |
| } |
| |
| IntPoint PaintLayerScrollableArea::convertFromContainingWidgetToScrollbar( |
| const Scrollbar& scrollbar, |
| const IntPoint& parentPoint) const { |
| LayoutView* view = box().view(); |
| if (!view) |
| return parentPoint; |
| |
| IntPoint point = view->frameView()->convertToLayoutItem(LayoutBoxItem(&box()), |
| parentPoint); |
| |
| point.move(-scrollbarOffset(scrollbar)); |
| return point; |
| } |
| |
| int PaintLayerScrollableArea::scrollSize( |
| ScrollbarOrientation orientation) const { |
| IntSize scrollDimensions = |
| maximumScrollOffsetInt() - minimumScrollOffsetInt(); |
| return (orientation == HorizontalScrollbar) ? scrollDimensions.width() |
| : scrollDimensions.height(); |
| } |
| |
| void PaintLayerScrollableArea::updateScrollOffset(const ScrollOffset& newOffset, |
| ScrollType scrollType) { |
| if (getScrollOffset() == newOffset) |
| return; |
| |
| showOverlayScrollbars(); |
| m_scrollOffset = newOffset; |
| |
| LocalFrame* frame = box().frame(); |
| DCHECK(frame); |
| |
| FrameView* frameView = box().frameView(); |
| |
| TRACE_EVENT1("devtools.timeline", "ScrollLayer", "data", |
| InspectorScrollLayerEvent::data(&box())); |
| |
| // FIXME(420741): Resolve circular dependency between scroll offset and |
| // compositing state, and remove this disabler. |
| DisableCompositingQueryAsserts disabler; |
| |
| // Update the positions of our child layers (if needed as only fixed layers |
| // should be impacted by a scroll). We don't update compositing layers, |
| // because we need to do a deep update from the compositing ancestor. |
| if (!frameView->isInPerformLayout()) { |
| // If we're in the middle of layout, we'll just update layers once layout |
| // has finished. |
| layer()->updateLayerPositionsAfterOverflowScroll(); |
| // Update regions, scrolling may change the clip of a particular region. |
| frameView->updateDocumentAnnotatedRegions(); |
| frameView->setNeedsUpdateWidgetGeometries(); |
| updateCompositingLayersAfterScroll(); |
| } |
| |
| const LayoutBoxModelObject& paintInvalidationContainer = |
| box().containerForPaintInvalidation(); |
| // The caret rect needs to be invalidated after scrolling |
| frame->selection().setCaretRectNeedsUpdate(); |
| |
| FloatQuad quadForFakeMouseMoveEvent = FloatQuad(FloatRect( |
| layer()->layoutObject()->previousVisualRectIncludingCompositedScrolling( |
| paintInvalidationContainer))); |
| |
| quadForFakeMouseMoveEvent = |
| paintInvalidationContainer.localToAbsoluteQuad(quadForFakeMouseMoveEvent); |
| frame->eventHandler().dispatchFakeMouseMoveEventSoonInQuad( |
| quadForFakeMouseMoveEvent); |
| |
| if (scrollType == UserScroll || scrollType == CompositorScroll) { |
| Page* page = frame->page(); |
| if (page) |
| page->chromeClient().clearToolTip(*frame); |
| } |
| |
| bool requiresPaintInvalidation = true; |
| |
| if (box().view()->compositor()->inCompositingMode()) { |
| bool onlyScrolledCompositedLayers = |
| scrollsOverflow() && layer()->isAllScrollingContentComposited() && |
| box().style()->backgroundLayers().attachment() != |
| LocalBackgroundAttachment; |
| |
| if (usesCompositedScrolling() || onlyScrolledCompositedLayers) |
| requiresPaintInvalidation = false; |
| } |
| |
| // Only the root layer can overlap non-composited fixed-position elements. |
| if (!requiresPaintInvalidation && layer()->isRootLayer() && |
| frameView->hasViewportConstrainedObjects()) { |
| if (!frameView->invalidateViewportConstrainedObjects()) |
| requiresPaintInvalidation = true; |
| } |
| |
| // Just schedule a full paint invalidation of our object. |
| // FIXME: This invalidation will be unnecessary in slimming paint phase 2. |
| if (requiresPaintInvalidation) { |
| box().setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); |
| } |
| |
| if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()) { |
| // The scrollOffsetTranslation paint property depends on the scroll offset. |
| // (see: PaintPropertyTreeBuilder.updateProperties(FrameView&,...) and |
| // PaintPropertyTreeBuilder.updateScrollAndScrollTranslation). |
| if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled() && |
| layer()->isRootLayer()) { |
| frameView->setNeedsPaintPropertyUpdate(); |
| } else { |
| box().setNeedsPaintPropertyUpdate(); |
| } |
| } |
| |
| // Schedule the scroll DOM event. |
| if (box().node()) |
| box().node()->document().enqueueScrollEventForNode(box().node()); |
| |
| if (AXObjectCache* cache = box().document().existingAXObjectCache()) |
| cache->handleScrollPositionChanged(&box()); |
| box().view()->clearHitTestCache(); |
| |
| // Inform the FrameLoader of the new scroll position, so it can be restored |
| // when navigating back. |
| if (layer()->isRootLayer()) { |
| frameView->frame().loader().saveScrollState(); |
| frameView->didChangeScrollOffset(); |
| } |
| |
| if (scrollTypeClearsFragmentAnchor(scrollType)) |
| frameView->clearFragmentAnchor(); |
| |
| // Clear the scroll anchor, unless it is the reason for this scroll. |
| if (RuntimeEnabledFeatures::scrollAnchoringEnabled() && |
| scrollType != AnchoringScroll && scrollType != ClampingScroll) |
| scrollAnchor()->clear(); |
| } |
| |
| IntSize PaintLayerScrollableArea::scrollOffsetInt() const { |
| return flooredIntSize(m_scrollOffset); |
| } |
| |
| ScrollOffset PaintLayerScrollableArea::getScrollOffset() const { |
| return m_scrollOffset; |
| } |
| |
| IntSize PaintLayerScrollableArea::minimumScrollOffsetInt() const { |
| return toIntSize(-scrollOrigin()); |
| } |
| |
| IntSize PaintLayerScrollableArea::maximumScrollOffsetInt() const { |
| if (!box().hasOverflowClip()) |
| return toIntSize(-scrollOrigin()); |
| |
| IntSize contentSize = contentsSize(); |
| IntSize visibleSize = |
| pixelSnappedIntRect(box().overflowClipRect(box().location())).size(); |
| |
| FrameHost* host = layoutBox()->document().frameHost(); |
| DCHECK(host); |
| TopDocumentRootScrollerController& controller = |
| host->globalRootScrollerController(); |
| |
| // The global root scroller should be clipped by the top FrameView 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. |
| if (this == controller.rootScrollerArea()) |
| visibleSize = controller.rootScrollerVisibleArea(); |
| |
| // 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). |
| contentSize = contentSize.expandedTo(visibleSize); |
| |
| return toIntSize(-scrollOrigin() + (contentSize - visibleSize)); |
| } |
| |
| IntRect PaintLayerScrollableArea::visibleContentRect( |
| IncludeScrollbarsInRect scrollbarInclusion) const { |
| int verticalScrollbarWidth = 0; |
| int horizontalScrollbarHeight = 0; |
| if (scrollbarInclusion == IncludeScrollbars) { |
| verticalScrollbarWidth = |
| (verticalScrollbar() && !verticalScrollbar()->isOverlayScrollbar()) |
| ? verticalScrollbar()->scrollbarThickness() |
| : 0; |
| horizontalScrollbarHeight = |
| (horizontalScrollbar() && !horizontalScrollbar()->isOverlayScrollbar()) |
| ? horizontalScrollbar()->scrollbarThickness() |
| : 0; |
| } |
| |
| // TODO(szager): Handle fractional scroll offsets correctly. |
| return IntRect( |
| flooredIntPoint(scrollPosition()), |
| IntSize(max(0, layer()->size().width() - verticalScrollbarWidth), |
| max(0, layer()->size().height() - horizontalScrollbarHeight))); |
| } |
| |
| void PaintLayerScrollableArea::visibleSizeChanged() { |
| showOverlayScrollbars(); |
| } |
| |
| int PaintLayerScrollableArea::visibleHeight() const { |
| return layer()->size().height(); |
| } |
| |
| int PaintLayerScrollableArea::visibleWidth() const { |
| return layer()->size().width(); |
| } |
| |
| IntSize PaintLayerScrollableArea::contentsSize() const { |
| return IntSize(pixelSnappedScrollWidth(), pixelSnappedScrollHeight()); |
| } |
| |
| IntPoint PaintLayerScrollableArea::lastKnownMousePosition() const { |
| return box().frame() ? box().frame()->eventHandler().lastKnownMousePosition() |
| : IntPoint(); |
| } |
| |
| bool PaintLayerScrollableArea::scrollAnimatorEnabled() const { |
| if (Settings* settings = box().frame()->settings()) |
| return settings->getScrollAnimatorEnabled(); |
| return false; |
| } |
| |
| bool PaintLayerScrollableArea::shouldSuspendScrollAnimations() const { |
| LayoutView* view = box().view(); |
| if (!view) |
| return true; |
| return view->frameView()->shouldSuspendScrollAnimations(); |
| } |
| |
| void PaintLayerScrollableArea::scrollbarVisibilityChanged() { |
| updateScrollbarEnabledState(); |
| |
| if (LayoutView* view = box().view()) |
| return view->clearHitTestCache(); |
| } |
| |
| bool PaintLayerScrollableArea::scrollbarsCanBeActive() const { |
| LayoutView* view = box().view(); |
| if (!view) |
| return false; |
| return view->frameView()->scrollbarsCanBeActive(); |
| } |
| |
| IntRect PaintLayerScrollableArea::scrollableAreaBoundingBox() const { |
| return box().absoluteBoundingBoxRect(); |
| } |
| |
| void PaintLayerScrollableArea::registerForAnimation() { |
| if (LocalFrame* frame = box().frame()) { |
| if (FrameView* frameView = frame->view()) |
| frameView->addAnimatingScrollableArea(this); |
| } |
| } |
| |
| void PaintLayerScrollableArea::deregisterForAnimation() { |
| if (LocalFrame* frame = box().frame()) { |
| if (FrameView* frameView = frame->view()) |
| frameView->removeAnimatingScrollableArea(this); |
| } |
| } |
| |
| bool PaintLayerScrollableArea::userInputScrollable( |
| ScrollbarOrientation orientation) const { |
| if (box().isIntrinsicallyScrollable(orientation)) |
| return true; |
| |
| EOverflow overflowStyle = (orientation == HorizontalScrollbar) |
| ? box().style()->overflowX() |
| : box().style()->overflowY(); |
| return (overflowStyle == EOverflow::Scroll || |
| overflowStyle == EOverflow::Auto || |
| overflowStyle == EOverflow::Overlay); |
| } |
| |
| bool PaintLayerScrollableArea::shouldPlaceVerticalScrollbarOnLeft() const { |
| return box().shouldPlaceBlockDirectionScrollbarOnLogicalLeft(); |
| } |
| |
| int PaintLayerScrollableArea::pageStep(ScrollbarOrientation orientation) const { |
| int length = (orientation == HorizontalScrollbar) |
| ? box().pixelSnappedClientWidth() |
| : box().pixelSnappedClientHeight(); |
| int minPageStep = static_cast<float>(length) * |
| ScrollableArea::minFractionToStepWhenPaging(); |
| int pageStep = |
| max(minPageStep, length - ScrollableArea::maxOverlapBetweenPages()); |
| |
| return max(pageStep, 1); |
| } |
| |
| LayoutBox& PaintLayerScrollableArea::box() const { |
| return *m_layer.layoutBox(); |
| } |
| |
| PaintLayer* PaintLayerScrollableArea::layer() const { |
| return &m_layer; |
| } |
| |
| LayoutUnit PaintLayerScrollableArea::scrollWidth() const { |
| return m_overflowRect.width(); |
| } |
| |
| LayoutUnit PaintLayerScrollableArea::scrollHeight() const { |
| return m_overflowRect.height(); |
| } |
| |
| int PaintLayerScrollableArea::pixelSnappedScrollWidth() const { |
| return snapSizeToPixel(scrollWidth(), |
| box().clientLeft() + box().location().x()); |
| } |
| |
| int PaintLayerScrollableArea::pixelSnappedScrollHeight() const { |
| return snapSizeToPixel(scrollHeight(), |
| box().clientTop() + box().location().y()); |
| } |
| |
| void PaintLayerScrollableArea::updateScrollOrigin() { |
| // This should do nothing prior to first layout; the if-clause will catch |
| // that. |
| if (overflowRect().isEmpty()) |
| return; |
| LayoutPoint scrollableOverflow = |
| m_overflowRect.location() - |
| LayoutSize(box().borderLeft(), box().borderTop()); |
| setScrollOrigin(flooredIntPoint(-scrollableOverflow) + |
| box().originAdjustmentForScrollbars()); |
| } |
| |
| void PaintLayerScrollableArea::updateScrollDimensions() { |
| if (m_overflowRect.size() != box().layoutOverflowRect().size()) |
| contentsResized(); |
| m_overflowRect = box().layoutOverflowRect(); |
| box().flipForWritingMode(m_overflowRect); |
| updateScrollOrigin(); |
| } |
| |
| void PaintLayerScrollableArea::updateScrollbarEnabledState() { |
| bool forceDisable = |
| ScrollbarTheme::theme().shouldDisableInvisibleScrollbars() && |
| scrollbarsHidden(); |
| |
| if (horizontalScrollbar()) |
| horizontalScrollbar()->setEnabled(hasHorizontalOverflow() && !forceDisable); |
| if (verticalScrollbar()) |
| verticalScrollbar()->setEnabled(hasVerticalOverflow() && !forceDisable); |
| } |
| |
| void PaintLayerScrollableArea::setScrollOffsetUnconditionally( |
| const ScrollOffset& offset, |
| ScrollType scrollType) { |
| cancelScrollAnimation(); |
| scrollOffsetChanged(offset, scrollType); |
| } |
| |
| void PaintLayerScrollableArea::updateAfterLayout() { |
| DCHECK(box().hasOverflowClip()); |
| |
| bool relayoutIsPrevented = PreventRelayoutScope::relayoutIsPrevented(); |
| bool scrollbarsAreFrozen = |
| m_inOverflowRelayout || FreezeScrollbarsScope::scrollbarsAreFrozen(); |
| |
| if (needsScrollbarReconstruction()) { |
| setHasHorizontalScrollbar(false); |
| setHasVerticalScrollbar(false); |
| } |
| |
| updateScrollDimensions(); |
| |
| bool hadHorizontalScrollbar = hasHorizontalScrollbar(); |
| bool hadVerticalScrollbar = hasVerticalScrollbar(); |
| |
| bool needsHorizontalScrollbar; |
| bool needsVerticalScrollbar; |
| computeScrollbarExistence(needsHorizontalScrollbar, needsVerticalScrollbar); |
| |
| bool horizontalScrollbarShouldChange = |
| needsHorizontalScrollbar != hadHorizontalScrollbar; |
| bool verticalScrollbarShouldChange = |
| needsVerticalScrollbar != hadVerticalScrollbar; |
| |
| bool scrollbarsWillChange = |
| !scrollbarsAreFrozen && |
| (horizontalScrollbarShouldChange || verticalScrollbarShouldChange); |
| if (scrollbarsWillChange) { |
| setHasHorizontalScrollbar(needsHorizontalScrollbar); |
| setHasVerticalScrollbar(needsVerticalScrollbar); |
| |
| if (hasScrollbar()) |
| updateScrollCornerStyle(); |
| |
| layer()->updateSelfPaintingLayer(); |
| |
| // Force an update since we know the scrollbars have changed things. |
| if (box().document().hasAnnotatedRegions()) |
| box().document().setAnnotatedRegionsDirty(true); |
| |
| // Our proprietary overflow: overlay value doesn't trigger a layout. |
| if ((horizontalScrollbarShouldChange && |
| box().style()->overflowX() != EOverflow::Overlay) || |
| (verticalScrollbarShouldChange && |
| box().style()->overflowY() != EOverflow::Overlay)) { |
| if ((verticalScrollbarShouldChange && box().isHorizontalWritingMode()) || |
| (horizontalScrollbarShouldChange && |
| !box().isHorizontalWritingMode())) { |
| box().setPreferredLogicalWidthsDirty(); |
| } |
| if (relayoutIsPrevented) { |
| // We're not doing re-layout right now, but we still want to |
| // add the scrollbar to the logical width now, to facilitate parent |
| // layout. |
| box().updateLogicalWidth(); |
| PreventRelayoutScope::setBoxNeedsLayout(*this, hadHorizontalScrollbar, |
| hadVerticalScrollbar); |
| } else { |
| m_inOverflowRelayout = true; |
| SubtreeLayoutScope layoutScope(box()); |
| layoutScope.setNeedsLayout(&box(), |
| LayoutInvalidationReason::ScrollbarChanged); |
| if (box().isLayoutBlock()) { |
| LayoutBlock& block = toLayoutBlock(box()); |
| block.scrollbarsChanged(horizontalScrollbarShouldChange, |
| verticalScrollbarShouldChange); |
| block.layoutBlock(true); |
| } else { |
| box().layout(); |
| } |
| m_inOverflowRelayout = false; |
| m_scrollbarManager.destroyDetachedScrollbars(); |
| } |
| LayoutObject* parent = box().parent(); |
| if (parent && parent->isFlexibleBox()) |
| toLayoutFlexibleBox(parent)->clearCachedMainSizeForChild(box()); |
| } |
| } |
| |
| { |
| // Hits in |
| // compositing/overflow/automatically-opt-into-composited-scrolling-after-style-change.html. |
| DisableCompositingQueryAsserts disabler; |
| |
| updateScrollbarEnabledState(); |
| |
| // Set up the range (and page step/line step). |
| if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { |
| int clientWidth = box().pixelSnappedClientWidth(); |
| horizontalScrollbar->setProportion(clientWidth, |
| overflowRect().width().toInt()); |
| } |
| if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) { |
| int clientHeight = box().pixelSnappedClientHeight(); |
| verticalScrollbar->setProportion(clientHeight, |
| overflowRect().height().toInt()); |
| } |
| } |
| |
| if (!scrollbarsAreFrozen && hasOverlayScrollbars()) { |
| if (!scrollSize(HorizontalScrollbar)) |
| setHasHorizontalScrollbar(false); |
| if (!scrollSize(VerticalScrollbar)) |
| setHasVerticalScrollbar(false); |
| } |
| |
| clampScrollOffsetAfterOverflowChange(); |
| |
| if (!scrollbarsAreFrozen) { |
| updateScrollableAreaSet(hasScrollableHorizontalOverflow() || |
| hasScrollableVerticalOverflow()); |
| } |
| |
| DisableCompositingQueryAsserts disabler; |
| positionOverflowControls(); |
| } |
| |
| void PaintLayerScrollableArea::clampScrollOffsetAfterOverflowChange() { |
| // 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. |
| |
| if (DelayScrollOffsetClampScope::clampingIsDelayed()) { |
| DelayScrollOffsetClampScope::setNeedsClamp(this); |
| return; |
| } |
| |
| if (scrollOriginChanged()) |
| setScrollOffsetUnconditionally(clampScrollOffset(getScrollOffset())); |
| else |
| ScrollableArea::setScrollOffset(getScrollOffset(), ClampingScroll); |
| |
| setNeedsScrollOffsetClamp(false); |
| resetScrollOriginChanged(); |
| m_scrollbarManager.destroyDetachedScrollbars(); |
| } |
| |
| void PaintLayerScrollableArea::didChangeGlobalRootScroller() { |
| // On Android, where the VisualViewport supplies scrollbars, we need to |
| // remove the PLSA's scrollbars. In general, this would be problematic as |
| // that can cause layout but this should only ever apply with overlay |
| // scrollbars. |
| if (!box().frame()->settings() || |
| !box().frame()->settings()->getViewportEnabled()) |
| return; |
| |
| bool needsHorizontalScrollbar; |
| bool needsVerticalScrollbar; |
| computeScrollbarExistence(needsHorizontalScrollbar, needsVerticalScrollbar); |
| setHasHorizontalScrollbar(needsHorizontalScrollbar); |
| setHasVerticalScrollbar(needsVerticalScrollbar); |
| } |
| |
| bool PaintLayerScrollableArea::shouldPerformScrollAnchoring() const { |
| return RuntimeEnabledFeatures::scrollAnchoringEnabled() && |
| m_scrollAnchor.hasScroller() && |
| layoutBox()->style()->overflowAnchor() != EOverflowAnchor::None && |
| !box().document().finishingOrIsPrinting(); |
| } |
| |
| FloatQuad PaintLayerScrollableArea::localToVisibleContentQuad( |
| const FloatQuad& quad, |
| const LayoutObject* localObject, |
| MapCoordinatesFlags flags) const { |
| LayoutBox* box = layoutBox(); |
| if (!box) |
| return quad; |
| DCHECK(localObject); |
| return localObject->localToAncestorQuad(quad, box, flags); |
| } |
| |
| ScrollBehavior PaintLayerScrollableArea::scrollBehaviorStyle() const { |
| return box().style()->getScrollBehavior(); |
| } |
| |
| bool PaintLayerScrollableArea::hasHorizontalOverflow() const { |
| // TODO(szager): Make the algorithm for adding/subtracting overflow:auto |
| // scrollbars memoryless (crbug.com/625300). This clientWidth 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. |
| int clientWidth = box().pixelSnappedClientWidth(); |
| if (needsRelayout() && !hadVerticalScrollbarBeforeRelayout()) |
| clientWidth += verticalScrollbarWidth(); |
| return pixelSnappedScrollWidth() > clientWidth; |
| } |
| |
| bool PaintLayerScrollableArea::hasVerticalOverflow() const { |
| return pixelSnappedScrollHeight() > box().pixelSnappedClientHeight(); |
| } |
| |
| bool PaintLayerScrollableArea::hasScrollableHorizontalOverflow() const { |
| return hasHorizontalOverflow() && box().scrollsOverflowX(); |
| } |
| |
| bool PaintLayerScrollableArea::hasScrollableVerticalOverflow() const { |
| return hasVerticalOverflow() && box().scrollsOverflowY(); |
| } |
| |
| // This function returns true if the given box requires overflow scrollbars (as |
| // opposed to the 'viewport' scrollbars managed by the PaintLayerCompositor). |
| // FIXME: we should use the same scrolling machinery for both the viewport and |
| // overflow. Currently, we need to avoid producing scrollbars here if they'll be |
| // handled externally in the RLC. |
| static bool canHaveOverflowScrollbars(const LayoutBox& box) { |
| return (RuntimeEnabledFeatures::rootLayerScrollingEnabled() || |
| !box.isLayoutView()) && |
| box.document().viewportDefiningElement() != box.node(); |
| } |
| |
| void PaintLayerScrollableArea::updateAfterStyleChange( |
| const ComputedStyle* oldStyle) { |
| // Don't do this on first style recalc, before layout has ever happened. |
| if (!overflowRect().size().isZero()) { |
| updateScrollableAreaSet(hasScrollableHorizontalOverflow() || |
| hasScrollableVerticalOverflow()); |
| } |
| |
| // Whenever background changes on the scrollable element, the scroll bar |
| // overlay style might need to be changed to have contrast against the |
| // background. |
| // Skip the need scrollbar check, because we dont know do we need a scrollbar |
| // when this method get called. |
| Color oldBackground; |
| if (oldStyle) { |
| oldBackground = oldStyle->visitedDependentColor(CSSPropertyBackgroundColor); |
| } |
| Color newBackground = |
| box().style()->visitedDependentColor(CSSPropertyBackgroundColor); |
| |
| if (newBackground != oldBackground) { |
| recalculateScrollbarOverlayColorTheme(newBackground); |
| } |
| |
| bool needsHorizontalScrollbar; |
| bool needsVerticalScrollbar; |
| // We add auto scrollbars only during layout to prevent spurious activations. |
| computeScrollbarExistence(needsHorizontalScrollbar, needsVerticalScrollbar, |
| ForbidAddingAutoBars); |
| |
| // Avoid some unnecessary computation if there were and will be no scrollbars. |
| if (!hasScrollbar() && !needsHorizontalScrollbar && !needsVerticalScrollbar) |
| return; |
| |
| bool horizontalScrollbarChanged = |
| setHasHorizontalScrollbar(needsHorizontalScrollbar); |
| bool verticalScrollbarChanged = |
| setHasVerticalScrollbar(needsVerticalScrollbar); |
| |
| if (box().isLayoutBlock() && |
| (horizontalScrollbarChanged || verticalScrollbarChanged)) { |
| toLayoutBlock(box()).scrollbarsChanged( |
| horizontalScrollbarChanged, verticalScrollbarChanged, |
| LayoutBlock::ScrollbarChangeContext::StyleChange); |
| } |
| |
| // With overflow: scroll, scrollbars are always visible but may be disabled. |
| // When switching to another value, we need to re-enable them (see bug 11985). |
| if (hasHorizontalScrollbar() && oldStyle && |
| oldStyle->overflowX() == EOverflow::Scroll && |
| box().style()->overflowX() != EOverflow::Scroll) { |
| horizontalScrollbar()->setEnabled(true); |
| } |
| |
| if (hasVerticalScrollbar() && oldStyle && |
| oldStyle->overflowY() == EOverflow::Scroll && |
| box().style()->overflowY() != EOverflow::Scroll) { |
| verticalScrollbar()->setEnabled(true); |
| } |
| |
| // FIXME: Need to detect a swap from custom to native scrollbars (and vice |
| // versa). |
| if (horizontalScrollbar()) |
| horizontalScrollbar()->styleChanged(); |
| if (verticalScrollbar()) |
| verticalScrollbar()->styleChanged(); |
| |
| updateScrollCornerStyle(); |
| updateResizerAreaSet(); |
| updateResizerStyle(); |
| } |
| |
| bool PaintLayerScrollableArea::updateAfterCompositingChange() { |
| layer()->updateScrollingStateAfterCompositingChange(); |
| const bool layersChanged = m_topmostScrollChild != m_nextTopmostScrollChild; |
| m_topmostScrollChild = m_nextTopmostScrollChild; |
| m_nextTopmostScrollChild = nullptr; |
| return layersChanged; |
| } |
| |
| void PaintLayerScrollableArea::updateAfterOverflowRecalc() { |
| updateScrollDimensions(); |
| if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) { |
| int clientWidth = box().pixelSnappedClientWidth(); |
| horizontalScrollbar->setProportion(clientWidth, |
| overflowRect().width().toInt()); |
| } |
| if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) { |
| int clientHeight = box().pixelSnappedClientHeight(); |
| verticalScrollbar->setProportion(clientHeight, |
| overflowRect().height().toInt()); |
| } |
| |
| bool needsHorizontalScrollbar; |
| bool needsVerticalScrollbar; |
| computeScrollbarExistence(needsHorizontalScrollbar, needsVerticalScrollbar); |
| |
| bool horizontalScrollbarShouldChange = |
| needsHorizontalScrollbar != hasHorizontalScrollbar(); |
| bool verticalScrollbarShouldChange = |
| needsVerticalScrollbar != hasVerticalScrollbar(); |
| |
| if ((box().hasAutoHorizontalScrollbar() && horizontalScrollbarShouldChange) || |
| (box().hasAutoVerticalScrollbar() && verticalScrollbarShouldChange)) { |
| box().setNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::Unknown); |
| } |
| |
| clampScrollOffsetAfterOverflowChange(); |
| } |
| |
| IntRect PaintLayerScrollableArea::rectForHorizontalScrollbar( |
| const IntRect& borderBoxRect) const { |
| if (!hasHorizontalScrollbar()) |
| return IntRect(); |
| |
| const IntRect& scrollCorner = scrollCornerRect(); |
| |
| return IntRect(horizontalScrollbarStart(borderBoxRect.x()), |
| borderBoxRect.maxY() - box().borderBottom() - |
| horizontalScrollbar()->scrollbarThickness(), |
| borderBoxRect.width() - |
| (box().borderLeft() + box().borderRight()) - |
| scrollCorner.width(), |
| horizontalScrollbar()->scrollbarThickness()); |
| } |
| |
| IntRect PaintLayerScrollableArea::rectForVerticalScrollbar( |
| const IntRect& borderBoxRect) const { |
| if (!hasVerticalScrollbar()) |
| return IntRect(); |
| |
| const IntRect& scrollCorner = scrollCornerRect(); |
| |
| return IntRect( |
| verticalScrollbarStart(borderBoxRect.x(), borderBoxRect.maxX()), |
| borderBoxRect.y() + box().borderTop(), |
| verticalScrollbar()->scrollbarThickness(), |
| borderBoxRect.height() - (box().borderTop() + box().borderBottom()) - |
| scrollCorner.height()); |
| } |
| |
| int PaintLayerScrollableArea::verticalScrollbarStart(int minX, int maxX) const { |
| if (box().shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| return minX + box().borderLeft(); |
| return maxX - box().borderRight() - verticalScrollbar()->scrollbarThickness(); |
| } |
| |
| int PaintLayerScrollableArea::horizontalScrollbarStart(int minX) const { |
| int x = minX + box().borderLeft(); |
| if (box().shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| x += hasVerticalScrollbar() |
| ? verticalScrollbar()->scrollbarThickness() |
| : resizerCornerRect(box().pixelSnappedBorderBoxRect(), |
| ResizerForPointer) |
| .width(); |
| return x; |
| } |
| |
| IntSize PaintLayerScrollableArea::scrollbarOffset( |
| const Scrollbar& scrollbar) const { |
| if (&scrollbar == verticalScrollbar()) |
| return IntSize(verticalScrollbarStart(0, box().size().width().toInt()), |
| box().borderTop()); |
| |
| if (&scrollbar == horizontalScrollbar()) |
| return IntSize( |
| horizontalScrollbarStart(0), |
| (box().size().height() - box().borderBottom() - scrollbar.height()) |
| .toInt()); |
| |
| ASSERT_NOT_REACHED(); |
| return IntSize(); |
| } |
| |
| static inline const LayoutObject& layoutObjectForScrollbar( |
| const LayoutObject& layoutObject) { |
| if (Node* node = layoutObject.node()) { |
| if (layoutObject.isLayoutView()) { |
| Document& doc = node->document(); |
| if (Settings* settings = doc.settings()) { |
| if (!settings->getAllowCustomScrollbarInMainFrame() && |
| layoutObject.frame() && layoutObject.frame()->isMainFrame()) |
| return layoutObject; |
| } |
| |
| // Try the <body> element first as a scrollbar source. |
| Element* body = doc.body(); |
| if (body && body->layoutObject() && |
| body->layoutObject()->style()->hasPseudoStyle(PseudoIdScrollbar)) |
| return *body->layoutObject(); |
| |
| // If the <body> didn't have a custom style, then the root element might. |
| Element* docElement = doc.documentElement(); |
| if (docElement && docElement->layoutObject() && |
| docElement->layoutObject()->style()->hasPseudoStyle( |
| PseudoIdScrollbar)) |
| return *docElement->layoutObject(); |
| } |
| |
| if (layoutObject.styleRef().hasPseudoStyle(PseudoIdScrollbar)) |
| return layoutObject; |
| |
| if (ShadowRoot* shadowRoot = node->containingShadowRoot()) { |
| if (shadowRoot->type() == ShadowRootType::UserAgent) |
| return *shadowRoot->host().layoutObject(); |
| } |
| } |
| |
| return layoutObject; |
| } |
| |
| bool PaintLayerScrollableArea::needsScrollbarReconstruction() const { |
| const LayoutObject& actualLayoutObject = layoutObjectForScrollbar(box()); |
| bool shouldUseCustom = |
| actualLayoutObject.isBox() && |
| actualLayoutObject.styleRef().hasPseudoStyle(PseudoIdScrollbar); |
| bool hasAnyScrollbar = hasScrollbar(); |
| bool hasCustom = |
| (hasHorizontalScrollbar() && |
| horizontalScrollbar()->isCustomScrollbar()) || |
| (hasVerticalScrollbar() && verticalScrollbar()->isCustomScrollbar()); |
| bool didCustomScrollbarOwnerChanged = false; |
| |
| if (hasHorizontalScrollbar() && horizontalScrollbar()->isCustomScrollbar()) { |
| if (actualLayoutObject != |
| toLayoutScrollbar(horizontalScrollbar())->owningLayoutObject()) |
| didCustomScrollbarOwnerChanged = true; |
| } |
| |
| if (hasVerticalScrollbar() && verticalScrollbar()->isCustomScrollbar()) { |
| if (actualLayoutObject != |
| toLayoutScrollbar(verticalScrollbar())->owningLayoutObject()) |
| didCustomScrollbarOwnerChanged = true; |
| } |
| |
| return hasAnyScrollbar && |
| ((shouldUseCustom != hasCustom) || |
| (shouldUseCustom && didCustomScrollbarOwnerChanged)); |
| } |
| |
| void PaintLayerScrollableArea::computeScrollbarExistence( |
| bool& needsHorizontalScrollbar, |
| bool& needsVerticalScrollbar, |
| ComputeScrollbarExistenceOption option) const { |
| // Scrollbars may be hidden or provided by visual viewport or frame instead. |
| DCHECK(box().frame()->settings()); |
| if (visualViewportSuppliesScrollbars() || !canHaveOverflowScrollbars(box()) || |
| box().frame()->settings()->getHideScrollbars()) { |
| needsHorizontalScrollbar = false; |
| needsVerticalScrollbar = false; |
| return; |
| } |
| |
| needsHorizontalScrollbar = box().scrollsOverflowX(); |
| needsVerticalScrollbar = box().scrollsOverflowY(); |
| |
| // Don't add auto scrollbars if the box contents aren't visible. |
| if (box().hasAutoHorizontalScrollbar()) { |
| if (option == ForbidAddingAutoBars) |
| needsHorizontalScrollbar &= hasHorizontalScrollbar(); |
| needsHorizontalScrollbar &= box().isRooted() && |
| this->hasHorizontalOverflow() && |
| box().pixelSnappedClientHeight(); |
| } |
| |
| if (box().hasAutoVerticalScrollbar()) { |
| if (option == ForbidAddingAutoBars) |
| needsVerticalScrollbar &= hasVerticalScrollbar(); |
| needsVerticalScrollbar &= box().isRooted() && this->hasVerticalOverflow() && |
| box().pixelSnappedClientWidth(); |
| } |
| |
| // Look for the scrollbarModes and reset the needs Horizontal & vertical |
| // Scrollbar values based on scrollbarModes, as during force style change |
| // StyleResolver::styleForDocument returns documentStyle with no overflow |
| // values, due to which we are destroying the scrollbars that were already |
| // present. |
| if (box().isLayoutView()) { |
| if (LocalFrame* frame = box().frame()) { |
| if (FrameView* frameView = frame->view()) { |
| ScrollbarMode hMode; |
| ScrollbarMode vMode; |
| frameView->calculateScrollbarModes(hMode, vMode); |
| if (hMode == ScrollbarAlwaysOn) |
| needsHorizontalScrollbar = true; |
| if (vMode == ScrollbarAlwaysOn) |
| needsVerticalScrollbar = true; |
| } |
| } |
| } |
| } |
| |
| bool PaintLayerScrollableArea::setHasHorizontalScrollbar(bool hasScrollbar) { |
| if (FreezeScrollbarsScope::scrollbarsAreFrozen()) |
| return false; |
| |
| if (hasScrollbar == hasHorizontalScrollbar()) |
| return false; |
| |
| setScrollbarNeedsPaintInvalidation(HorizontalScrollbar); |
| |
| m_scrollbarManager.setHasHorizontalScrollbar(hasScrollbar); |
| |
| 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 (box().document().hasAnnotatedRegions()) |
| box().document().setAnnotatedRegionsDirty(true); |
| return true; |
| } |
| |
| bool PaintLayerScrollableArea::setHasVerticalScrollbar(bool hasScrollbar) { |
| if (FreezeScrollbarsScope::scrollbarsAreFrozen()) |
| return false; |
| |
| if (hasScrollbar == hasVerticalScrollbar()) |
| return false; |
| |
| setScrollbarNeedsPaintInvalidation(VerticalScrollbar); |
| |
| m_scrollbarManager.setHasVerticalScrollbar(hasScrollbar); |
| |
| 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 (box().document().hasAnnotatedRegions()) |
| box().document().setAnnotatedRegionsDirty(true); |
| return true; |
| } |
| |
| int PaintLayerScrollableArea::verticalScrollbarWidth( |
| OverlayScrollbarClipBehavior overlayScrollbarClipBehavior) const { |
| if (!hasVerticalScrollbar()) |
| return 0; |
| if (verticalScrollbar()->isOverlayScrollbar() && |
| (overlayScrollbarClipBehavior == IgnoreOverlayScrollbarSize || |
| !verticalScrollbar()->shouldParticipateInHitTesting())) |
| return 0; |
| return verticalScrollbar()->scrollbarThickness(); |
| } |
| |
| int PaintLayerScrollableArea::horizontalScrollbarHeight( |
| OverlayScrollbarClipBehavior overlayScrollbarClipBehavior) const { |
| if (!hasHorizontalScrollbar()) |
| return 0; |
| if (horizontalScrollbar()->isOverlayScrollbar() && |
| (overlayScrollbarClipBehavior == IgnoreOverlayScrollbarSize || |
| !horizontalScrollbar()->shouldParticipateInHitTesting())) |
| return 0; |
| return horizontalScrollbar()->scrollbarThickness(); |
| } |
| |
| void PaintLayerScrollableArea::positionOverflowControls() { |
| if (!hasScrollbar() && !box().canResize()) |
| return; |
| |
| const IntRect borderBox = box().pixelSnappedBorderBoxRect(); |
| if (Scrollbar* verticalScrollbar = this->verticalScrollbar()) |
| verticalScrollbar->setFrameRect(rectForVerticalScrollbar(borderBox)); |
| |
| if (Scrollbar* horizontalScrollbar = this->horizontalScrollbar()) |
| horizontalScrollbar->setFrameRect(rectForHorizontalScrollbar(borderBox)); |
| |
| const IntRect& scrollCorner = scrollCornerRect(); |
| if (m_scrollCorner) |
| m_scrollCorner->setFrameRect(LayoutRect(scrollCorner)); |
| |
| if (m_resizer) |
| m_resizer->setFrameRect( |
| LayoutRect(resizerCornerRect(borderBox, ResizerForPointer))); |
| |
| // FIXME, this should eventually be removed, once we are certain that |
| // composited controls get correctly positioned on a compositor update. For |
| // now, conservatively leaving this unchanged. |
| if (layer()->hasCompositedLayerMapping()) |
| layer()->compositedLayerMapping()->positionOverflowControlsLayers(); |
| } |
| |
| void PaintLayerScrollableArea::updateScrollCornerStyle() { |
| if (!m_scrollCorner && !hasScrollbar()) |
| return; |
| if (!m_scrollCorner && hasOverlayScrollbars()) |
| return; |
| |
| const LayoutObject& actualLayoutObject = layoutObjectForScrollbar(box()); |
| RefPtr<ComputedStyle> corner = |
| box().hasOverflowClip() |
| ? actualLayoutObject.getUncachedPseudoStyle( |
| PseudoStyleRequest(PseudoIdScrollbarCorner), |
| actualLayoutObject.style()) |
| : PassRefPtr<ComputedStyle>(nullptr); |
| if (corner) { |
| if (!m_scrollCorner) { |
| m_scrollCorner = |
| LayoutScrollbarPart::createAnonymous(&box().document(), this); |
| m_scrollCorner->setDangerousOneWayParent(&box()); |
| } |
| m_scrollCorner->setStyleWithWritingModeOfParent(std::move(corner)); |
| } else if (m_scrollCorner) { |
| m_scrollCorner->destroy(); |
| m_scrollCorner = nullptr; |
| } |
| } |
| |
| bool PaintLayerScrollableArea::hitTestOverflowControls( |
| HitTestResult& result, |
| const IntPoint& localPoint) { |
| if (!hasScrollbar() && !box().canResize()) |
| return false; |
| |
| IntRect resizeControlRect; |
| if (box().style()->resize() != RESIZE_NONE) { |
| resizeControlRect = |
| resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer); |
| if (resizeControlRect.contains(localPoint)) |
| return true; |
| } |
| |
| int resizeControlSize = max(resizeControlRect.height(), 0); |
| if (hasVerticalScrollbar() && |
| verticalScrollbar()->shouldParticipateInHitTesting()) { |
| LayoutRect vBarRect(verticalScrollbarStart(0, box().size().width().toInt()), |
| box().borderTop(), |
| verticalScrollbar()->scrollbarThickness(), |
| box().size().height().toInt() - |
| (box().borderTop() + box().borderBottom()) - |
| (hasHorizontalScrollbar() |
| ? horizontalScrollbar()->scrollbarThickness() |
| : resizeControlSize)); |
| if (vBarRect.contains(localPoint)) { |
| result.setScrollbar(verticalScrollbar()); |
| return true; |
| } |
| } |
| |
| resizeControlSize = max(resizeControlRect.width(), 0); |
| if (hasHorizontalScrollbar() && |
| horizontalScrollbar()->shouldParticipateInHitTesting()) { |
| // TODO(crbug.com/638981): Are the conversions to int intentional? |
| LayoutRect hBarRect( |
| horizontalScrollbarStart(0), |
| (box().size().height() - box().borderBottom() - |
| horizontalScrollbar()->scrollbarThickness()) |
| .toInt(), |
| (box().size().width() - (box().borderLeft() + box().borderRight()) - |
| (hasVerticalScrollbar() ? verticalScrollbar()->scrollbarThickness() |
| : resizeControlSize)) |
| .toInt(), |
| horizontalScrollbar()->scrollbarThickness()); |
| if (hBarRect.contains(localPoint)) { |
| result.setScrollbar(horizontalScrollbar()); |
| return true; |
| } |
| } |
| |
| // FIXME: We should hit test the m_scrollCorner and pass it back through the |
| // result. |
| |
| return false; |
| } |
| |
| IntRect PaintLayerScrollableArea::resizerCornerRect( |
| const IntRect& bounds, |
| ResizerHitTestType resizerHitTestType) const { |
| if (box().style()->resize() == RESIZE_NONE) |
| return IntRect(); |
| IntRect corner = |
| cornerRect(box(), horizontalScrollbar(), verticalScrollbar(), bounds); |
| |
| if (resizerHitTestType == ResizerForTouch) { |
| // 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. |
| int expandRatio = ResizerControlExpandRatioForTouch - 1; |
| corner.move(-corner.width() * expandRatio, -corner.height() * expandRatio); |
| corner.expand(corner.width() * expandRatio, corner.height() * expandRatio); |
| } |
| |
| return corner; |
| } |
| |
| IntRect PaintLayerScrollableArea::scrollCornerAndResizerRect() const { |
| IntRect scrollCornerAndResizer = scrollCornerRect(); |
| if (scrollCornerAndResizer.isEmpty()) |
| scrollCornerAndResizer = |
| resizerCornerRect(box().pixelSnappedBorderBoxRect(), ResizerForPointer); |
| return scrollCornerAndResizer; |
| } |
| |
| bool PaintLayerScrollableArea::isPointInResizeControl( |
| const IntPoint& absolutePoint, |
| ResizerHitTestType resizerHitTestType) const { |
| if (!box().canResize()) |
| return false; |
| |
| IntPoint localPoint = |
| roundedIntPoint(box().absoluteToLocal(absolutePoint, UseTransforms)); |
| IntRect localBounds(0, 0, box().pixelSnappedWidth(), |
| box().pixelSnappedHeight()); |
| return resizerCornerRect(localBounds, resizerHitTestType) |
| .contains(localPoint); |
| } |
| |
| bool PaintLayerScrollableArea::hitTestResizerInFragments( |
| const PaintLayerFragments& layerFragments, |
| const HitTestLocation& hitTestLocation) const { |
| if (!box().canResize()) |
| return false; |
| |
| if (layerFragments.isEmpty()) |
| return false; |
| |
| for (int i = layerFragments.size() - 1; i >= 0; --i) { |
| const PaintLayerFragment& fragment = layerFragments.at(i); |
| if (fragment.backgroundRect.intersects(hitTestLocation) && |
| resizerCornerRect(pixelSnappedIntRect(fragment.layerBounds), |
| ResizerForPointer) |
| .contains(hitTestLocation.roundedPoint())) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void PaintLayerScrollableArea::updateResizerAreaSet() { |
| LocalFrame* frame = box().frame(); |
| if (!frame) |
| return; |
| FrameView* frameView = frame->view(); |
| if (!frameView) |
| return; |
| if (box().canResize()) |
| frameView->addResizerArea(box()); |
| else |
| frameView->removeResizerArea(box()); |
| } |
| |
| void PaintLayerScrollableArea::updateResizerStyle() { |
| if (!m_resizer && !box().canResize()) |
| return; |
| |
| const LayoutObject& actualLayoutObject = layoutObjectForScrollbar(box()); |
| RefPtr<ComputedStyle> resizer = |
| box().hasOverflowClip() |
| ? actualLayoutObject.getUncachedPseudoStyle( |
| PseudoStyleRequest(PseudoIdResizer), actualLayoutObject.style()) |
| : PassRefPtr<ComputedStyle>(nullptr); |
| if (resizer) { |
| if (!m_resizer) { |
| m_resizer = LayoutScrollbarPart::createAnonymous(&box().document(), this); |
| m_resizer->setDangerousOneWayParent(&box()); |
| } |
| m_resizer->setStyleWithWritingModeOfParent(std::move(resizer)); |
| } else if (m_resizer) { |
| m_resizer->destroy(); |
| m_resizer = nullptr; |
| } |
| } |
| |
| void PaintLayerScrollableArea::invalidateAllStickyConstraints() { |
| if (PaintLayerScrollableAreaRareData* d = rareData()) { |
| for (PaintLayer* stickyLayer : d->m_stickyConstraintsMap.keys()) { |
| if (stickyLayer->layoutObject()->style()->position() == StickyPosition) |
| stickyLayer->setNeedsCompositingInputsUpdate(); |
| } |
| d->m_stickyConstraintsMap.clear(); |
| } |
| } |
| |
| void PaintLayerScrollableArea::invalidateStickyConstraintsFor( |
| PaintLayer* layer, |
| bool needsCompositingUpdate) { |
| if (PaintLayerScrollableAreaRareData* d = rareData()) { |
| d->m_stickyConstraintsMap.remove(layer); |
| if (needsCompositingUpdate && |
| layer->layoutObject()->style()->position() == StickyPosition) |
| layer->setNeedsCompositingInputsUpdate(); |
| } |
| } |
| |
| IntSize PaintLayerScrollableArea::offsetFromResizeCorner( |
| const IntPoint& absolutePoint) 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? |
| IntSize elementSize = layer()->size(); |
| if (box().shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| elementSize.setWidth(0); |
| IntPoint resizerPoint = IntPoint(elementSize); |
| IntPoint localPoint = |
| roundedIntPoint(box().absoluteToLocal(absolutePoint, UseTransforms)); |
| return localPoint - resizerPoint; |
| } |
| |
| void PaintLayerScrollableArea::resize(const IntPoint& pos, |
| const LayoutSize& oldOffset) { |
| // FIXME: This should be possible on generated content but is not right now. |
| if (!inResizeMode() || !box().canResize() || !box().node()) |
| return; |
| |
| DCHECK(box().node()->isElementNode()); |
| Element* element = toElement(box().node()); |
| |
| Document& document = element->document(); |
| |
| float zoomFactor = box().style()->effectiveZoom(); |
| |
| IntSize newOffset = |
| offsetFromResizeCorner(document.view()->rootFrameToContents(pos)); |
| newOffset.setWidth(newOffset.width() / zoomFactor); |
| newOffset.setHeight(newOffset.height() / zoomFactor); |
| |
| LayoutSize currentSize = box().size(); |
| currentSize.scale(1 / zoomFactor); |
| LayoutSize minimumSize = |
| element->minimumSizeForResizing().shrunkTo(currentSize); |
| element->setMinimumSizeForResizing(minimumSize); |
| |
| LayoutSize adjustedOldOffset = LayoutSize(oldOffset.width() / zoomFactor, |
| oldOffset.height() / zoomFactor); |
| if (box().shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { |
| newOffset.setWidth(-newOffset.width()); |
| adjustedOldOffset.setWidth(-adjustedOldOffset.width()); |
| } |
| |
| LayoutSize difference( |
| (currentSize + newOffset - adjustedOldOffset).expandedTo(minimumSize) - |
| currentSize); |
| |
| bool isBoxSizingBorder = box().style()->boxSizing() == EBoxSizing::kBorderBox; |
| |
| EResize resize = box().style()->resize(); |
| if (resize != RESIZE_VERTICAL && difference.width()) { |
| if (element->isFormControlElement()) { |
| // Make implicit margins from the theme explicit (see |
| // <http://bugs.webkit.org/show_bug.cgi?id=9547>). |
| element->setInlineStyleProperty(CSSPropertyMarginLeft, |
| box().marginLeft() / zoomFactor, |
| CSSPrimitiveValue::UnitType::Pixels); |
| element->setInlineStyleProperty(CSSPropertyMarginRight, |
| box().marginRight() / zoomFactor, |
| CSSPrimitiveValue::UnitType::Pixels); |
| } |
| LayoutUnit baseWidth = |
| box().size().width() - |
| (isBoxSizingBorder ? LayoutUnit() : box().borderAndPaddingWidth()); |
| baseWidth = LayoutUnit(baseWidth / zoomFactor); |
| element->setInlineStyleProperty(CSSPropertyWidth, |
| roundToInt(baseWidth + difference.width()), |
| CSSPrimitiveValue::UnitType::Pixels); |
| } |
| |
| if (resize != RESIZE_HORIZONTAL && difference.height()) { |
| if (element->isFormControlElement()) { |
| // Make implicit margins from the theme explicit (see |
| // <http://bugs.webkit.org/show_bug.cgi?id=9547>). |
| element->setInlineStyleProperty(CSSPropertyMarginTop, |
| box().marginTop() / zoomFactor, |
| CSSPrimitiveValue::UnitType::Pixels); |
| element->setInlineStyleProperty(CSSPropertyMarginBottom, |
| box().marginBottom() / zoomFactor, |
| CSSPrimitiveValue::UnitType::Pixels); |
| } |
| LayoutUnit baseHeight = |
| box().size().height() - |
| (isBoxSizingBorder ? LayoutUnit() : box().borderAndPaddingHeight()); |
| baseHeight = LayoutUnit(baseHeight / zoomFactor); |
| element->setInlineStyleProperty( |
| CSSPropertyHeight, roundToInt(baseHeight + difference.height()), |
| CSSPrimitiveValue::UnitType::Pixels); |
| } |
| |
| document.updateStyleAndLayout(); |
| |
| // FIXME (Radar 4118564): We should also autoscroll the window as necessary to |
| // keep the point under the cursor in view. |
| } |
| |
| LayoutRect PaintLayerScrollableArea::scrollIntoView( |
| const LayoutRect& rect, |
| const ScrollAlignment& alignX, |
| const ScrollAlignment& alignY, |
| ScrollType scrollType) { |
| LayoutRect localExposeRect( |
| box() |
| .absoluteToLocalQuad(FloatQuad(FloatRect(rect)), UseTransforms) |
| .boundingBox()); |
| localExposeRect.move(-box().borderLeft(), -box().borderTop()); |
| LayoutRect layerBounds(LayoutPoint(), |
| LayoutSize(box().clientWidth(), box().clientHeight())); |
| LayoutRect r = ScrollAlignment::getRectToExpose(layerBounds, localExposeRect, |
| alignX, alignY); |
| |
| ScrollOffset oldScrollOffset = getScrollOffset(); |
| ScrollOffset newScrollOffset(clampScrollOffset(roundedIntSize( |
| toScrollOffset(FloatPoint(r.location()) + oldScrollOffset)))); |
| setScrollOffset(newScrollOffset, scrollType, ScrollBehaviorInstant); |
| ScrollOffset scrollOffsetDifference = getScrollOffset() - oldScrollOffset; |
| localExposeRect.move(-LayoutSize(scrollOffsetDifference)); |
| |
| LayoutRect intersect = |
| localToAbsolute(box(), intersection(layerBounds, localExposeRect)); |
| if (intersect.isEmpty() && !layerBounds.isEmpty() && |
| !localExposeRect.isEmpty()) { |
| return localToAbsolute(box(), localExposeRect); |
| } |
| return intersect; |
| } |
| |
| void PaintLayerScrollableArea::updateScrollableAreaSet(bool hasOverflow) { |
| LocalFrame* frame = box().frame(); |
| if (!frame) |
| return; |
| |
| FrameView* frameView = frame->view(); |
| if (!frameView) |
| return; |
| |
| // FIXME: Does this need to be fixed later for OOPI? |
| bool isVisibleToHitTest = box().style()->visibleToHitTesting(); |
| if (HTMLFrameOwnerElement* owner = frame->deprecatedLocalOwner()) { |
| isVisibleToHitTest &= owner->layoutObject() && |
| owner->layoutObject()->style()->visibleToHitTesting(); |
| } |
| |
| bool didScrollOverflow = m_scrollsOverflow; |
| |
| m_scrollsOverflow = hasOverflow && isVisibleToHitTest; |
| if (didScrollOverflow == scrollsOverflow()) |
| return; |
| |
| if (RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()) { |
| // The scroll and scroll offset properties depend on |scrollsOverflow| (see: |
| // PaintPropertyTreeBuilder::updateScrollAndScrollTranslation). |
| box().setNeedsPaintPropertyUpdate(); |
| } |
| |
| if (m_scrollsOverflow) { |
| DCHECK(canHaveOverflowScrollbars(box())); |
| frameView->addScrollableArea(this); |
| } else { |
| frameView->removeScrollableArea(this); |
| } |
| } |
| |
| void PaintLayerScrollableArea::updateCompositingLayersAfterScroll() { |
| PaintLayerCompositor* compositor = box().view()->compositor(); |
| if (compositor->inCompositingMode()) { |
| if (usesCompositedScrolling()) { |
| DCHECK(layer()->hasCompositedLayerMapping()); |
| layer()->compositedLayerMapping()->setNeedsGraphicsLayerUpdate( |
| GraphicsLayerUpdateSubtree); |
| compositor->setNeedsCompositingUpdate( |
| CompositingUpdateAfterGeometryChange); |
| } else { |
| layer()->setNeedsCompositingInputsUpdate(); |
| } |
| } |
| } |
| |
| ScrollingCoordinator* PaintLayerScrollableArea::getScrollingCoordinator() |
| const { |
| LocalFrame* frame = box().frame(); |
| if (!frame) |
| return nullptr; |
| |
| Page* page = frame->page(); |
| if (!page) |
| return nullptr; |
| |
| return page->scrollingCoordinator(); |
| } |
| |
| bool PaintLayerScrollableArea::usesCompositedScrolling() const { |
| // See https://codereview.chromium.org/176633003/ for the tests that fail |
| // without this disabler. |
| DisableCompositingQueryAsserts disabler; |
| return layer()->hasCompositedLayerMapping() && |
| layer()->compositedLayerMapping()->scrollingLayer(); |
| } |
| |
| bool PaintLayerScrollableArea::shouldScrollOnMainThread() const { |
| if (LocalFrame* frame = box().frame()) { |
| if (frame->view()->mainThreadScrollingReasons()) |
| return true; |
| } |
| return ScrollableArea::shouldScrollOnMainThread(); |
| } |
| |
| bool PaintLayerScrollableArea::computeNeedsCompositedScrolling( |
| const LCDTextMode mode, |
| const PaintLayer* layer) { |
| if (!layer->scrollsOverflow()) |
| return false; |
| |
| Node* node = layer->enclosingNode(); |
| if (node && node->isElementNode() && |
| (toElement(node)->compositorMutableProperties() & |
| (CompositorMutableProperty::kScrollTop | |
| CompositorMutableProperty::kScrollLeft))) |
| return true; |
| |
| if (layer->size().isEmpty()) |
| return false; |
| |
| bool needsCompositedScrolling = true; |
| |
| // TODO(flackr): Allow integer transforms as long as all of the ancestor |
| // transforms are also integer. |
| bool backgroundSupportsLCDText = |
| RuntimeEnabledFeatures::compositeOpaqueScrollersEnabled() && |
| layer->layoutObject()->style()->isStackingContext() && |
| layer->backgroundPaintLocation() & BackgroundPaintInScrollingContents && |
| layer->backgroundIsKnownToBeOpaqueInRect( |
| toLayoutBox(layer->layoutObject())->paddingBoxRect()) && |
| !layer->compositesWithTransform() && !layer->compositesWithOpacity(); |
| |
| if (mode == PaintLayerScrollableArea::ConsiderLCDText && |
| !layer->compositor()->preferCompositingToLCDTextEnabled() && |
| !backgroundSupportsLCDText) { |
| if (layer->compositesWithOpacity()) { |
| addStyleRelatedMainThreadScrollingReasons( |
| MainThreadScrollingReason::kHasOpacityAndLCDText); |
| } |
| if (layer->compositesWithTransform()) { |
| addStyleRelatedMainThreadScrollingReasons( |
| MainThreadScrollingReason::kHasTransformAndLCDText); |
| } |
| if (!layer->backgroundIsKnownToBeOpaqueInRect( |
| toLayoutBox(layer->layoutObject())->paddingBoxRect())) { |
| addStyleRelatedMainThreadScrollingReasons( |
| MainThreadScrollingReason::kBackgroundNotOpaqueInRectAndLCDText); |
| } |
| needsCompositedScrolling = false; |
| } |
| |
| // TODO(schenney) Tests fail if we do not also exclude |
| // layer->layoutObject()->style()->hasBorderDecoration() (missing background |
| // behind dashed borders). Resolve this case, or not, and update this check |
| // with the results. |
| if (layer->layoutObject()->style()->hasBorderRadius()) { |
| addStyleRelatedMainThreadScrollingReasons( |
| MainThreadScrollingReason::kHasBorderRadius); |
| needsCompositedScrolling = false; |
| } |
| if (layer->layoutObject()->hasClip() || layer->hasDescendantWithClipPath() || |
| layer->hasAncestorWithClipPath()) { |
| addStyleRelatedMainThreadScrollingReasons( |
| MainThreadScrollingReason::kHasClipRelatedProperty); |
| needsCompositedScrolling = false; |
| } |
| return needsCompositedScrolling; |
| } |
| |
| void PaintLayerScrollableArea::addStyleRelatedMainThreadScrollingReasons( |
| const uint32_t reason) { |
| LocalFrame* frame = box().frame(); |
| if (!frame) |
| return; |
| FrameView* frameView = frame->view(); |
| if (!frameView) |
| return; |
| |
| frameView->adjustStyleRelatedMainThreadScrollingReasons(reason, true); |
| m_reasons |= reason; |
| } |
| |
| void PaintLayerScrollableArea::removeStyleRelatedMainThreadScrollingReasons() { |
| LocalFrame* frame = box().frame(); |
| if (!frame) |
| return; |
| FrameView* frameView = frame->view(); |
| if (!frameView) |
| return; |
| |
| // Decrese the number of layers that have any main thread |
| // scrolling reasons stored in FrameView |
| for (uint32_t i = 0; |
| i < MainThreadScrollingReason::kMainThreadScrollingReasonCount; ++i) { |
| uint32_t reason = 1 << i; |
| if (hasMainThreadScrollingReason(reason)) { |
| m_reasons &= ~reason; |
| frameView->adjustStyleRelatedMainThreadScrollingReasons(reason, false); |
| } |
| } |
| } |
| |
| void PaintLayerScrollableArea::updateNeedsCompositedScrolling( |
| LCDTextMode mode) { |
| // Clear all style related main thread scrolling reasons, if any, |
| // before calling computeNeedsCompositedScrolling |
| removeStyleRelatedMainThreadScrollingReasons(); |
| const bool needsCompositedScrolling = |
| computeNeedsCompositedScrolling(mode, layer()); |
| |
| if (static_cast<bool>(m_needsCompositedScrolling) != |
| needsCompositedScrolling) { |
| m_needsCompositedScrolling = needsCompositedScrolling; |
| layer()->didUpdateNeedsCompositedScrolling(); |
| } |
| } |
| |
| void PaintLayerScrollableArea::setTopmostScrollChild(PaintLayer* scrollChild) { |
| // We only want to track the topmost scroll child for scrollable areas with |
| // overlay scrollbars. |
| if (!hasOverlayScrollbars()) |
| return; |
| m_nextTopmostScrollChild = scrollChild; |
| } |
| |
| bool PaintLayerScrollableArea::visualViewportSuppliesScrollbars() const { |
| LocalFrame* frame = box().frame(); |
| if (!frame || !frame->settings()) |
| return false; |
| |
| // On desktop, we always use the layout viewport's scrollbars. |
| if (!frame->settings()->getViewportEnabled()) |
| return false; |
| |
| const TopDocumentRootScrollerController& controller = |
| layoutBox()->document().frameHost()->globalRootScrollerController(); |
| |
| return RootScrollerUtil::scrollableAreaForRootScroller( |
| controller.globalRootScroller()) == this; |
| } |
| |
| Widget* PaintLayerScrollableArea::getWidget() { |
| return box().frame()->view(); |
| } |
| |
| void PaintLayerScrollableArea::resetRebuildScrollbarLayerFlags() { |
| m_rebuildHorizontalScrollbarLayer = false; |
| m_rebuildVerticalScrollbarLayer = false; |
| } |
| |
| CompositorAnimationHost* PaintLayerScrollableArea::compositorAnimationHost() |
| const { |
| if (ScrollingCoordinator* coordinator = getScrollingCoordinator()) |
| return coordinator->compositorAnimationHost(); |
| |
| return nullptr; |
| } |
| |
| CompositorAnimationTimeline* |
| PaintLayerScrollableArea::compositorAnimationTimeline() const { |
| if (ScrollingCoordinator* coordinator = getScrollingCoordinator()) |
| return coordinator->compositorAnimationTimeline(); |
| |
| return nullptr; |
| } |
| |
| PaintLayerScrollableArea* |
| PaintLayerScrollableArea::ScrollbarManager::scrollableArea() { |
| return toPaintLayerScrollableArea(m_scrollableArea.get()); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::destroyDetachedScrollbars() { |
| DCHECK(!m_hBarIsAttached || m_hBar); |
| DCHECK(!m_vBarIsAttached || m_vBar); |
| if (m_hBar && !m_hBarIsAttached) |
| destroyScrollbar(HorizontalScrollbar); |
| if (m_vBar && !m_vBarIsAttached) |
| destroyScrollbar(VerticalScrollbar); |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::setHasHorizontalScrollbar( |
| bool hasScrollbar) { |
| if (hasScrollbar) { |
| // This doesn't hit in any tests, but since the equivalent code in |
| // setHasVerticalScrollbar does, presumably this code does as well. |
| DisableCompositingQueryAsserts disabler; |
| if (!m_hBar) { |
| m_hBar = createScrollbar(HorizontalScrollbar); |
| m_hBarIsAttached = 1; |
| if (!m_hBar->isCustomScrollbar()) |
| scrollableArea()->didAddScrollbar(*m_hBar, HorizontalScrollbar); |
| } else { |
| m_hBarIsAttached = 1; |
| } |
| } else { |
| m_hBarIsAttached = 0; |
| if (!DelayScrollOffsetClampScope::clampingIsDelayed()) |
| destroyScrollbar(HorizontalScrollbar); |
| } |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::setHasVerticalScrollbar( |
| bool hasScrollbar) { |
| if (hasScrollbar) { |
| DisableCompositingQueryAsserts disabler; |
| if (!m_vBar) { |
| m_vBar = createScrollbar(VerticalScrollbar); |
| m_vBarIsAttached = 1; |
| if (!m_vBar->isCustomScrollbar()) |
| scrollableArea()->didAddScrollbar(*m_vBar, VerticalScrollbar); |
| } else { |
| m_vBarIsAttached = 1; |
| } |
| } else { |
| m_vBarIsAttached = 0; |
| if (!DelayScrollOffsetClampScope::clampingIsDelayed()) |
| destroyScrollbar(VerticalScrollbar); |
| } |
| } |
| |
| Scrollbar* PaintLayerScrollableArea::ScrollbarManager::createScrollbar( |
| ScrollbarOrientation orientation) { |
| DCHECK(orientation == HorizontalScrollbar ? !m_hBarIsAttached |
| : !m_vBarIsAttached); |
| Scrollbar* scrollbar = nullptr; |
| const LayoutObject& actualLayoutObject = |
| layoutObjectForScrollbar(scrollableArea()->box()); |
| bool hasCustomScrollbarStyle = |
| actualLayoutObject.isBox() && |
| actualLayoutObject.styleRef().hasPseudoStyle(PseudoIdScrollbar); |
| if (hasCustomScrollbarStyle) { |
| scrollbar = LayoutScrollbar::createCustomScrollbar( |
| scrollableArea(), orientation, actualLayoutObject.node()); |
| } else { |
| ScrollbarControlSize scrollbarSize = RegularScrollbar; |
| if (actualLayoutObject.styleRef().hasAppearance()) |
| scrollbarSize = LayoutTheme::theme().scrollbarControlSizeForPart( |
| actualLayoutObject.styleRef().appearance()); |
| scrollbar = Scrollbar::create( |
| scrollableArea(), orientation, scrollbarSize, |
| &scrollableArea()->box().frame()->page()->chromeClient()); |
| } |
| scrollableArea()->box().document().view()->addChild(scrollbar); |
| return scrollbar; |
| } |
| |
| void PaintLayerScrollableArea::ScrollbarManager::destroyScrollbar( |
| ScrollbarOrientation orientation) { |
| Member<Scrollbar>& scrollbar = |
| orientation == HorizontalScrollbar ? m_hBar : m_vBar; |
| DCHECK(orientation == HorizontalScrollbar ? !m_hBarIsAttached |
| : !m_vBarIsAttached); |
| if (!scrollbar) |
| return; |
| |
| scrollableArea()->setScrollbarNeedsPaintInvalidation(orientation); |
| if (orientation == HorizontalScrollbar) |
| scrollableArea()->m_rebuildHorizontalScrollbarLayer = true; |
| else |
| scrollableArea()->m_rebuildVerticalScrollbarLayer = true; |
| |
| if (!scrollbar->isCustomScrollbar()) |
| scrollableArea()->willRemoveScrollbar(*scrollbar, orientation); |
| |
| toFrameView(scrollbar->parent())->removeChild(scrollbar.get()); |
| scrollbar->disconnectFromScrollableArea(); |
| scrollbar = nullptr; |
| } |
| |
| uint64_t PaintLayerScrollableArea::id() const { |
| return DOMNodeIds::idForNode(box().node()); |
| } |
| |
| int PaintLayerScrollableArea::PreventRelayoutScope::s_count = 0; |
| SubtreeLayoutScope* |
| PaintLayerScrollableArea::PreventRelayoutScope::s_layoutScope = nullptr; |
| bool PaintLayerScrollableArea::PreventRelayoutScope::s_relayoutNeeded = false; |
| PersistentHeapVector<Member<PaintLayerScrollableArea>>* |
| PaintLayerScrollableArea::PreventRelayoutScope::s_needsRelayout = nullptr; |
| |
| PaintLayerScrollableArea::PreventRelayoutScope::PreventRelayoutScope( |
| SubtreeLayoutScope& layoutScope) { |
| if (!s_count) { |
| DCHECK(!s_layoutScope); |
| DCHECK(!s_needsRelayout || s_needsRelayout->isEmpty()); |
| s_layoutScope = &layoutScope; |
| } |
| s_count++; |
| } |
| |
| PaintLayerScrollableArea::PreventRelayoutScope::~PreventRelayoutScope() { |
| if (--s_count == 0) { |
| if (s_relayoutNeeded) { |
| for (auto scrollableArea : *s_needsRelayout) { |
| DCHECK(scrollableArea->needsRelayout()); |
| LayoutBox& box = scrollableArea->box(); |
| s_layoutScope->setNeedsLayout( |
| &box, LayoutInvalidationReason::ScrollbarChanged); |
| if (box.isLayoutBlock()) { |
| bool horizontalScrollbarChanged = |
| scrollableArea->hasHorizontalScrollbar() != |
| scrollableArea->hadHorizontalScrollbarBeforeRelayout(); |
| bool verticalScrollbarChanged = |
| scrollableArea->hasVerticalScrollbar() != |
| scrollableArea->hadVerticalScrollbarBeforeRelayout(); |
| if (horizontalScrollbarChanged || verticalScrollbarChanged) |
| toLayoutBlock(box).scrollbarsChanged(horizontalScrollbarChanged, |
| verticalScrollbarChanged); |
| } |
| scrollableArea->setNeedsRelayout(false); |
| } |
| |
| s_needsRelayout->clear(); |
| } |
| s_layoutScope = nullptr; |
| } |
| } |
| |
| void PaintLayerScrollableArea::PreventRelayoutScope::setBoxNeedsLayout( |
| PaintLayerScrollableArea& scrollableArea, |
| bool hadHorizontalScrollbar, |
| bool hadVerticalScrollbar) { |
| DCHECK(s_count); |
| DCHECK(s_layoutScope); |
| if (scrollableArea.needsRelayout()) |
| return; |
| scrollableArea.setNeedsRelayout(true); |
| scrollableArea.setHadHorizontalScrollbarBeforeRelayout( |
| hadHorizontalScrollbar); |
| scrollableArea.setHadVerticalScrollbarBeforeRelayout(hadVerticalScrollbar); |
| |
| s_relayoutNeeded = true; |
| if (!s_needsRelayout) |
| s_needsRelayout = |
| new PersistentHeapVector<Member<PaintLayerScrollableArea>>(); |
| s_needsRelayout->push_back(&scrollableArea); |
| } |
| |
| void PaintLayerScrollableArea::PreventRelayoutScope::resetRelayoutNeeded() { |
| DCHECK_EQ(s_count, 0); |
| DCHECK(!s_needsRelayout || s_needsRelayout->isEmpty()); |
| s_relayoutNeeded = false; |
| } |
| |
| int PaintLayerScrollableArea::FreezeScrollbarsScope::s_count = 0; |
| |
| int PaintLayerScrollableArea::DelayScrollOffsetClampScope::s_count = 0; |
| PersistentHeapVector<Member<PaintLayerScrollableArea>>* |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope::s_needsClamp = |
| nullptr; |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope:: |
| DelayScrollOffsetClampScope() { |
| if (!s_needsClamp) |
| s_needsClamp = new PersistentHeapVector<Member<PaintLayerScrollableArea>>(); |
| DCHECK(s_count > 0 || s_needsClamp->isEmpty()); |
| s_count++; |
| } |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope:: |
| ~DelayScrollOffsetClampScope() { |
| if (--s_count == 0) |
| DelayScrollOffsetClampScope::clampScrollableAreas(); |
| } |
| |
| void PaintLayerScrollableArea::DelayScrollOffsetClampScope::setNeedsClamp( |
| PaintLayerScrollableArea* scrollableArea) { |
| if (!scrollableArea->needsScrollOffsetClamp()) { |
| scrollableArea->setNeedsScrollOffsetClamp(true); |
| s_needsClamp->push_back(scrollableArea); |
| } |
| } |
| |
| void PaintLayerScrollableArea::DelayScrollOffsetClampScope:: |
| clampScrollableAreas() { |
| for (auto& scrollableArea : *s_needsClamp) |
| scrollableArea->clampScrollOffsetAfterOverflowChange(); |
| delete s_needsClamp; |
| s_needsClamp = nullptr; |
| } |
| |
| } // namespace blink |