/* * Copyright (C) 2011 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "ScrollingCoordinator.h" #include "Document.h" #include "EventNames.h" #include "Frame.h" #include "FrameView.h" #include "GraphicsLayer.h" #include "Page.h" #include "PlatformWheelEvent.h" #include "PluginViewBase.h" #include "Region.h" #include "RenderLayerCompositor.h" #include "RenderView.h" #include "ScrollAnimator.h" #include "Settings.h" #include #include namespace WebCore { #if PLATFORM(IOS_FAMILY) || !ENABLE(ASYNC_SCROLLING) Ref ScrollingCoordinator::create(Page* page) { return adoptRef(*new ScrollingCoordinator(page)); } #endif ScrollingCoordinator::ScrollingCoordinator(Page* page) : m_page(page) { } ScrollingCoordinator::~ScrollingCoordinator() { ASSERT(!m_page); } void ScrollingCoordinator::pageDestroyed() { ASSERT(m_page); m_page = nullptr; } bool ScrollingCoordinator::coordinatesScrollingForFrameView(const FrameView& frameView) const { ASSERT(isMainThread()); ASSERT(m_page); if (!frameView.frame().isMainFrame() && !m_page->settings().scrollingTreeIncludesFrames() #if PLATFORM(MAC) || USE(NICOSIA) && !m_page->settings().asyncFrameScrollingEnabled() #endif ) return false; auto* renderView = frameView.frame().contentRenderer(); if (!renderView) return false; return renderView->usesCompositing(); } bool ScrollingCoordinator::coordinatesScrollingForOverflowLayer(const RenderLayer& layer) const { ASSERT(isMainThread()); ASSERT(m_page); return layer.hasCompositedScrollableOverflow(); } ScrollingNodeID ScrollingCoordinator::scrollableContainerNodeID(const RenderObject&) const { return 0; } EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegionsForFrame(const Frame& frame) const { auto* renderView = frame.contentRenderer(); if (!renderView || renderView->renderTreeBeingDestroyed()) return EventTrackingRegions(); #if ENABLE(IOS_TOUCH_EVENTS) // On iOS, we use nonFastScrollableRegion to represent the region covered by elements with touch event handlers. ASSERT(frame.isMainFrame()); auto* document = frame.document(); if (!document) return { }; return document->eventTrackingRegions(); #else auto* frameView = frame.view(); if (!frameView) return EventTrackingRegions(); Region nonFastScrollableRegion; // FIXME: should ASSERT(!frameView->needsLayout()) here, but need to fix DebugPageOverlays // to not ask for regions at bad times. if (auto* scrollableAreas = frameView->scrollableAreas()) { for (auto& scrollableArea : *scrollableAreas) { // Composited scrollable areas can be scrolled off the main thread. if (scrollableArea->usesAsyncScrolling()) continue; bool isInsideFixed; IntRect box = scrollableArea->scrollableAreaBoundingBox(&isInsideFixed); if (isInsideFixed) box = IntRect(frameView->fixedScrollableAreaBoundsInflatedForScrolling(LayoutRect(box))); nonFastScrollableRegion.unite(box); } } for (auto& widget : frameView->widgetsInRenderTree()) { if (!is(*widget)) continue; if (!downcast(*widget).wantsWheelEvents()) continue; auto* renderWidget = RenderWidget::find(*widget); if (!renderWidget) continue; nonFastScrollableRegion.unite(renderWidget->absoluteBoundingBoxRect()); } EventTrackingRegions eventTrackingRegions; // FIXME: if we've already accounted for this subframe as a scrollable area, we can avoid recursing into it here. for (auto* subframe = frame.tree().firstChild(); subframe; subframe = subframe->tree().nextSibling()) { auto* subframeView = subframe->view(); if (!subframeView) continue; EventTrackingRegions subframeRegion = absoluteEventTrackingRegionsForFrame(*subframe); // Map from the frame document to our document. IntPoint offset = subframeView->contentsToContainingViewContents(IntPoint()); // FIXME: this translation ignores non-trival transforms on the frame. subframeRegion.translate(toIntSize(offset)); eventTrackingRegions.unite(subframeRegion); } #if !ENABLE(WHEEL_EVENT_REGIONS) auto wheelHandlerRegion = frame.document()->absoluteRegionForEventTargets(frame.document()->wheelEventTargets()); bool wheelHandlerInFixedContent = wheelHandlerRegion.second; if (wheelHandlerInFixedContent) { // FIXME: need to handle position:sticky here too. LayoutRect inflatedWheelHandlerBounds = frameView->fixedScrollableAreaBoundsInflatedForScrolling(LayoutRect(wheelHandlerRegion.first.bounds())); wheelHandlerRegion.first.unite(enclosingIntRect(inflatedWheelHandlerBounds)); } nonFastScrollableRegion.unite(wheelHandlerRegion.first); #endif // FIXME: If this is not the main frame, we could clip the region to the frame's bounds. eventTrackingRegions.uniteSynchronousRegion(eventNames().wheelEvent, nonFastScrollableRegion); return eventTrackingRegions; #endif } EventTrackingRegions ScrollingCoordinator::absoluteEventTrackingRegions() const { return absoluteEventTrackingRegionsForFrame(m_page->mainFrame()); } void ScrollingCoordinator::frameViewFixedObjectsDidChange(FrameView& frameView) { ASSERT(isMainThread()); ASSERT(m_page); if (!coordinatesScrollingForFrameView(frameView)) return; updateSynchronousScrollingReasons(frameView); } GraphicsLayer* ScrollingCoordinator::scrollContainerLayerForFrameView(FrameView& frameView) { if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().scrollContainerLayer(); return nullptr; } GraphicsLayer* ScrollingCoordinator::scrolledContentsLayerForFrameView(FrameView& frameView) { if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().scrolledContentsLayer(); return nullptr; } GraphicsLayer* ScrollingCoordinator::headerLayerForFrameView(FrameView& frameView) { #if ENABLE(RUBBER_BANDING) if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().headerLayer(); return nullptr; #else UNUSED_PARAM(frameView); return nullptr; #endif } GraphicsLayer* ScrollingCoordinator::footerLayerForFrameView(FrameView& frameView) { #if ENABLE(RUBBER_BANDING) if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().footerLayer(); return nullptr; #else UNUSED_PARAM(frameView); return nullptr; #endif } GraphicsLayer* ScrollingCoordinator::counterScrollingLayerForFrameView(FrameView& frameView) { if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().fixedRootBackgroundLayer(); return nullptr; } GraphicsLayer* ScrollingCoordinator::insetClipLayerForFrameView(FrameView& frameView) { if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().clipLayer(); return nullptr; } GraphicsLayer* ScrollingCoordinator::contentShadowLayerForFrameView(FrameView& frameView) { #if ENABLE(RUBBER_BANDING) if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().layerForContentShadow(); return nullptr; #else UNUSED_PARAM(frameView); return nullptr; #endif } GraphicsLayer* ScrollingCoordinator::rootContentsLayerForFrameView(FrameView& frameView) { if (auto* renderView = frameView.frame().contentRenderer()) return renderView->compositor().rootContentsLayer(); return nullptr; } void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView& frameView) { ASSERT(isMainThread()); ASSERT(m_page); if (!coordinatesScrollingForFrameView(frameView)) return; frameViewLayoutUpdated(frameView); updateSynchronousScrollingReasons(frameView); } bool ScrollingCoordinator::hasVisibleSlowRepaintViewportConstrainedObjects(const FrameView& frameView) const { auto* viewportConstrainedObjects = frameView.viewportConstrainedObjects(); if (!viewportConstrainedObjects) return false; for (auto& viewportConstrainedObject : *viewportConstrainedObjects) { if (!is(viewportConstrainedObject) || !viewportConstrainedObject.hasLayer()) return true; auto& layer = *downcast(viewportConstrainedObject).layer(); // Any explicit reason that a fixed position element is not composited shouldn't cause slow scrolling. if (!layer.isComposited() && layer.viewportConstrainedNotCompositedReason() == RenderLayer::NoNotCompositedReason) return true; } return false; } void ScrollingCoordinator::updateSynchronousScrollingReasons(FrameView& frameView) { ASSERT(coordinatesScrollingForFrameView(frameView)); OptionSet newSynchronousScrollingReasons; // RenderLayerCompositor::updateSynchronousScrollingReasons maintains this bit, so maintain its current value. if (synchronousScrollingReasons(frameView.scrollingNodeID()).contains(SynchronousScrollingReason::HasSlowRepaintObjects)) newSynchronousScrollingReasons.add(SynchronousScrollingReason::HasSlowRepaintObjects); if (m_forceSynchronousScrollLayerPositionUpdates) newSynchronousScrollingReasons.add(SynchronousScrollingReason::ForcedOnMainThread); if (hasVisibleSlowRepaintViewportConstrainedObjects(frameView)) newSynchronousScrollingReasons.add(SynchronousScrollingReason::HasNonLayerViewportConstrainedObjects); if (frameView.frame().mainFrame().document() && frameView.frame().document()->isImageDocument()) newSynchronousScrollingReasons.add(SynchronousScrollingReason::IsImageDocument); setSynchronousScrollingReasons(frameView.scrollingNodeID(), newSynchronousScrollingReasons); } void ScrollingCoordinator::updateSynchronousScrollingReasonsForAllFrames() { for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) { if (auto* frameView = frame->view()) { if (coordinatesScrollingForFrameView(*frameView)) updateSynchronousScrollingReasons(*frameView); } } } void ScrollingCoordinator::setForceSynchronousScrollLayerPositionUpdates(bool forceSynchronousScrollLayerPositionUpdates) { if (m_forceSynchronousScrollLayerPositionUpdates == forceSynchronousScrollLayerPositionUpdates) return; m_forceSynchronousScrollLayerPositionUpdates = forceSynchronousScrollLayerPositionUpdates; updateSynchronousScrollingReasonsForAllFrames(); } bool ScrollingCoordinator::shouldUpdateScrollLayerPositionSynchronously(const FrameView& frameView) const { if (&frameView == m_page->mainFrame().view()) return !synchronousScrollingReasons(frameView.scrollingNodeID()).isEmpty(); return true; } ScrollingNodeID ScrollingCoordinator::uniqueScrollingNodeID() { static ScrollingNodeID uniqueScrollingNodeID = 1; return uniqueScrollingNodeID++; } String ScrollingCoordinator::scrollingStateTreeAsText(ScrollingStateTreeAsTextBehavior) const { return emptyString(); } String ScrollingCoordinator::scrollingTreeAsText(ScrollingStateTreeAsTextBehavior) const { return emptyString(); } String ScrollingCoordinator::synchronousScrollingReasonsAsText(OptionSet reasons) { auto string = makeString(reasons.contains(SynchronousScrollingReason::ForcedOnMainThread) ? "Forced on main thread, " : "", reasons.contains(SynchronousScrollingReason::HasSlowRepaintObjects) ? "Has slow repaint objects, " : "", reasons.contains(SynchronousScrollingReason::HasViewportConstrainedObjectsWithoutSupportingFixedLayers) ? "Has viewport constrained objects without supporting fixed layers, " : "", reasons.contains(SynchronousScrollingReason::HasNonLayerViewportConstrainedObjects) ? "Has non-layer viewport-constrained objects, " : "", reasons.contains(SynchronousScrollingReason::IsImageDocument) ? "Is image document, " : "", reasons.contains(SynchronousScrollingReason::DescendantScrollersHaveSynchronousScrolling) ? "Has slow repaint descendant scrollers, " : ""); return string.isEmpty() ? string : string.left(string.length() - 2); } String ScrollingCoordinator::synchronousScrollingReasonsAsText() const { if (auto* frameView = m_page->mainFrame().view()) return synchronousScrollingReasonsAsText(synchronousScrollingReasons(frameView->scrollingNodeID())); return String(); } TextStream& operator<<(TextStream& ts, ScrollableAreaParameters scrollableAreaParameters) { ts.dumpProperty("horizontal scroll elasticity", scrollableAreaParameters.horizontalScrollElasticity); ts.dumpProperty("vertical scroll elasticity", scrollableAreaParameters.verticalScrollElasticity); ts.dumpProperty("horizontal scrollbar mode", scrollableAreaParameters.horizontalScrollbarMode); ts.dumpProperty("vertical scrollbar mode", scrollableAreaParameters.verticalScrollbarMode); if (scrollableAreaParameters.allowsHorizontalScrolling) ts.dumpProperty("allows horizontal scrolling", scrollableAreaParameters.allowsHorizontalScrolling); if (scrollableAreaParameters.allowsVerticalScrolling) ts.dumpProperty("allows vertical scrolling", scrollableAreaParameters.allowsVerticalScrolling); if (scrollableAreaParameters.horizontalScrollbarHiddenByStyle) ts.dumpProperty("horizontal scrollbar hidden by style", scrollableAreaParameters.horizontalScrollbarHiddenByStyle); if (scrollableAreaParameters.verticalScrollbarHiddenByStyle) ts.dumpProperty("vertical scrollbar hidden by style", scrollableAreaParameters.verticalScrollbarHiddenByStyle); return ts; } TextStream& operator<<(TextStream& ts, ScrollingNodeType nodeType) { switch (nodeType) { case ScrollingNodeType::MainFrame: ts << "main-frame-scrolling"; break; case ScrollingNodeType::Subframe: ts << "subframe-scrolling"; break; case ScrollingNodeType::FrameHosting: ts << "frame-hosting"; break; case ScrollingNodeType::Overflow: ts << "overflow-scrolling"; break; case ScrollingNodeType::OverflowProxy: ts << "overflow-scroll-proxy"; break; case ScrollingNodeType::Fixed: ts << "fixed"; break; case ScrollingNodeType::Sticky: ts << "sticky"; break; case ScrollingNodeType::Positioned: ts << "positioned"; break; } return ts; } TextStream& operator<<(TextStream& ts, ScrollingLayerPositionAction action) { switch (action) { case ScrollingLayerPositionAction::Set: ts << "set"; break; case ScrollingLayerPositionAction::SetApproximate: ts << "set approximate"; break; case ScrollingLayerPositionAction::Sync: ts << "sync"; break; } return ts; } TextStream& operator<<(TextStream& ts, ViewportRectStability stability) { switch (stability) { case ViewportRectStability::Stable: ts << "stable"; break; case ViewportRectStability::Unstable: ts << "unstable"; break; case ViewportRectStability::ChangingObscuredInsetsInteractively: ts << "changing obscured insets interactively"; break; } return ts; } } // namespace WebCore