blob: 3c58c154cd25889f3fcf2c356fc1bafc49f01e7f [file] [log] [blame]
/*
* 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 "core/page/scrolling/ScrollingCoordinator.h"
#include "core/dom/Document.h"
#include "core/dom/Fullscreen.h"
#include "core/dom/Node.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/frame/VisualViewport.h"
#include "core/html/HTMLElement.h"
#include "core/layout/LayoutGeometryMap.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/plugins/PluginView.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/TraceEvent.h"
#include "platform/animation/CompositorAnimationTimeline.h"
#include "platform/exported/WebScrollbarImpl.h"
#include "platform/exported/WebScrollbarThemeGeometryNative.h"
#include "platform/geometry/Region.h"
#include "platform/geometry/TransformState.h"
#include "platform/graphics/GraphicsLayer.h"
#if OS(MACOSX)
#include "platform/mac/ScrollAnimatorMac.h"
#endif
#include "platform/scroll/MainThreadScrollingReason.h"
#include "platform/scroll/ScrollAnimatorBase.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebLayerPositionConstraint.h"
#include "public/platform/WebLayerTreeView.h"
#include "public/platform/WebScrollbarLayer.h"
#include "public/platform/WebScrollbarThemeGeometry.h"
#include "public/platform/WebScrollbarThemePainter.h"
#include "wtf/PtrUtil.h"
#include "wtf/text/StringBuilder.h"
#include <memory>
using blink::WebLayer;
using blink::WebLayerPositionConstraint;
using blink::WebRect;
using blink::WebScrollbarLayer;
using blink::WebVector;
namespace {
WebLayer* toWebLayer(blink::GraphicsLayer* layer) {
return layer ? layer->platformLayer() : nullptr;
}
} // namespace
namespace blink {
ScrollingCoordinator* ScrollingCoordinator::create(Page* page) {
return new ScrollingCoordinator(page);
}
ScrollingCoordinator::ScrollingCoordinator(Page* page)
: m_page(page),
m_scrollGestureRegionIsDirty(false),
m_touchEventTargetRectsAreDirty(false),
m_shouldScrollOnMainThreadDirty(false),
m_wasFrameScrollable(false),
m_lastMainThreadScrollingReasons(0) {}
ScrollingCoordinator::~ScrollingCoordinator() {
ASSERT(!m_page);
}
DEFINE_TRACE(ScrollingCoordinator) {
visitor->trace(m_page);
visitor->trace(m_horizontalScrollbars);
visitor->trace(m_verticalScrollbars);
}
void ScrollingCoordinator::setShouldHandleScrollGestureOnMainThreadRegion(
const Region& region) {
if (!m_page->mainFrame()->isLocalFrame() ||
!m_page->deprecatedLocalMainFrame()->view())
return;
if (WebLayer* scrollLayer = toWebLayer(
m_page->deprecatedLocalMainFrame()->view()->layerForScrolling())) {
Vector<IntRect> rects = region.rects();
WebVector<WebRect> webRects(rects.size());
for (size_t i = 0; i < rects.size(); ++i)
webRects[i] = rects[i];
scrollLayer->setNonFastScrollableRegion(webRects);
}
}
void ScrollingCoordinator::notifyGeometryChanged() {
m_scrollGestureRegionIsDirty = true;
m_touchEventTargetRectsAreDirty = true;
m_shouldScrollOnMainThreadDirty = true;
}
void ScrollingCoordinator::notifyOverflowUpdated() {
m_scrollGestureRegionIsDirty = true;
}
void ScrollingCoordinator::scrollableAreasDidChange() {
ASSERT(m_page);
if (!m_page->mainFrame()->isLocalFrame() ||
!m_page->deprecatedLocalMainFrame()->view())
return;
// Layout may update scrollable area bounding boxes. It also sets the same
// dirty flag making this one redundant (See
// |ScrollingCoordinator::notifyGeometryChanged|).
// So if layout is expected, ignore this call allowing scrolling coordinator
// to be notified post-layout to recompute gesture regions.
if (m_page->deprecatedLocalMainFrame()->view()->needsLayout())
return;
m_scrollGestureRegionIsDirty = true;
}
void ScrollingCoordinator::updateAfterCompositingChangeIfNeeded() {
if (!m_page->mainFrame()->isLocalFrame())
return;
if (!shouldUpdateAfterCompositingChange())
return;
TRACE_EVENT0("input",
"ScrollingCoordinator::updateAfterCompositingChangeIfNeeded");
if (m_scrollGestureRegionIsDirty) {
// Compute the region of the page where we can't handle scroll gestures and
// mousewheel events
// on the impl thread. This currently includes:
// 1. All scrollable areas, such as subframes, overflow divs and list boxes,
// whose composited scrolling are not enabled. We need to do this even if
// the frame view whose layout was updated is not the main frame.
// 2. Resize control areas, e.g. the small rect at the right bottom of
// div/textarea/iframe when CSS property "resize" is enabled.
// 3. Plugin areas.
Region shouldHandleScrollGestureOnMainThreadRegion =
computeShouldHandleScrollGestureOnMainThreadRegion(
m_page->deprecatedLocalMainFrame(), IntPoint());
setShouldHandleScrollGestureOnMainThreadRegion(
shouldHandleScrollGestureOnMainThreadRegion);
m_scrollGestureRegionIsDirty = false;
}
if (m_touchEventTargetRectsAreDirty) {
updateTouchEventTargetRectsIfNeeded();
m_touchEventTargetRectsAreDirty = false;
}
FrameView* frameView = m_page->deprecatedLocalMainFrame()->view();
bool frameIsScrollable = frameView && frameView->isScrollable();
if (m_shouldScrollOnMainThreadDirty ||
m_wasFrameScrollable != frameIsScrollable) {
setShouldUpdateScrollLayerPositionOnMainThread(
mainThreadScrollingReasons());
m_shouldScrollOnMainThreadDirty = false;
}
m_wasFrameScrollable = frameIsScrollable;
if (WebLayer* layoutViewportScrollLayer =
frameView ? toWebLayer(frameView->layerForScrolling()) : nullptr) {
layoutViewportScrollLayer->setBounds(frameView->contentsSize());
// If there is a non-root fullscreen element, prevent the viewport from
// scrolling.
Document* mainFrameDocument =
m_page->deprecatedLocalMainFrame()->document();
Element* fullscreenElement =
Fullscreen::fullscreenElementFrom(*mainFrameDocument);
WebLayer* visualViewportScrollLayer =
toWebLayer(m_page->frameHost().visualViewport().scrollLayer());
if (visualViewportScrollLayer) {
if (fullscreenElement &&
fullscreenElement != mainFrameDocument->documentElement())
visualViewportScrollLayer->setUserScrollable(false, false);
else
visualViewportScrollLayer->setUserScrollable(true, true);
}
layoutViewportScrollLayer->setUserScrollable(
frameView->userInputScrollable(HorizontalScrollbar),
frameView->userInputScrollable(VerticalScrollbar));
}
const FrameTree& tree = m_page->mainFrame()->tree();
for (const Frame* child = tree.firstChild(); child;
child = child->tree().nextSibling()) {
if (!child->isLocalFrame())
continue;
FrameView* frameView = toLocalFrame(child)->view();
if (!frameView || frameView->shouldThrottleRendering())
continue;
if (WebLayer* scrollLayer = toWebLayer(frameView->layerForScrolling()))
scrollLayer->setBounds(frameView->contentsSize());
}
}
void ScrollingCoordinator::setLayerIsContainerForFixedPositionLayers(
GraphicsLayer* layer,
bool enable) {
if (WebLayer* scrollableLayer = toWebLayer(layer))
scrollableLayer->setIsContainerForFixedPositionLayers(enable);
}
static void clearPositionConstraintExceptForLayer(GraphicsLayer* layer,
GraphicsLayer* except) {
if (layer && layer != except && toWebLayer(layer))
toWebLayer(layer)->setPositionConstraint(WebLayerPositionConstraint());
}
static WebLayerPositionConstraint computePositionConstraint(
const PaintLayer* layer) {
ASSERT(layer->hasCompositedLayerMapping());
do {
if (layer->layoutObject()->style()->position() == FixedPosition) {
const LayoutObject* fixedPositionObject = layer->layoutObject();
bool fixedToRight = !fixedPositionObject->style()->right().isAuto();
bool fixedToBottom = !fixedPositionObject->style()->bottom().isAuto();
return WebLayerPositionConstraint::fixedPosition(fixedToRight,
fixedToBottom);
}
layer = layer->parent();
// Composited layers that inherit a fixed position state will be positioned
// with respect to the nearest compositedLayerMapping's GraphicsLayer.
// So, once we find a layer that has its own compositedLayerMapping, we can
// stop searching for a fixed position LayoutObject.
} while (layer && !layer->hasCompositedLayerMapping());
return WebLayerPositionConstraint();
}
void ScrollingCoordinator::updateLayerPositionConstraint(PaintLayer* layer) {
ASSERT(layer->hasCompositedLayerMapping());
CompositedLayerMapping* compositedLayerMapping =
layer->compositedLayerMapping();
GraphicsLayer* mainLayer = compositedLayerMapping->childForSuperlayers();
// Avoid unnecessary commits
clearPositionConstraintExceptForLayer(
compositedLayerMapping->squashingContainmentLayer(), mainLayer);
clearPositionConstraintExceptForLayer(
compositedLayerMapping->ancestorClippingLayer(), mainLayer);
clearPositionConstraintExceptForLayer(
compositedLayerMapping->mainGraphicsLayer(), mainLayer);
if (WebLayer* scrollableLayer = toWebLayer(mainLayer))
scrollableLayer->setPositionConstraint(computePositionConstraint(layer));
}
void ScrollingCoordinator::willDestroyScrollableArea(
ScrollableArea* scrollableArea) {
removeWebScrollbarLayer(scrollableArea, HorizontalScrollbar);
removeWebScrollbarLayer(scrollableArea, VerticalScrollbar);
}
void ScrollingCoordinator::removeWebScrollbarLayer(
ScrollableArea* scrollableArea,
ScrollbarOrientation orientation) {
ScrollbarMap& scrollbars = orientation == HorizontalScrollbar
? m_horizontalScrollbars
: m_verticalScrollbars;
if (std::unique_ptr<WebScrollbarLayer> scrollbarLayer =
scrollbars.take(scrollableArea))
GraphicsLayer::unregisterContentsLayer(scrollbarLayer->layer());
}
static std::unique_ptr<WebScrollbarLayer> createScrollbarLayer(
Scrollbar& scrollbar,
float deviceScaleFactor) {
ScrollbarTheme& theme = scrollbar.theme();
WebScrollbarThemePainter painter(theme, scrollbar, deviceScaleFactor);
std::unique_ptr<WebScrollbarThemeGeometry> geometry(
WebScrollbarThemeGeometryNative::create(theme));
std::unique_ptr<WebScrollbarLayer> scrollbarLayer =
wrapUnique(Platform::current()->compositorSupport()->createScrollbarLayer(
WebScrollbarImpl::create(&scrollbar), painter, geometry.release()));
GraphicsLayer::registerContentsLayer(scrollbarLayer->layer());
return scrollbarLayer;
}
std::unique_ptr<WebScrollbarLayer>
ScrollingCoordinator::createSolidColorScrollbarLayer(
ScrollbarOrientation orientation,
int thumbThickness,
int trackStart,
bool isLeftSideVerticalScrollbar) {
WebScrollbar::Orientation webOrientation =
(orientation == HorizontalScrollbar) ? WebScrollbar::Horizontal
: WebScrollbar::Vertical;
std::unique_ptr<WebScrollbarLayer> scrollbarLayer = wrapUnique(
Platform::current()->compositorSupport()->createSolidColorScrollbarLayer(
webOrientation, thumbThickness, trackStart,
isLeftSideVerticalScrollbar));
GraphicsLayer::registerContentsLayer(scrollbarLayer->layer());
return scrollbarLayer;
}
static void detachScrollbarLayer(GraphicsLayer* scrollbarGraphicsLayer) {
ASSERT(scrollbarGraphicsLayer);
scrollbarGraphicsLayer->setContentsToPlatformLayer(nullptr);
scrollbarGraphicsLayer->setDrawsContent(true);
}
static void setupScrollbarLayer(GraphicsLayer* scrollbarGraphicsLayer,
WebScrollbarLayer* scrollbarLayer,
WebLayer* scrollLayer) {
ASSERT(scrollbarGraphicsLayer);
ASSERT(scrollbarLayer);
if (!scrollLayer) {
detachScrollbarLayer(scrollbarGraphicsLayer);
return;
}
scrollbarLayer->setScrollLayer(scrollLayer);
scrollbarGraphicsLayer->setContentsToPlatformLayer(scrollbarLayer->layer());
scrollbarGraphicsLayer->setDrawsContent(false);
}
WebScrollbarLayer* ScrollingCoordinator::addWebScrollbarLayer(
ScrollableArea* scrollableArea,
ScrollbarOrientation orientation,
std::unique_ptr<WebScrollbarLayer> scrollbarLayer) {
ScrollbarMap& scrollbars = orientation == HorizontalScrollbar
? m_horizontalScrollbars
: m_verticalScrollbars;
return scrollbars.add(scrollableArea, std::move(scrollbarLayer))
.storedValue->value.get();
}
WebScrollbarLayer* ScrollingCoordinator::getWebScrollbarLayer(
ScrollableArea* scrollableArea,
ScrollbarOrientation orientation) {
ScrollbarMap& scrollbars = orientation == HorizontalScrollbar
? m_horizontalScrollbars
: m_verticalScrollbars;
return scrollbars.get(scrollableArea);
}
void ScrollingCoordinator::scrollableAreaScrollbarLayerDidChange(
ScrollableArea* scrollableArea,
ScrollbarOrientation orientation) {
if (!m_page || !m_page->mainFrame())
return;
bool isMainFrame = isForMainFrame(scrollableArea);
GraphicsLayer* scrollbarGraphicsLayer =
orientation == HorizontalScrollbar
? scrollableArea->layerForHorizontalScrollbar()
: scrollableArea->layerForVerticalScrollbar();
if (scrollbarGraphicsLayer) {
Scrollbar& scrollbar = orientation == HorizontalScrollbar
? *scrollableArea->horizontalScrollbar()
: *scrollableArea->verticalScrollbar();
if (scrollbar.isCustomScrollbar()) {
detachScrollbarLayer(scrollbarGraphicsLayer);
scrollbarGraphicsLayer->platformLayer()->addMainThreadScrollingReasons(
MainThreadScrollingReason::kCustomScrollbarScrolling);
return;
}
// Invalidate custom scrollbar scrolling reason in case a custom
// scrollbar becomes a non-custom one.
scrollbarGraphicsLayer->platformLayer()->clearMainThreadScrollingReasons(
MainThreadScrollingReason::kCustomScrollbarScrolling);
WebScrollbarLayer* scrollbarLayer =
getWebScrollbarLayer(scrollableArea, orientation);
if (!scrollbarLayer) {
Settings* settings = m_page->mainFrame()->settings();
std::unique_ptr<WebScrollbarLayer> webScrollbarLayer;
if (settings->useSolidColorScrollbars()) {
ASSERT(RuntimeEnabledFeatures::overlayScrollbarsEnabled());
webScrollbarLayer = createSolidColorScrollbarLayer(
orientation, scrollbar.theme().thumbThickness(scrollbar),
scrollbar.theme().trackPosition(scrollbar),
scrollableArea->shouldPlaceVerticalScrollbarOnLeft());
} else {
webScrollbarLayer =
createScrollbarLayer(scrollbar, m_page->deviceScaleFactor());
}
scrollbarLayer = addWebScrollbarLayer(scrollableArea, orientation,
std::move(webScrollbarLayer));
}
WebLayer* scrollLayer = toWebLayer(scrollableArea->layerForScrolling());
setupScrollbarLayer(scrollbarGraphicsLayer, scrollbarLayer, scrollLayer);
// Root layer non-overlay scrollbars should be marked opaque to disable
// blending.
bool isOpaqueScrollbar = !scrollbar.isOverlayScrollbar();
scrollbarGraphicsLayer->setContentsOpaque(isMainFrame && isOpaqueScrollbar);
} else {
removeWebScrollbarLayer(scrollableArea, orientation);
}
}
bool ScrollingCoordinator::scrollableAreaScrollLayerDidChange(
ScrollableArea* scrollableArea) {
if (!m_page || !m_page->mainFrame())
return false;
GraphicsLayer* scrollLayer = scrollableArea->layerForScrolling();
if (scrollLayer) {
bool isForVisualViewport =
scrollableArea == &m_page->frameHost().visualViewport();
scrollLayer->setScrollableArea(scrollableArea, isForVisualViewport);
}
WebLayer* webLayer = toWebLayer(scrollableArea->layerForScrolling());
WebLayer* containerLayer = toWebLayer(scrollableArea->layerForContainer());
if (webLayer) {
webLayer->setScrollClipLayer(containerLayer);
DoublePoint scrollPosition(scrollableArea->scrollPositionDouble() +
toDoubleSize(scrollableArea->scrollOrigin()));
webLayer->setScrollPositionDouble(scrollPosition);
webLayer->setBounds(scrollableArea->contentsSize());
bool canScrollX = scrollableArea->userInputScrollable(HorizontalScrollbar);
bool canScrollY = scrollableArea->userInputScrollable(VerticalScrollbar);
webLayer->setUserScrollable(canScrollX, canScrollY);
}
if (WebScrollbarLayer* scrollbarLayer =
getWebScrollbarLayer(scrollableArea, HorizontalScrollbar)) {
GraphicsLayer* horizontalScrollbarLayer =
scrollableArea->layerForHorizontalScrollbar();
if (horizontalScrollbarLayer)
setupScrollbarLayer(horizontalScrollbarLayer, scrollbarLayer, webLayer);
}
if (WebScrollbarLayer* scrollbarLayer =
getWebScrollbarLayer(scrollableArea, VerticalScrollbar)) {
GraphicsLayer* verticalScrollbarLayer =
scrollableArea->layerForVerticalScrollbar();
if (verticalScrollbarLayer)
setupScrollbarLayer(verticalScrollbarLayer, scrollbarLayer, webLayer);
}
// Update the viewport layer registration if the outer viewport may have
// changed.
if (RuntimeEnabledFeatures::rootLayerScrollingEnabled() &&
isForRootLayer(scrollableArea))
m_page->chromeClient().registerViewportLayers();
scrollableArea->layerForScrollingDidChange(
m_programmaticScrollAnimatorTimeline.get());
return !!webLayer;
}
using GraphicsLayerHitTestRects =
WTF::HashMap<const GraphicsLayer*, Vector<LayoutRect>>;
// In order to do a DFS cross-frame walk of the Layer tree, we need to know
// which Layers have child frames inside of them. This computes a mapping for
// the current frame which we can consult while walking the layers of that
// frame. Whenever we descend into a new frame, a new map will be created.
using LayerFrameMap =
HeapHashMap<const PaintLayer*, HeapVector<Member<const LocalFrame>>>;
static void makeLayerChildFrameMap(const LocalFrame* currentFrame,
LayerFrameMap* map) {
map->clear();
const FrameTree& tree = currentFrame->tree();
for (const Frame* child = tree.firstChild(); child;
child = child->tree().nextSibling()) {
if (!child->isLocalFrame())
continue;
const LayoutItem ownerLayoutItem = toLocalFrame(child)->ownerLayoutItem();
if (ownerLayoutItem.isNull())
continue;
const PaintLayer* containingLayer = ownerLayoutItem.enclosingLayer();
LayerFrameMap::iterator iter = map->find(containingLayer);
if (iter == map->end())
map->add(containingLayer, HeapVector<Member<const LocalFrame>>())
.storedValue->value.append(toLocalFrame(child));
else
iter->value.append(toLocalFrame(child));
}
}
static void projectRectsToGraphicsLayerSpaceRecursive(
const PaintLayer* curLayer,
const LayerHitTestRects& layerRects,
GraphicsLayerHitTestRects& graphicsRects,
LayoutGeometryMap& geometryMap,
HashSet<const PaintLayer*>& layersWithRects,
LayerFrameMap& layerChildFrameMap) {
// If this layer is throttled, ignore it.
if (curLayer->layoutObject()->frameView() &&
curLayer->layoutObject()->frameView()->shouldThrottleRendering())
return;
// Project any rects for the current layer
LayerHitTestRects::const_iterator layerIter = layerRects.find(curLayer);
if (layerIter != layerRects.end()) {
// Find the enclosing composited layer when it's in another document (for
// non-composited iframes).
const PaintLayer* compositedLayer =
layerIter->key
->enclosingLayerForPaintInvalidationCrossingFrameBoundaries();
ASSERT(compositedLayer);
// Find the appropriate GraphicsLayer for the composited Layer.
GraphicsLayer* graphicsLayer =
compositedLayer->graphicsLayerBackingForScrolling();
GraphicsLayerHitTestRects::iterator glIter =
graphicsRects.find(graphicsLayer);
Vector<LayoutRect>* glRects;
if (glIter == graphicsRects.end())
glRects = &graphicsRects.add(graphicsLayer, Vector<LayoutRect>())
.storedValue->value;
else
glRects = &glIter->value;
// Transform each rect to the co-ordinate space of the graphicsLayer.
for (size_t i = 0; i < layerIter->value.size(); ++i) {
LayoutRect rect = layerIter->value[i];
if (compositedLayer != curLayer) {
FloatQuad compositorQuad = geometryMap.mapToAncestor(
FloatRect(rect), compositedLayer->layoutObject());
rect = LayoutRect(compositorQuad.boundingBox());
// If the enclosing composited layer itself is scrolled, we have to undo
// the subtraction of its scroll offset since we want the offset
// relative to the scrolling content, not the element itself.
if (compositedLayer->layoutObject()->hasOverflowClip())
rect.move(compositedLayer->layoutBox()->scrolledContentOffset());
}
PaintLayer::mapRectInPaintInvalidationContainerToBacking(
*compositedLayer->layoutObject(), rect);
glRects->append(rect);
}
}
// Walk child layers of interest
for (const PaintLayer* childLayer = curLayer->firstChild(); childLayer;
childLayer = childLayer->nextSibling()) {
if (layersWithRects.contains(childLayer)) {
geometryMap.pushMappingsToAncestor(childLayer, curLayer);
projectRectsToGraphicsLayerSpaceRecursive(
childLayer, layerRects, graphicsRects, geometryMap, layersWithRects,
layerChildFrameMap);
geometryMap.popMappingsToAncestor(curLayer);
}
}
// If this layer has any frames of interest as a child of it, walk those (with
// an updated frame map).
LayerFrameMap::iterator mapIter = layerChildFrameMap.find(curLayer);
if (mapIter != layerChildFrameMap.end()) {
for (size_t i = 0; i < mapIter->value.size(); i++) {
const LocalFrame* childFrame = mapIter->value[i];
const PaintLayer* childLayer =
childFrame->view()->layoutViewItem().layer();
if (layersWithRects.contains(childLayer)) {
LayerFrameMap newLayerChildFrameMap;
makeLayerChildFrameMap(childFrame, &newLayerChildFrameMap);
geometryMap.pushMappingsToAncestor(childLayer, curLayer);
projectRectsToGraphicsLayerSpaceRecursive(
childLayer, layerRects, graphicsRects, geometryMap, layersWithRects,
newLayerChildFrameMap);
geometryMap.popMappingsToAncestor(curLayer);
}
}
}
}
static void projectRectsToGraphicsLayerSpace(
LocalFrame* mainFrame,
const LayerHitTestRects& layerRects,
GraphicsLayerHitTestRects& graphicsRects) {
TRACE_EVENT0("input",
"ScrollingCoordinator::projectRectsToGraphicsLayerSpace");
bool touchHandlerInChildFrame = false;
// We have a set of rects per Layer, we need to map them to their bounding
// boxes in their enclosing composited layer. To do this most efficiently
// we'll walk the Layer tree using LayoutGeometryMap. First record all the
// branches we should traverse in the tree (including all documents on the
// page).
HashSet<const PaintLayer*> layersWithRects;
for (const auto& layerRect : layerRects) {
const PaintLayer* layer = layerRect.key;
do {
if (!layersWithRects.add(layer).isNewEntry)
break;
if (layer->parent()) {
layer = layer->parent();
} else {
LayoutItem parentDocLayoutItem =
layer->layoutObject()->frame()->ownerLayoutItem();
if (!parentDocLayoutItem.isNull()) {
layer = parentDocLayoutItem.enclosingLayer();
touchHandlerInChildFrame = true;
}
}
} while (layer);
}
// Now walk the layer projecting rects while maintaining a LayoutGeometryMap
MapCoordinatesFlags flags = UseTransforms;
if (touchHandlerInChildFrame)
flags |= TraverseDocumentBoundaries;
PaintLayer* rootLayer = mainFrame->contentLayoutItem().layer();
LayoutGeometryMap geometryMap(flags);
geometryMap.pushMappingsToAncestor(rootLayer, 0);
LayerFrameMap layerChildFrameMap;
makeLayerChildFrameMap(mainFrame, &layerChildFrameMap);
projectRectsToGraphicsLayerSpaceRecursive(
rootLayer, layerRects, graphicsRects, geometryMap, layersWithRects,
layerChildFrameMap);
}
void ScrollingCoordinator::updateTouchEventTargetRectsIfNeeded() {
TRACE_EVENT0("input",
"ScrollingCoordinator::updateTouchEventTargetRectsIfNeeded");
if (!RuntimeEnabledFeatures::touchEnabled())
return;
// TODO(chrishtr): implement touch event target rects for SPv2.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return;
LayerHitTestRects touchEventTargetRects;
computeTouchEventTargetRects(touchEventTargetRects);
setTouchEventTargetRects(touchEventTargetRects);
}
void ScrollingCoordinator::reset() {
for (const auto& scrollbar : m_horizontalScrollbars)
GraphicsLayer::unregisterContentsLayer(scrollbar.value->layer());
for (const auto& scrollbar : m_verticalScrollbars)
GraphicsLayer::unregisterContentsLayer(scrollbar.value->layer());
m_horizontalScrollbars.clear();
m_verticalScrollbars.clear();
m_layersWithTouchRects.clear();
m_wasFrameScrollable = false;
m_lastMainThreadScrollingReasons = 0;
setShouldUpdateScrollLayerPositionOnMainThread(
m_lastMainThreadScrollingReasons);
}
// Note that in principle this could be called more often than
// computeTouchEventTargetRects, for example during a non-composited scroll
// (although that's not yet implemented - crbug.com/261307).
void ScrollingCoordinator::setTouchEventTargetRects(
LayerHitTestRects& layerRects) {
TRACE_EVENT0("input", "ScrollingCoordinator::setTouchEventTargetRects");
// Update the list of layers with touch hit rects.
HashSet<const PaintLayer*> oldLayersWithTouchRects;
m_layersWithTouchRects.swap(oldLayersWithTouchRects);
for (const auto& layerRect : layerRects) {
if (!layerRect.value.isEmpty()) {
const PaintLayer* compositedLayer =
layerRect.key
->enclosingLayerForPaintInvalidationCrossingFrameBoundaries();
ASSERT(compositedLayer);
m_layersWithTouchRects.add(compositedLayer);
}
}
// Ensure we have an entry for each composited layer that previously had rects
// (so that old ones will get cleared out). Note that ideally we'd track this
// on GraphicsLayer instead of Layer, but we have no good hook into the
// lifetime of a GraphicsLayer.
for (const PaintLayer* layer : oldLayersWithTouchRects) {
if (!layerRects.contains(layer))
layerRects.add(layer, Vector<LayoutRect>());
}
GraphicsLayerHitTestRects graphicsLayerRects;
projectRectsToGraphicsLayerSpace(m_page->deprecatedLocalMainFrame(),
layerRects, graphicsLayerRects);
for (const auto& layerRect : graphicsLayerRects) {
const GraphicsLayer* graphicsLayer = layerRect.key;
WebVector<WebRect> webRects(layerRect.value.size());
for (size_t i = 0; i < layerRect.value.size(); ++i)
webRects[i] = enclosingIntRect(layerRect.value[i]);
graphicsLayer->platformLayer()->setTouchEventHandlerRegion(webRects);
}
}
void ScrollingCoordinator::touchEventTargetRectsDidChange() {
if (!RuntimeEnabledFeatures::touchEnabled())
return;
ASSERT(m_page);
if (!m_page->mainFrame()->isLocalFrame() ||
!m_page->deprecatedLocalMainFrame()->view())
return;
// Wait until after layout to update.
if (m_page->deprecatedLocalMainFrame()->view()->needsLayout())
return;
// FIXME: scheduleAnimation() is just a method of forcing the compositor to
// realize that it needs to commit here. We should expose a cleaner API for
// this.
LayoutViewItem layoutView =
m_page->deprecatedLocalMainFrame()->contentLayoutItem();
if (!layoutView.isNull() && layoutView.compositor() &&
layoutView.compositor()->staleInCompositingMode())
m_page->deprecatedLocalMainFrame()->view()->scheduleAnimation();
m_touchEventTargetRectsAreDirty = true;
}
void ScrollingCoordinator::updateScrollParentForGraphicsLayer(
GraphicsLayer* child,
const PaintLayer* parent) {
WebLayer* scrollParentWebLayer = nullptr;
if (parent && parent->hasCompositedLayerMapping())
scrollParentWebLayer =
toWebLayer(parent->compositedLayerMapping()->scrollingContentsLayer());
child->setScrollParent(scrollParentWebLayer);
}
void ScrollingCoordinator::updateClipParentForGraphicsLayer(
GraphicsLayer* child,
const PaintLayer* parent) {
WebLayer* clipParentWebLayer = nullptr;
if (parent && parent->hasCompositedLayerMapping())
clipParentWebLayer =
toWebLayer(parent->compositedLayerMapping()->parentForSublayers());
child->setClipParent(clipParentWebLayer);
}
void ScrollingCoordinator::willDestroyLayer(PaintLayer* layer) {
m_layersWithTouchRects.remove(layer);
}
void ScrollingCoordinator::setShouldUpdateScrollLayerPositionOnMainThread(
MainThreadScrollingReasons mainThreadScrollingReasons) {
if (!m_page->mainFrame()->isLocalFrame() ||
!m_page->deprecatedLocalMainFrame()->view())
return;
GraphicsLayer* visualViewportLayer =
m_page->frameHost().visualViewport().scrollLayer();
WebLayer* visualViewportScrollLayer = toWebLayer(visualViewportLayer);
GraphicsLayer* layer =
m_page->deprecatedLocalMainFrame()->view()->layerForScrolling();
if (WebLayer* scrollLayer = toWebLayer(layer)) {
m_lastMainThreadScrollingReasons = mainThreadScrollingReasons;
if (mainThreadScrollingReasons) {
if (ScrollAnimatorBase* scrollAnimator =
layer->getScrollableArea()->existingScrollAnimator()) {
DCHECK(RuntimeEnabledFeatures::slimmingPaintV2Enabled() ||
m_page->deprecatedLocalMainFrame()
->document()
->lifecycle()
.state() >= DocumentLifecycle::CompositingClean);
scrollAnimator->takeOverCompositorAnimation();
}
scrollLayer->addMainThreadScrollingReasons(mainThreadScrollingReasons);
if (visualViewportScrollLayer) {
if (ScrollAnimatorBase* scrollAnimator =
visualViewportLayer->getScrollableArea()
->existingScrollAnimator()) {
DCHECK(RuntimeEnabledFeatures::slimmingPaintV2Enabled() ||
m_page->deprecatedLocalMainFrame()
->document()
->lifecycle()
.state() >= DocumentLifecycle::CompositingClean);
scrollAnimator->takeOverCompositorAnimation();
}
visualViewportScrollLayer->addMainThreadScrollingReasons(
mainThreadScrollingReasons);
}
} else {
// Clear all main thread scrolling reasons except the one that's set
// if there is a running scroll animation.
uint32_t mainThreadScrollingReasonsToClear = ~0u;
mainThreadScrollingReasonsToClear &=
~MainThreadScrollingReason::kHandlingScrollFromMainThread;
scrollLayer->clearMainThreadScrollingReasons(
mainThreadScrollingReasonsToClear);
if (visualViewportScrollLayer)
visualViewportScrollLayer->clearMainThreadScrollingReasons(
mainThreadScrollingReasonsToClear);
}
}
}
void ScrollingCoordinator::layerTreeViewInitialized(
WebLayerTreeView& layerTreeView) {
if (Platform::current()->isThreadedAnimationEnabled()) {
m_programmaticScrollAnimatorTimeline =
CompositorAnimationTimeline::create();
layerTreeView.attachCompositorAnimationTimeline(
m_programmaticScrollAnimatorTimeline->animationTimeline());
}
}
void ScrollingCoordinator::willCloseLayerTreeView(
WebLayerTreeView& layerTreeView) {
if (m_programmaticScrollAnimatorTimeline) {
layerTreeView.detachCompositorAnimationTimeline(
m_programmaticScrollAnimatorTimeline->animationTimeline());
m_programmaticScrollAnimatorTimeline.reset();
}
}
void ScrollingCoordinator::willBeDestroyed() {
ASSERT(m_page);
m_page = nullptr;
for (const auto& scrollbar : m_horizontalScrollbars)
GraphicsLayer::unregisterContentsLayer(scrollbar.value->layer());
for (const auto& scrollbar : m_verticalScrollbars)
GraphicsLayer::unregisterContentsLayer(scrollbar.value->layer());
}
bool ScrollingCoordinator::coordinatesScrollingForFrameView(
FrameView* frameView) const {
ASSERT(isMainThread());
// We currently only support composited mode.
LayoutViewItem layoutView = frameView->frame().contentLayoutItem();
if (layoutView.isNull())
return false;
return layoutView.usesCompositing();
}
Region ScrollingCoordinator::computeShouldHandleScrollGestureOnMainThreadRegion(
const LocalFrame* frame,
const IntPoint& frameLocation) const {
Region shouldHandleScrollGestureOnMainThreadRegion;
FrameView* frameView = frame->view();
if (!frameView || frameView->shouldThrottleRendering())
return shouldHandleScrollGestureOnMainThreadRegion;
IntPoint offset = frameLocation;
offset.moveBy(frameView->frameRect().location());
if (const FrameView::ScrollableAreaSet* scrollableAreas =
frameView->scrollableAreas()) {
for (const ScrollableArea* scrollableArea : *scrollableAreas) {
if (scrollableArea->isFrameView() &&
toFrameView(scrollableArea)->shouldThrottleRendering())
continue;
// Composited scrollable areas can be scrolled off the main thread.
if (scrollableArea->usesCompositedScrolling())
continue;
IntRect box = scrollableArea->scrollableAreaBoundingBox();
box.moveBy(offset);
shouldHandleScrollGestureOnMainThreadRegion.unite(box);
}
}
// We use GestureScrollBegin/Update/End for moving the resizer handle. So we
// mark these small resizer areas as non-fast-scrollable to allow the scroll
// gestures to be passed to main thread if they are targeting the resizer
// area. (Resizing is done in EventHandler.cpp on main thread).
if (const FrameView::ResizerAreaSet* resizerAreas =
frameView->resizerAreas()) {
for (const LayoutBox* box : *resizerAreas) {
IntRect bounds = box->absoluteBoundingBoxRect();
IntRect corner =
box->layer()->getScrollableArea()->touchResizerCornerRect(bounds);
corner.moveBy(offset);
shouldHandleScrollGestureOnMainThreadRegion.unite(corner);
}
}
if (const FrameView::ChildrenWidgetSet* children = frameView->children()) {
for (const Member<Widget>& child : *children) {
if (!(*child).isPluginView())
continue;
PluginView* pluginView = toPluginView(child.get());
if (pluginView->wantsWheelEvents()) {
IntRect box = pluginView->frameRect();
box.moveBy(offset);
shouldHandleScrollGestureOnMainThreadRegion.unite(box);
}
}
}
const FrameTree& tree = frame->tree();
for (Frame* subFrame = tree.firstChild(); subFrame;
subFrame = subFrame->tree().nextSibling()) {
if (subFrame->isLocalFrame())
shouldHandleScrollGestureOnMainThreadRegion.unite(
computeShouldHandleScrollGestureOnMainThreadRegion(
toLocalFrame(subFrame), offset));
}
return shouldHandleScrollGestureOnMainThreadRegion;
}
static void accumulateDocumentTouchEventTargetRects(LayerHitTestRects& rects,
const Document* document) {
ASSERT(document);
const EventTargetSet* targets =
document->frameHost()->eventHandlerRegistry().eventHandlerTargets(
EventHandlerRegistry::TouchStartOrMoveEventBlocking);
if (!targets)
return;
// If there's a handler on the window, document, html or body element (fairly
// common in practice), then we can quickly mark the entire document and skip
// looking at any other handlers. Note that technically a handler on the body
// doesn't cover the whole document, but it's reasonable to be conservative
// and report the whole document anyway.
//
// Fullscreen HTML5 video when OverlayFullscreenVideo is enabled is
// implemented by replacing the root cc::layer with the video layer so doing
// this optimization causes the compositor to think that there are no
// handlers, therefore skip it.
if (!document->layoutViewItem().compositor()->inOverlayFullscreenVideo()) {
for (const auto& eventTarget : *targets) {
EventTarget* target = eventTarget.key;
Node* node = target->toNode();
LocalDOMWindow* window = target->toLocalDOMWindow();
// If the target is inside a throttled frame, skip it.
if (window && window->frame()->view() &&
window->frame()->view()->shouldThrottleRendering())
continue;
if (node && node->document().view() &&
node->document().view()->shouldThrottleRendering())
continue;
if (window || node == document || node == document->documentElement() ||
node == document->body()) {
if (LayoutViewItem layoutView = document->layoutViewItem()) {
layoutView.computeLayerHitTestRects(rects);
}
return;
}
}
}
for (const auto& eventTarget : *targets) {
EventTarget* target = eventTarget.key;
Node* node = target->toNode();
if (!node || !node->isConnected())
continue;
// If the document belongs to an invisible subframe it does not have a
// composited layer and should be skipped.
if (node->document().isInInvisibleSubframe())
continue;
// If the node belongs to a throttled frame, skip it.
if (node->document().view() &&
node->document().view()->shouldThrottleRendering())
continue;
if (node->isDocumentNode() && node != document) {
accumulateDocumentTouchEventTargetRects(rects, toDocument(node));
} else if (LayoutObject* layoutObject = node->layoutObject()) {
// If the set also contains one of our ancestor nodes then processing
// this node would be redundant.
bool hasTouchEventTargetAncestor = false;
for (Node& ancestor : NodeTraversal::ancestorsOf(*node)) {
if (hasTouchEventTargetAncestor)
break;
if (targets->contains(&ancestor))
hasTouchEventTargetAncestor = true;
}
if (!hasTouchEventTargetAncestor) {
// Walk up the tree to the outermost non-composited scrollable layer.
PaintLayer* enclosingNonCompositedScrollLayer = nullptr;
for (PaintLayer* parent = layoutObject->enclosingLayer();
parent && parent->compositingState() == NotComposited;
parent = parent->parent()) {
if (parent->scrollsOverflow())
enclosingNonCompositedScrollLayer = parent;
}
// Report the whole non-composited scroll layer as a touch hit rect
// because any rects inside of it may move around relative to their
// enclosing composited layer without causing the rects to be
// recomputed. Non-composited scrolling occurs on the main thread, so
// we're not getting much benefit from compositor touch hit testing in
// this case anyway.
if (enclosingNonCompositedScrollLayer)
enclosingNonCompositedScrollLayer->computeSelfHitTestRects(rects);
layoutObject->computeLayerHitTestRects(rects);
}
}
}
}
void ScrollingCoordinator::computeTouchEventTargetRects(
LayerHitTestRects& rects) {
TRACE_EVENT0("input", "ScrollingCoordinator::computeTouchEventTargetRects");
ASSERT(RuntimeEnabledFeatures::touchEnabled());
Document* document = m_page->deprecatedLocalMainFrame()->document();
if (!document || !document->view())
return;
accumulateDocumentTouchEventTargetRects(rects, document);
}
void ScrollingCoordinator::
frameViewHasBackgroundAttachmentFixedObjectsDidChange(
FrameView* frameView) {
ASSERT(isMainThread());
ASSERT(m_page);
if (!coordinatesScrollingForFrameView(frameView))
return;
m_shouldScrollOnMainThreadDirty = true;
}
void ScrollingCoordinator::frameViewFixedObjectsDidChange(
FrameView* frameView) {
ASSERT(isMainThread());
ASSERT(m_page);
if (!coordinatesScrollingForFrameView(frameView))
return;
m_shouldScrollOnMainThreadDirty = true;
}
bool ScrollingCoordinator::isForRootLayer(
ScrollableArea* scrollableArea) const {
if (!m_page->mainFrame()->isLocalFrame())
return false;
// FIXME(305811): Refactor for OOPI.
LayoutViewItem layoutViewItem =
m_page->deprecatedLocalMainFrame()->view()->layoutViewItem();
return layoutViewItem.isNull()
? false
: scrollableArea == layoutViewItem.layer()->getScrollableArea();
}
bool ScrollingCoordinator::isForMainFrame(
ScrollableArea* scrollableArea) const {
if (!m_page->mainFrame()->isLocalFrame())
return false;
// FIXME(305811): Refactor for OOPI.
return scrollableArea == m_page->deprecatedLocalMainFrame()->view();
}
void ScrollingCoordinator::frameViewRootLayerDidChange(FrameView* frameView) {
ASSERT(isMainThread());
ASSERT(m_page);
if (!coordinatesScrollingForFrameView(frameView))
return;
notifyGeometryChanged();
}
#if OS(MACOSX)
void ScrollingCoordinator::handleWheelEventPhase(
PlatformWheelEventPhase phase) {
ASSERT(isMainThread());
if (!m_page)
return;
FrameView* frameView = m_page->deprecatedLocalMainFrame()->view();
if (!frameView)
return;
frameView->scrollAnimator().handleWheelEventPhase(phase);
}
#endif
bool ScrollingCoordinator::hasVisibleSlowRepaintViewportConstrainedObjects(
FrameView* frameView) const {
const FrameView::ViewportConstrainedObjectSet* viewportConstrainedObjects =
frameView->viewportConstrainedObjects();
if (!viewportConstrainedObjects)
return false;
for (const LayoutObject* layoutObject : *viewportConstrainedObjects) {
ASSERT(layoutObject->isBoxModelObject() && layoutObject->hasLayer());
ASSERT(layoutObject->style()->position() == FixedPosition ||
layoutObject->style()->position() == StickyPosition);
PaintLayer* layer = toLayoutBoxModelObject(layoutObject)->layer();
// Whether the Layer scrolls with the viewport is a tree-depenent
// property and our viewportConstrainedObjects collection is maintained
// with only LayoutObject-level information.
if (!layer->scrollsWithViewport())
continue;
// If the whole subtree is invisible, there's no reason to scroll on
// the main thread because we don't need to generate invalidations
// for invisible content.
if (layer->subtreeIsInvisible())
continue;
// We're only smart enough to scroll viewport-constrainted objects
// in the compositor if they have their own backing or they paint
// into a grouped back (which necessarily all have the same viewport
// constraints).
CompositingState compositingState = layer->compositingState();
if (compositingState != PaintsIntoOwnBacking &&
compositingState != PaintsIntoGroupedBacking)
return true;
}
return false;
}
MainThreadScrollingReasons ScrollingCoordinator::mainThreadScrollingReasons()
const {
MainThreadScrollingReasons reasons =
static_cast<MainThreadScrollingReasons>(0);
if (!m_page->settings().threadedScrollingEnabled())
reasons |= MainThreadScrollingReason::kThreadedScrollingDisabled;
if (!m_page->mainFrame()->isLocalFrame())
return reasons;
// TODO(flackr) Currently we combine reasons for main thread scrolling from
// all frames but we should only look at the targetted frame (and its
// ancestors if the scroll bubbles up). http://crbug.com/568901
for (Frame* frame = m_page->mainFrame(); frame;
frame = frame->tree().traverseNext()) {
if (!frame->isLocalFrame())
continue;
// TODO(alexmos,kenrb): For OOPIF, local roots that are different from
// the main frame can't be used in the calculation, since they use
// different compositors with unrelated state, which breaks some of the
// calculations below.
if (toLocalFrame(frame)->localFrameRoot() != m_page->mainFrame())
continue;
FrameView* frameView = toLocalFrame(frame)->view();
if (!frameView || frameView->shouldThrottleRendering())
continue;
if (frameView->hasBackgroundAttachmentFixedObjects())
reasons |=
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
FrameView::ScrollingReasons scrollingReasons =
frameView->getScrollingReasons();
const bool mayBeScrolledByInput =
(scrollingReasons == FrameView::Scrollable);
const bool mayBeScrolledByScript =
mayBeScrolledByInput ||
(scrollingReasons == FrameView::NotScrollableExplicitlyDisabled);
// TODO(awoloszyn) Currently crbug.com/304810 will let certain
// overflow:hidden elements scroll on the compositor thread, so we should
// not let this move there path as an optimization, when we have
// slow-repaint elements.
if (mayBeScrolledByScript &&
hasVisibleSlowRepaintViewportConstrainedObjects(frameView)) {
reasons |=
MainThreadScrollingReason::kHasNonLayerViewportConstrainedObjects;
}
}
return reasons;
}
String ScrollingCoordinator::mainThreadScrollingReasonsAsText() const {
ASSERT(m_page->deprecatedLocalMainFrame()->document()->lifecycle().state() >=
DocumentLifecycle::CompositingClean);
if (WebLayer* scrollLayer = toWebLayer(
m_page->deprecatedLocalMainFrame()->view()->layerForScrolling())) {
String result(MainThreadScrollingReason::mainThreadScrollingReasonsAsText(
scrollLayer->mainThreadScrollingReasons())
.c_str());
return result;
}
String result(MainThreadScrollingReason::mainThreadScrollingReasonsAsText(
m_lastMainThreadScrollingReasons)
.c_str());
return result;
}
bool ScrollingCoordinator::frameViewIsDirty() const {
FrameView* frameView = m_page->mainFrame()->isLocalFrame()
? m_page->deprecatedLocalMainFrame()->view()
: nullptr;
bool frameIsScrollable = frameView && frameView->isScrollable();
if (frameIsScrollable != m_wasFrameScrollable)
return true;
if (WebLayer* scrollLayer =
frameView ? toWebLayer(frameView->layerForScrolling()) : nullptr)
return WebSize(frameView->contentsSize()) != scrollLayer->bounds();
return false;
}
} // namespace blink