blob: 5bf31587930deefad20acae200e2e77b9287bb30 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010, 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. ``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 COMPUTER, INC. OR
* 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/layout/compositing/CompositedLayerMapping.h"
#include <memory>
#include "core/HTMLNames.h"
#include "core/dom/DOMNodeIds.h"
#include "core/frame/FrameView.h"
#include "core/frame/RemoteFrame.h"
#include "core/frame/Settings.h"
#include "core/frame/VisualViewport.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLIFrameElement.h"
#include "core/html/HTMLMediaElement.h"
#include "core/html/HTMLVideoElement.h"
#include "core/html/canvas/CanvasRenderingContext.h"
#include "core/layout/LayoutEmbeddedObject.h"
#include "core/layout/LayoutHTMLCanvas.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/LayoutVideo.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutAPIShim.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/loader/resource/ImageResourceContent.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/page/scrolling/StickyPositionScrollingConstraints.h"
#include "core/page/scrolling/TopDocumentRootScrollerController.h"
#include "core/paint/ObjectPaintInvalidator.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayerPainter.h"
#include "core/paint/PaintLayerStackingNodeIterator.h"
#include "core/paint/ScrollableAreaPainter.h"
#include "core/paint/TransformRecorder.h"
#include "core/plugins/PluginView.h"
#include "core/probe/CoreProbes.h"
#include "platform/LengthFunctions.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/fonts/FontCache.h"
#include "platform/geometry/TransformState.h"
#include "platform/graphics/BitmapImage.h"
#include "platform/graphics/CompositorFilterOperations.h"
#include "platform/graphics/CompositorMutableProperties.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/paint/ClipDisplayItem.h"
#include "platform/graphics/paint/CullRect.h"
#include "platform/graphics/paint/DrawingRecorder.h"
#include "platform/graphics/paint/PaintController.h"
#include "platform/graphics/paint/PaintRecordBuilder.h"
#include "platform/graphics/paint/TransformDisplayItem.h"
#include "public/platform/WebLayerStickyPositionConstraint.h"
#include "wtf/CurrentTime.h"
#include "wtf/text/StringBuilder.h"
namespace blink {
using namespace HTMLNames;
static IntRect clipBox(LayoutBox& layoutObject);
static IntRect contentsRect(const LayoutObject& layoutObject) {
if (!layoutObject.isBox())
return IntRect();
if (layoutObject.isCanvas()) {
return pixelSnappedIntRect(
toLayoutHTMLCanvas(layoutObject).replacedContentRect());
}
if (layoutObject.isVideo()) {
return pixelSnappedIntRect(
toLayoutVideo(layoutObject).replacedContentRect());
}
return pixelSnappedIntRect(toLayoutBox(layoutObject).contentBoxRect());
}
static IntRect backgroundRect(const LayoutObject& layoutObject) {
if (!layoutObject.isBox())
return IntRect();
LayoutRect rect;
const LayoutBox& box = toLayoutBox(layoutObject);
return pixelSnappedIntRect(box.backgroundRect(BackgroundClipRect));
}
static inline bool isCompositedCanvas(const LayoutObject& layoutObject) {
if (layoutObject.isCanvas()) {
HTMLCanvasElement* canvas = toHTMLCanvasElement(layoutObject.node());
if (CanvasRenderingContext* context = canvas->renderingContext())
return context->isComposited();
}
return false;
}
static inline bool isPlaceholderCanvas(const LayoutObject& layoutObject) {
if (layoutObject.isCanvas()) {
HTMLCanvasElement* canvas = toHTMLCanvasElement(layoutObject.node());
return canvas->surfaceLayerBridge();
}
return false;
}
static bool hasBoxDecorationsOrBackgroundImage(const ComputedStyle& style) {
return style.hasBoxDecorations() || style.hasBackgroundImage();
}
static bool contentLayerSupportsDirectBackgroundComposition(
const LayoutObject& layoutObject) {
// No support for decorations - border, border-radius or outline.
// Only simple background - solid color or transparent.
if (hasBoxDecorationsOrBackgroundImage(layoutObject.styleRef()))
return false;
// If there is no background, there is nothing to support.
if (!layoutObject.style()->hasBackground())
return true;
// Simple background that is contained within the contents rect.
return contentsRect(layoutObject).contains(backgroundRect(layoutObject));
}
static WebLayer* platformLayerForPlugin(LayoutObject& layoutObject) {
if (!layoutObject.isEmbeddedObject())
return nullptr;
PluginView* plugin = toLayoutEmbeddedObject(layoutObject).plugin();
return plugin ? plugin->platformLayer() : nullptr;
}
static inline bool isAcceleratedContents(LayoutObject& layoutObject) {
return isCompositedCanvas(layoutObject) ||
(layoutObject.isEmbeddedObject() &&
toLayoutEmbeddedObject(layoutObject)
.requiresAcceleratedCompositing()) ||
layoutObject.isVideo();
}
// Get the scrolling coordinator in a way that works inside
// CompositedLayerMapping's destructor.
static ScrollingCoordinator* scrollingCoordinatorFromLayer(PaintLayer& layer) {
Page* page = layer.layoutObject().frame()->page();
return (!page) ? nullptr : page->scrollingCoordinator();
}
CompositedLayerMapping::CompositedLayerMapping(PaintLayer& layer)
: m_owningLayer(layer),
m_contentOffsetInCompositingLayerDirty(false),
m_pendingUpdateScope(GraphicsLayerUpdateNone),
m_isMainFrameLayoutViewLayer(false),
m_backgroundLayerPaintsFixedRootBackground(false),
m_scrollingContentsAreEmpty(false),
m_backgroundPaintsOntoScrollingContentsLayer(false),
m_backgroundPaintsOntoGraphicsLayer(false),
m_drawsBackgroundOntoContentLayer(false) {
if (layer.isRootLayer() && layoutObject().frame()->isMainFrame())
m_isMainFrameLayoutViewLayer = true;
createPrimaryGraphicsLayer();
}
CompositedLayerMapping::~CompositedLayerMapping() {
// Hits in compositing/squashing/squash-onto-nephew.html.
DisableCompositingQueryAsserts disabler;
// Do not leave the destroyed pointer dangling on any Layers that painted to
// this mapping's squashing layer.
for (size_t i = 0; i < m_squashedLayers.size(); ++i) {
PaintLayer* oldSquashedLayer = m_squashedLayers[i].paintLayer;
// Assert on incorrect mappings between layers and groups
DCHECK_EQ(oldSquashedLayer->groupedMapping(), this);
if (oldSquashedLayer->groupedMapping() == this) {
oldSquashedLayer->setGroupedMapping(
0, PaintLayer::DoNotInvalidateLayerAndRemoveFromMapping);
oldSquashedLayer->setLostGroupedMapping(true);
}
}
updateClippingLayers(false, false, false);
updateOverflowControlsLayers(false, false, false, false);
updateChildTransformLayer(false);
updateForegroundLayer(false);
updateBackgroundLayer(false);
updateMaskLayer(false);
updateChildClippingMaskLayer(false);
updateScrollingLayers(false);
updateSquashingLayers(false);
destroyGraphicsLayers();
}
std::unique_ptr<GraphicsLayer> CompositedLayerMapping::createGraphicsLayer(
CompositingReasons reasons,
SquashingDisallowedReasons squashingDisallowedReasons) {
std::unique_ptr<GraphicsLayer> graphicsLayer = GraphicsLayer::create(this);
graphicsLayer->setCompositingReasons(reasons);
graphicsLayer->setSquashingDisallowedReasons(squashingDisallowedReasons);
if (Node* owningNode = m_owningLayer.layoutObject().node())
graphicsLayer->setOwnerNodeId(DOMNodeIds::idForNode(owningNode));
return graphicsLayer;
}
void CompositedLayerMapping::createPrimaryGraphicsLayer() {
m_graphicsLayer =
createGraphicsLayer(m_owningLayer.getCompositingReasons(),
m_owningLayer.getSquashingDisallowedReasons());
updateOpacity(layoutObject().styleRef());
updateTransform(layoutObject().styleRef());
updateFilters(layoutObject().styleRef());
updateBackdropFilters(layoutObject().styleRef());
updateLayerBlendMode(layoutObject().styleRef());
updateIsRootForIsolatedGroup();
}
void CompositedLayerMapping::destroyGraphicsLayers() {
if (m_graphicsLayer)
m_graphicsLayer->removeFromParent();
m_ancestorClippingLayer = nullptr;
m_ancestorClippingMaskLayer = nullptr;
m_graphicsLayer = nullptr;
m_foregroundLayer = nullptr;
m_backgroundLayer = nullptr;
m_childContainmentLayer = nullptr;
m_childTransformLayer = nullptr;
m_maskLayer = nullptr;
m_childClippingMaskLayer = nullptr;
m_scrollingLayer = nullptr;
m_scrollingContentsLayer = nullptr;
}
void CompositedLayerMapping::updateOpacity(const ComputedStyle& style) {
m_graphicsLayer->setOpacity(compositingOpacity(style.opacity()));
}
void CompositedLayerMapping::updateTransform(const ComputedStyle& style) {
// FIXME: This could use m_owningLayer.transform(), but that currently has
// transform-origin baked into it, and we don't want that.
TransformationMatrix t;
if (m_owningLayer.hasTransformRelatedProperty()) {
style.applyTransform(
t, LayoutSize(toLayoutBox(layoutObject()).pixelSnappedSize()),
ComputedStyle::ExcludeTransformOrigin, ComputedStyle::IncludeMotionPath,
ComputedStyle::IncludeIndependentTransformProperties);
makeMatrixRenderable(t, compositor()->hasAcceleratedCompositing());
}
m_graphicsLayer->setTransform(t);
}
void CompositedLayerMapping::updateFilters(const ComputedStyle& style) {
m_graphicsLayer->setFilters(
owningLayer().createCompositorFilterOperationsForFilter(style));
}
void CompositedLayerMapping::updateBackdropFilters(const ComputedStyle& style) {
m_graphicsLayer->setBackdropFilters(
owningLayer().createCompositorFilterOperationsForBackdropFilter(style));
}
void CompositedLayerMapping::updateStickyConstraints(
const ComputedStyle& style,
const PaintLayer* compositingContainer) {
bool sticky = style.position() == EPosition::kSticky;
const PaintLayer* ancestorOverflowLayer =
m_owningLayer.ancestorOverflowLayer();
// TODO(flackr): Do we still need this?
if (sticky) {
if (!ancestorOverflowLayer->isRootLayer()) {
sticky = ancestorOverflowLayer->needsCompositedScrolling();
} else {
sticky = layoutObject().view()->frameView()->isScrollable();
}
}
WebLayerStickyPositionConstraint webConstraint;
if (sticky) {
const StickyConstraintsMap& constraintsMap =
ancestorOverflowLayer->getScrollableArea()->stickyConstraintsMap();
const StickyPositionScrollingConstraints& constraints =
constraintsMap.at(&m_owningLayer);
// Find the layout offset of the unshifted sticky box within its parent
// composited layer. This information is used by the compositor side to
// compute the additional offset required to keep the element stuck under
// compositor scrolling.
//
// Starting from the scroll container relative location, removing the
// enclosing layer's offset and the content offset in the composited layer
// results in the parent-layer relative offset.
FloatPoint parentRelativeStickyBoxOffset =
constraints.scrollContainerRelativeStickyBoxRect().location();
// The enclosing layers offset returned from |convertToLayerCoords| must be
// adjusted for both scroll and ancestor sticky elements.
LayoutPoint enclosingLayerOffset;
compositingContainer->convertToLayerCoords(ancestorOverflowLayer,
enclosingLayerOffset);
DCHECK(!scrollParent() || scrollParent() == ancestorOverflowLayer);
if (!scrollParent() && compositingContainer != ancestorOverflowLayer) {
enclosingLayerOffset += LayoutSize(
ancestorOverflowLayer->getScrollableArea()->getScrollOffset());
}
// TODO(smcgruer): Until http://crbug.com/702229 is fixed, the nearest
// sticky ancestor may be non-composited which will make this offset wrong.
if (const LayoutBoxModelObject* ancestor =
constraints.nearestStickyAncestor()) {
enclosingLayerOffset -=
roundedIntSize(constraintsMap.at(ancestor->layer())
.getTotalContainingBlockStickyOffset());
}
DCHECK(!m_contentOffsetInCompositingLayerDirty);
parentRelativeStickyBoxOffset.moveBy(
FloatPoint(-enclosingLayerOffset) -
FloatSize(contentOffsetInCompositingLayer()));
webConstraint.isSticky = true;
webConstraint.isAnchoredLeft =
constraints.anchorEdges() &
StickyPositionScrollingConstraints::AnchorEdgeLeft;
webConstraint.isAnchoredRight =
constraints.anchorEdges() &
StickyPositionScrollingConstraints::AnchorEdgeRight;
webConstraint.isAnchoredTop =
constraints.anchorEdges() &
StickyPositionScrollingConstraints::AnchorEdgeTop;
webConstraint.isAnchoredBottom =
constraints.anchorEdges() &
StickyPositionScrollingConstraints::AnchorEdgeBottom;
webConstraint.leftOffset = constraints.leftOffset();
webConstraint.rightOffset = constraints.rightOffset();
webConstraint.topOffset = constraints.topOffset();
webConstraint.bottomOffset = constraints.bottomOffset();
webConstraint.parentRelativeStickyBoxOffset =
roundedIntPoint(parentRelativeStickyBoxOffset);
webConstraint.scrollContainerRelativeStickyBoxRect =
enclosingIntRect(constraints.scrollContainerRelativeStickyBoxRect());
webConstraint.scrollContainerRelativeContainingBlockRect = enclosingIntRect(
constraints.scrollContainerRelativeContainingBlockRect());
// TODO(smcgruer): Until http://crbug.com/702229 is fixed, the nearest
// sticky layers may not be composited and we may incorrectly end up with
// invalid layer IDs.
LayoutBoxModelObject* stickyBoxShiftingAncestor =
constraints.nearestStickyBoxShiftingStickyBox();
if (stickyBoxShiftingAncestor &&
stickyBoxShiftingAncestor->layer()->compositedLayerMapping()) {
webConstraint.nearestLayerShiftingStickyBox =
stickyBoxShiftingAncestor->layer()
->compositedLayerMapping()
->mainGraphicsLayer()
->platformLayer()
->id();
}
LayoutBoxModelObject* containingBlockShiftingAncestor =
constraints.nearestStickyBoxShiftingContainingBlock();
if (containingBlockShiftingAncestor &&
containingBlockShiftingAncestor->layer()->compositedLayerMapping()) {
webConstraint.nearestLayerShiftingContainingBlock =
containingBlockShiftingAncestor->layer()
->compositedLayerMapping()
->mainGraphicsLayer()
->platformLayer()
->id();
}
}
m_graphicsLayer->setStickyPositionConstraint(webConstraint);
}
void CompositedLayerMapping::updateLayerBlendMode(const ComputedStyle& style) {
setBlendMode(style.blendMode());
}
void CompositedLayerMapping::updateIsRootForIsolatedGroup() {
bool isolate = m_owningLayer.shouldIsolateCompositedDescendants();
// non stacking context layers should never isolate
DCHECK(m_owningLayer.stackingNode()->isStackingContext() || !isolate);
m_graphicsLayer->setIsRootForIsolatedGroup(isolate);
}
void CompositedLayerMapping::
updateBackgroundPaintsOntoScrollingContentsLayer() {
// We can only paint the background onto the scrolling contents layer if
// it would be visually correct and we are using composited scrolling meaning
// we have a scrolling contents layer to paint it into.
BackgroundPaintLocation paintLocation =
m_owningLayer.backgroundPaintLocation();
bool shouldPaintOntoScrollingContentsLayer =
paintLocation & BackgroundPaintInScrollingContents &&
m_owningLayer.getScrollableArea()->usesCompositedScrolling();
if (shouldPaintOntoScrollingContentsLayer !=
backgroundPaintsOntoScrollingContentsLayer()) {
m_backgroundPaintsOntoScrollingContentsLayer =
shouldPaintOntoScrollingContentsLayer;
// The scrolling contents layer needs to be updated for changed
// m_backgroundPaintsOntoScrollingContentsLayer.
if (hasScrollingLayer())
m_scrollingContentsLayer->setNeedsDisplay();
}
bool shouldPaintOntoGraphicsLayer =
!m_backgroundPaintsOntoScrollingContentsLayer ||
paintLocation & BackgroundPaintInGraphicsLayer;
if (shouldPaintOntoGraphicsLayer != !!m_backgroundPaintsOntoGraphicsLayer) {
m_backgroundPaintsOntoGraphicsLayer = shouldPaintOntoGraphicsLayer;
// The graphics layer needs to be updated for changed
// m_backgroundPaintsOntoGraphicsLayer.
m_graphicsLayer->setNeedsDisplay();
}
}
void CompositedLayerMapping::updateContentsOpaque() {
if (isCompositedCanvas(layoutObject())) {
CanvasRenderingContext* context =
toHTMLCanvasElement(layoutObject().node())->renderingContext();
WebLayer* layer = context ? context->platformLayer() : nullptr;
// Determine whether the external texture layer covers the whole graphics
// layer. This may not be the case if there are box decorations or
// shadows.
if (layer &&
layer->bounds() == m_graphicsLayer->platformLayer()->bounds()) {
// Determine whether the rendering context's external texture layer is
// opaque.
if (!context->creationAttributes().alpha()) {
m_graphicsLayer->setContentsOpaque(true);
} else {
m_graphicsLayer->setContentsOpaque(
!Color(layer->backgroundColor()).hasAlpha());
}
} else {
m_graphicsLayer->setContentsOpaque(false);
}
} else if (m_backgroundLayer) {
m_graphicsLayer->setContentsOpaque(false);
m_backgroundLayer->setContentsOpaque(
m_owningLayer.backgroundIsKnownToBeOpaqueInRect(compositedBounds()));
} else if (isPlaceholderCanvas(layoutObject())) {
// TODO(crbug.com/705019): Contents could be opaque, but that cannot be
// determined from the main thread. Or can it?
m_graphicsLayer->setContentsOpaque(false);
} else {
// For non-root layers, background is painted by the scrolling contents
// layer if all backgrounds are background attachment local, otherwise
// background is painted by the primary graphics layer.
if (hasScrollingLayer() && m_backgroundPaintsOntoScrollingContentsLayer) {
// Backgrounds painted onto the foreground are clipped by the padding box
// rect.
// TODO(flackr): This should actually check the entire overflow rect
// within the scrolling contents layer but since we currently only trigger
// this for solid color backgrounds the answer will be the same.
m_scrollingContentsLayer->setContentsOpaque(
m_owningLayer.backgroundIsKnownToBeOpaqueInRect(
toLayoutBox(layoutObject()).paddingBoxRect()));
if (m_owningLayer.backgroundPaintLocation() &
BackgroundPaintInGraphicsLayer) {
m_graphicsLayer->setContentsOpaque(
m_owningLayer.backgroundIsKnownToBeOpaqueInRect(
compositedBounds()));
} else {
// If we only paint the background onto the scrolling contents layer we
// are going to leave a hole in the m_graphicsLayer where the background
// is so it is not opaque.
m_graphicsLayer->setContentsOpaque(false);
}
} else {
if (hasScrollingLayer())
m_scrollingContentsLayer->setContentsOpaque(false);
m_graphicsLayer->setContentsOpaque(
m_owningLayer.backgroundIsKnownToBeOpaqueInRect(compositedBounds()));
}
}
}
void CompositedLayerMapping::updateRasterizationPolicy() {
bool allowTransformedRasterization =
!requiresCompositing(m_owningLayer.getCompositingReasons() &
~CompositingReasonSquashingDisallowed);
m_graphicsLayer->contentLayer()->setAllowTransformedRasterization(
allowTransformedRasterization);
if (m_squashingLayer)
m_squashingLayer->contentLayer()->setAllowTransformedRasterization(true);
}
void CompositedLayerMapping::updateCompositedBounds() {
DCHECK_EQ(m_owningLayer.compositor()->lifecycle().state(),
DocumentLifecycle::InCompositingUpdate);
// FIXME: if this is really needed for performance, it would be better to
// store it on Layer.
m_compositedBounds = m_owningLayer.boundingBoxForCompositing();
m_contentOffsetInCompositingLayerDirty = true;
}
void CompositedLayerMapping::updateAfterPartResize() {
if (layoutObject().isLayoutPart()) {
if (PaintLayerCompositor* innerCompositor =
PaintLayerCompositor::frameContentsCompositor(
toLayoutPart(layoutObject()))) {
innerCompositor->frameViewDidChangeSize();
// We can floor this point because our frameviews are always aligned to
// pixel boundaries.
DCHECK(m_compositedBounds.location() ==
flooredIntPoint(m_compositedBounds.location()));
innerCompositor->frameViewDidChangeLocation(
flooredIntPoint(contentsBox().location()));
}
}
}
void CompositedLayerMapping::updateCompositingReasons() {
// All other layers owned by this mapping will have the same compositing
// reason for their lifetime, so they are initialized only when created.
m_graphicsLayer->setCompositingReasons(m_owningLayer.getCompositingReasons());
m_graphicsLayer->setSquashingDisallowedReasons(
m_owningLayer.getSquashingDisallowedReasons());
}
bool CompositedLayerMapping::ancestorRoundedCornersWontClip(
const LayoutBoxModelObject& child,
const LayoutBoxModelObject& clippingAncestor) {
LayoutRect localVisualRect = m_compositedBounds;
child.mapToVisualRectInAncestorSpace(&clippingAncestor, localVisualRect);
FloatRoundedRect roundedClipRect =
clippingAncestor.style()->getRoundedInnerBorderFor(
clippingAncestor.localVisualRect());
FloatRect innerClipRect = roundedClipRect.radiusCenterRect();
// The first condition catches cases where the child is certainly inside
// the rounded corner portion of the border, and cannot be clipped by
// the rounded portion. The second catches cases where the child is
// entirely outside the rectangular border (ignoring rounded corners) so
// is also unaffected by the rounded corners. In both cases the existing
// rectangular clip is adequate and the mask is unnecessary.
return innerClipRect.contains(FloatRect(localVisualRect)) ||
!localVisualRect.intersects(
enclosingLayoutRect(roundedClipRect.rect()));
}
void CompositedLayerMapping::
owningLayerClippedOrMaskedByLayerNotAboveCompositedAncestor(
const PaintLayer* scrollParent,
bool& owningLayerIsClipped,
bool& owningLayerIsMasked) {
owningLayerIsClipped = false;
owningLayerIsMasked = false;
if (!m_owningLayer.parent())
return;
const PaintLayer* compositingAncestor =
m_owningLayer.enclosingLayerWithCompositedLayerMapping(ExcludeSelf);
if (!compositingAncestor)
return;
const LayoutBoxModelObject* clippingContainer =
m_owningLayer.clippingContainer();
if (!clippingContainer)
return;
if (clippingContainer->enclosingLayer() == scrollParent)
return;
if (compositingAncestor->layoutObject().isDescendantOf(clippingContainer))
return;
// We ignore overflow clip here; we want composited overflow content to
// behave as if it lives in an unclipped universe so it can prepaint, etc.
// This means that we need to check if we are actually clipped before
// setting up m_ancestorClippingLayer otherwise
// updateAncestorClippingLayerGeometry will fail as the clip rect will be
// infinite.
// FIXME: this should use cached clip rects, but this sometimes give
// inaccurate results (and trips the ASSERTS in PaintLayerClipper).
ClipRectsContext clipRectsContext(compositingAncestor, UncachedClipRects,
IgnorePlatformOverlayScrollbarSize);
clipRectsContext.setIgnoreOverflowClip();
ClipRect clipRect;
m_owningLayer.clipper(PaintLayer::DoNotUseGeometryMapper)
.calculateBackgroundClipRect(clipRectsContext, clipRect);
IntRect parentClipRect = pixelSnappedIntRect(clipRect.rect());
owningLayerIsClipped = parentClipRect != LayoutRect::infiniteIntRect();
// TODO(schenney): CSS clips are not applied to composited children, and
// should be via mask or by compositing the parent too.
// https://bugs.chromium.org/p/chromium/issues/detail?id=615870
DCHECK(clippingContainer->style());
owningLayerIsMasked =
owningLayerIsClipped && clippingContainer->style()->hasBorderRadius() &&
!ancestorRoundedCornersWontClip(layoutObject(), *clippingContainer);
}
const PaintLayer* CompositedLayerMapping::scrollParent() {
const PaintLayer* scrollParent = m_owningLayer.scrollParent();
if (scrollParent && !scrollParent->needsCompositedScrolling())
return nullptr;
return scrollParent;
}
bool CompositedLayerMapping::updateGraphicsLayerConfiguration() {
DCHECK_EQ(m_owningLayer.compositor()->lifecycle().state(),
DocumentLifecycle::InCompositingUpdate);
// Note carefully: here we assume that the compositing state of all
// descendants have been updated already, so it is legitimate to compute and
// cache the composited bounds for this layer.
updateCompositedBounds();
PaintLayerCompositor* compositor = this->compositor();
LayoutObject& layoutObject = this->layoutObject();
const ComputedStyle& style = layoutObject.styleRef();
bool layerConfigChanged = false;
setBackgroundLayerPaintsFixedRootBackground(
compositor->needsFixedRootBackgroundLayer(&m_owningLayer));
// The background layer is currently only used for fixed root backgrounds.
if (updateBackgroundLayer(m_backgroundLayerPaintsFixedRootBackground))
layerConfigChanged = true;
if (updateForegroundLayer(
compositor->needsContentsCompositingLayer(&m_owningLayer)))
layerConfigChanged = true;
bool needsDescendantsClippingLayer =
compositor->clipsCompositingDescendants(&m_owningLayer);
// Our scrolling layer will clip.
if (m_owningLayer.needsCompositedScrolling())
needsDescendantsClippingLayer = false;
const PaintLayer* scrollParent = this->scrollParent();
// This is required because compositing layers are parented according to the
// z-order hierarchy, yet clipping goes down the layoutObject hierarchy. Thus,
// a PaintLayer can be clipped by a PaintLayer that is an ancestor in the
// layoutObject hierarchy, but a sibling in the z-order hierarchy. Further,
// that sibling need not be composited at all. In such scenarios, an ancestor
// clipping layer is necessary to apply the composited clip for this layer.
bool needsAncestorClip = false;
bool needsAncestorClippingMask = false;
owningLayerClippedOrMaskedByLayerNotAboveCompositedAncestor(
scrollParent, needsAncestorClip, needsAncestorClippingMask);
if (updateClippingLayers(needsAncestorClip, needsAncestorClippingMask,
needsDescendantsClippingLayer))
layerConfigChanged = true;
bool scrollingConfigChanged = false;
if (updateScrollingLayers(m_owningLayer.needsCompositedScrolling())) {
layerConfigChanged = true;
scrollingConfigChanged = true;
}
// If the outline needs to draw over the composited scrolling contents layer
// or scrollbar layers it needs to be drawn into a separate layer.
int minBorderWidth =
std::min(layoutObject.style()->borderTopWidth(),
std::min(layoutObject.style()->borderLeftWidth(),
std::min(layoutObject.style()->borderRightWidth(),
layoutObject.style()->borderBottomWidth())));
bool needsDecorationOutlineLayer =
m_owningLayer.getScrollableArea() &&
m_owningLayer.getScrollableArea()->usesCompositedScrolling() &&
layoutObject.style()->hasOutline() &&
layoutObject.style()->outlineOffset() < -minBorderWidth;
if (updateDecorationOutlineLayer(needsDecorationOutlineLayer))
layerConfigChanged = true;
if (updateOverflowControlsLayers(
requiresHorizontalScrollbarLayer(), requiresVerticalScrollbarLayer(),
requiresScrollCornerLayer(), needsAncestorClip))
layerConfigChanged = true;
bool hasPerspective = style.hasPerspective();
bool needsChildTransformLayer = hasPerspective && layoutObject.isBox();
if (updateChildTransformLayer(needsChildTransformLayer))
layerConfigChanged = true;
if (updateSquashingLayers(!m_squashedLayers.isEmpty()))
layerConfigChanged = true;
updateScrollParent(scrollParent);
updateClipParent(scrollParent);
if (layerConfigChanged)
updateInternalHierarchy();
if (scrollingConfigChanged) {
if (layoutObject.view())
compositor->scrollingLayerDidChange(&m_owningLayer);
}
// A mask layer is not part of the hierarchy proper, it's an auxiliary layer
// that's plugged into another GraphicsLayer that is part of the hierarchy.
// It has no parent or child GraphicsLayer. For that reason, we process it
// here, after the hierarchy has been updated.
bool maskLayerChanged = updateMaskLayer(layoutObject.hasMask());
if (maskLayerChanged)
m_graphicsLayer->setMaskLayer(m_maskLayer.get());
bool hasChildClippingLayer =
compositor->clipsCompositingDescendants(&m_owningLayer) &&
(hasClippingLayer() || hasScrollingLayer());
// If we have a border radius or clip path on a scrolling layer, we need a
// clipping mask to properly clip the scrolled contents, even if there are no
// composited descendants.
bool hasClipPath = style.clipPath();
bool needsChildClippingMask =
(hasClipPath || style.hasBorderRadius()) &&
(hasChildClippingLayer || isAcceleratedContents(layoutObject) ||
hasScrollingLayer());
GraphicsLayer* layerToApplyChildClippingMask = nullptr;
bool shouldApplyChildClippingMaskOnContents = false;
if (needsChildClippingMask) {
if (hasClipPath) {
// Clip path clips the entire subtree, including scrollbars. It must be
// attached directly onto the main m_graphicsLayer.
layerToApplyChildClippingMask = m_graphicsLayer.get();
} else if (hasClippingLayer()) {
layerToApplyChildClippingMask = clippingLayer();
} else if (hasScrollingLayer()) {
layerToApplyChildClippingMask = scrollingLayer();
} else if (isAcceleratedContents(layoutObject)) {
shouldApplyChildClippingMaskOnContents = true;
}
}
updateChildClippingMaskLayer(needsChildClippingMask);
if (layerToApplyChildClippingMask == m_graphicsLayer.get()) {
if (m_graphicsLayer->maskLayer() != m_childClippingMaskLayer.get()) {
m_graphicsLayer->setMaskLayer(m_childClippingMaskLayer.get());
maskLayerChanged = true;
}
} else if (m_graphicsLayer->maskLayer() &&
m_graphicsLayer->maskLayer() != m_maskLayer.get()) {
m_graphicsLayer->setMaskLayer(nullptr);
maskLayerChanged = true;
}
if (hasClippingLayer())
clippingLayer()->setMaskLayer(layerToApplyChildClippingMask ==
clippingLayer()
? m_childClippingMaskLayer.get()
: nullptr);
if (hasScrollingLayer())
scrollingLayer()->setMaskLayer(layerToApplyChildClippingMask ==
scrollingLayer()
? m_childClippingMaskLayer.get()
: nullptr);
m_graphicsLayer->setContentsClippingMaskLayer(
shouldApplyChildClippingMaskOnContents ? m_childClippingMaskLayer.get()
: nullptr);
updateBackgroundColor();
if (layoutObject.isImage()) {
if (isDirectlyCompositedImage()) {
updateImageContents();
} else if (m_graphicsLayer->hasContentsLayer()) {
m_graphicsLayer->setContentsToImage(nullptr);
}
}
if (WebLayer* layer = platformLayerForPlugin(layoutObject)) {
m_graphicsLayer->setContentsToPlatformLayer(layer);
} else if (layoutObject.node() &&
layoutObject.node()->isFrameOwnerElement() &&
toHTMLFrameOwnerElement(layoutObject.node())->contentFrame()) {
Frame* frame = toHTMLFrameOwnerElement(layoutObject.node())->contentFrame();
if (frame->isRemoteFrame()) {
WebLayer* layer = toRemoteFrame(frame)->webLayer();
m_graphicsLayer->setContentsToPlatformLayer(layer);
}
} else if (layoutObject.isVideo()) {
HTMLMediaElement* mediaElement = toHTMLMediaElement(layoutObject.node());
m_graphicsLayer->setContentsToPlatformLayer(mediaElement->platformLayer());
} else if (isPlaceholderCanvas(layoutObject)) {
HTMLCanvasElement* canvas = toHTMLCanvasElement(layoutObject.node());
m_graphicsLayer->setContentsToPlatformLayer(
canvas->surfaceLayerBridge()->getWebLayer());
layerConfigChanged = true;
} else if (isCompositedCanvas(layoutObject)) {
HTMLCanvasElement* canvas = toHTMLCanvasElement(layoutObject.node());
if (CanvasRenderingContext* context = canvas->renderingContext())
m_graphicsLayer->setContentsToPlatformLayer(context->platformLayer());
layerConfigChanged = true;
}
if (layoutObject.isLayoutPart()) {
if (PaintLayerCompositor::attachFrameContentLayersToIframeLayer(
toLayoutPart(layoutObject)))
layerConfigChanged = true;
}
// Changes to either the internal hierarchy or the mask layer have an impact
// on painting phases, so we need to update when either are updated.
if (layerConfigChanged || maskLayerChanged)
updatePaintingPhases();
updateElementIdAndCompositorMutableProperties();
m_graphicsLayer->setHasWillChangeTransformHint(
style.hasWillChangeTransformHint());
if (style.preserves3D() && style.hasOpacity() &&
m_owningLayer.has3DTransformedDescendant())
UseCounter::count(layoutObject.document(),
UseCounter::OpacityWithPreserve3DQuirk);
return layerConfigChanged;
}
static IntRect clipBox(LayoutBox& layoutObject) {
// TODO(chrishtr): pixel snapping is most likely incorrect here.
return pixelSnappedIntRect(layoutObject.clippingRect());
}
static LayoutPoint computeOffsetFromCompositedAncestor(
const PaintLayer* layer,
const PaintLayer* compositedAncestor,
const LayoutPoint& localRepresentativePointForFragmentation) {
// Add in the offset of the composited bounds from the coordinate space of
// the PaintLayer, since visualOffsetFromAncestor() requires the pre-offset
// input to be in the space of the PaintLayer. We also need to add in this
// offset before computation of visualOffsetFromAncestor(), because it affects
// fragmentation offset if compositedAncestor crosses a pagination boundary.
//
// Currently, visual fragmentation for composited layers is not implemented.
// For fragmented contents, we paint in the logical coordinates of the flow
// thread, then split the result by fragment boundary and paste each part
// into each fragment's physical position.
// Since composited layers don't support visual fragmentation, we have to
// choose a "representative" fragment to position the painted contents. This
// is where localRepresentativePointForFragmentation comes into play.
// The fragment that the representative point resides in will be chosen as
// the representative fragment for layer position purpose.
// For layers that are not fragmented, the point doesn't affect behavior as
// there is one and only one fragment.
LayoutPoint offset = layer->visualOffsetFromAncestor(
compositedAncestor, localRepresentativePointForFragmentation);
if (compositedAncestor)
offset.move(compositedAncestor->compositedLayerMapping()
->owningLayer()
.subpixelAccumulation());
offset.moveBy(-localRepresentativePointForFragmentation);
return offset;
}
void CompositedLayerMapping::computeBoundsOfOwningLayer(
const PaintLayer* compositedAncestor,
IntRect& localBounds,
IntRect& compositingBoundsRelativeToCompositedAncestor,
LayoutPoint& offsetFromCompositedAncestor,
IntPoint& snappedOffsetFromCompositedAncestor) {
LayoutRect localRawCompositingBounds = compositedBounds();
offsetFromCompositedAncestor = computeOffsetFromCompositedAncestor(
&m_owningLayer, compositedAncestor, localRawCompositingBounds.location());
snappedOffsetFromCompositedAncestor =
IntPoint(offsetFromCompositedAncestor.x().round(),
offsetFromCompositedAncestor.y().round());
LayoutSize subpixelAccumulation =
offsetFromCompositedAncestor - snappedOffsetFromCompositedAncestor;
m_owningLayer.setSubpixelAccumulation(subpixelAccumulation);
// Move the bounds by the subpixel accumulation so that it pixel-snaps
// relative to absolute pixels instead of local coordinates.
localRawCompositingBounds.move(subpixelAccumulation);
localBounds = pixelSnappedIntRect(localRawCompositingBounds);
compositingBoundsRelativeToCompositedAncestor = localBounds;
compositingBoundsRelativeToCompositedAncestor.moveBy(
snappedOffsetFromCompositedAncestor);
}
void CompositedLayerMapping::updateSquashingLayerGeometry(
const IntPoint& graphicsLayerParentLocation,
const PaintLayer* compositingContainer,
Vector<GraphicsLayerPaintInfo>& layers,
GraphicsLayer* squashingLayer,
LayoutPoint* offsetFromTransformedAncestor,
Vector<PaintLayer*>& layersNeedingPaintInvalidation) {
if (!squashingLayer)
return;
LayoutPoint compositingContainerOffsetFromParentGraphicsLayer =
-graphicsLayerParentLocation;
if (compositingContainer)
compositingContainerOffsetFromParentGraphicsLayer +=
compositingContainer->subpixelAccumulation();
#if 0 && DCHECK_IS_ON()
// TODO(trchen): We should enable this for below comment out |DCHECK()| once
// we have simple reproduce case and fix it. See http://crbug.com/646437 for
// details.
const PaintLayer* commonTransformAncestor = nullptr;
if (compositingContainer && compositingContainer->transform())
commonTransformAncestor = compositingContainer;
else if (compositingContainer)
commonTransformAncestor = compositingContainer->transformAncestor();
#endif
// FIXME: Cache these offsets.
LayoutPoint compositingContainerOffsetFromTransformedAncestor;
if (compositingContainer && !compositingContainer->transform())
compositingContainerOffsetFromTransformedAncestor =
compositingContainer->computeOffsetFromTransformedAncestor();
LayoutRect totalSquashBounds;
for (size_t i = 0; i < layers.size(); ++i) {
LayoutRect squashedBounds =
layers[i].paintLayer->boundingBoxForCompositing();
// Store the local bounds of the Layer subtree before applying the offset.
layers[i].compositedBounds = squashedBounds;
#if 0 && DCHECK_IS_ON()
// TODO(trchen): We should enable this |DCHECK()| once we have simple
// reproduce case and fix it. See http://crbug.com/646437 for details.
DCHECK(layers[i].paintLayer->transformAncestor() ==
commonTransformAncestor);
#endif
LayoutPoint squashedLayerOffsetFromTransformedAncestor =
layers[i].paintLayer->computeOffsetFromTransformedAncestor();
LayoutSize squashedLayerOffsetFromCompositingContainer =
squashedLayerOffsetFromTransformedAncestor -
compositingContainerOffsetFromTransformedAncestor;
squashedBounds.move(squashedLayerOffsetFromCompositingContainer);
totalSquashBounds.unite(squashedBounds);
}
// The totalSquashBounds is positioned with respect to compositingContainer.
// But the squashingLayer needs to be positioned with respect to the
// graphicsLayerParent. The conversion between compositingContainer and the
// graphicsLayerParent is already computed as
// compositingContainerOffsetFromParentGraphicsLayer.
totalSquashBounds.moveBy(compositingContainerOffsetFromParentGraphicsLayer);
const IntRect squashLayerBounds = enclosingIntRect(totalSquashBounds);
const IntPoint squashLayerOrigin = squashLayerBounds.location();
const LayoutSize squashLayerOriginInCompositingContainerSpace =
squashLayerOrigin - compositingContainerOffsetFromParentGraphicsLayer;
// Now that the squashing bounds are known, we can convert the PaintLayer
// painting offsets from compositingContainer space to the squashing layer
// space.
//
// The painting offset we want to compute for each squashed PaintLayer is
// essentially the position of the squashed PaintLayer described w.r.t.
// compositingContainer's origin. So we just need to convert that point from
// compositingContainer space to the squashing layer's space. This is done by
// subtracting squashLayerOriginInCompositingContainerSpace, but then the
// offset overall needs to be negated because that's the direction that the
// painting code expects the offset to be.
for (size_t i = 0; i < layers.size(); ++i) {
const LayoutPoint squashedLayerOffsetFromTransformedAncestor =
layers[i].paintLayer->computeOffsetFromTransformedAncestor();
const LayoutSize offsetFromSquashLayerOrigin =
(squashedLayerOffsetFromTransformedAncestor -
compositingContainerOffsetFromTransformedAncestor) -
squashLayerOriginInCompositingContainerSpace;
IntSize newOffsetFromLayoutObject =
-IntSize(offsetFromSquashLayerOrigin.width().round(),
offsetFromSquashLayerOrigin.height().round());
LayoutSize subpixelAccumulation =
offsetFromSquashLayerOrigin + newOffsetFromLayoutObject;
if (layers[i].offsetFromLayoutObjectSet &&
layers[i].offsetFromLayoutObject != newOffsetFromLayoutObject) {
// It is ok to issue paint invalidation here, because all of the geometry
// needed to correctly invalidate paint is computed by this point.
DisablePaintInvalidationStateAsserts disabler;
ObjectPaintInvalidator(layers[i].paintLayer->layoutObject())
.invalidatePaintIncludingNonCompositingDescendants();
TRACE_LAYER_INVALIDATION(layers[i].paintLayer,
InspectorLayerInvalidationTrackingEvent::
SquashingLayerGeometryWasUpdated);
layersNeedingPaintInvalidation.push_back(layers[i].paintLayer);
}
layers[i].offsetFromLayoutObject = newOffsetFromLayoutObject;
layers[i].offsetFromLayoutObjectSet = true;
layers[i].paintLayer->setSubpixelAccumulation(subpixelAccumulation);
}
squashingLayer->setPosition(squashLayerBounds.location());
squashingLayer->setSize(FloatSize(squashLayerBounds.size()));
*offsetFromTransformedAncestor =
compositingContainerOffsetFromTransformedAncestor;
offsetFromTransformedAncestor->move(
squashLayerOriginInCompositingContainerSpace);
for (size_t i = 0; i < layers.size(); ++i)
layers[i].localClipRectForSquashedLayer =
localClipRectForSquashedLayer(m_owningLayer, layers[i], layers);
}
void CompositedLayerMapping::updateGraphicsLayerGeometry(
const PaintLayer* compositingContainer,
const PaintLayer* compositingStackingContext,
Vector<PaintLayer*>& layersNeedingPaintInvalidation) {
DCHECK_EQ(m_owningLayer.compositor()->lifecycle().state(),
DocumentLifecycle::InCompositingUpdate);
// Set transform property, if it is not animating. We have to do this here
// because the transform is affected by the layer dimensions.
if (!layoutObject().style()->isRunningTransformAnimationOnCompositor())
updateTransform(layoutObject().styleRef());
// Set opacity, if it is not animating.
if (!layoutObject().style()->isRunningOpacityAnimationOnCompositor())
updateOpacity(layoutObject().styleRef());
if (!layoutObject().style()->isRunningFilterAnimationOnCompositor())
updateFilters(layoutObject().styleRef());
if (!layoutObject().style()->isRunningBackdropFilterAnimationOnCompositor())
updateBackdropFilters(layoutObject().styleRef());
// We compute everything relative to the enclosing compositing layer.
IntRect ancestorCompositingBounds;
if (compositingContainer) {
DCHECK(compositingContainer->hasCompositedLayerMapping());
ancestorCompositingBounds = compositingContainer->compositedLayerMapping()
->pixelSnappedCompositedBounds();
}
IntRect localCompositingBounds;
IntRect relativeCompositingBounds;
LayoutPoint offsetFromCompositedAncestor;
IntPoint snappedOffsetFromCompositedAncestor;
computeBoundsOfOwningLayer(
compositingContainer, localCompositingBounds, relativeCompositingBounds,
offsetFromCompositedAncestor, snappedOffsetFromCompositedAncestor);
IntPoint graphicsLayerParentLocation;
computeGraphicsLayerParentLocation(compositingContainer,
ancestorCompositingBounds,
graphicsLayerParentLocation);
// Might update graphicsLayerParentLocation.
updateAncestorClippingLayerGeometry(compositingContainer,
snappedOffsetFromCompositedAncestor,
graphicsLayerParentLocation);
FloatSize contentsSize(relativeCompositingBounds.size());
updateMainGraphicsLayerGeometry(relativeCompositingBounds,
localCompositingBounds,
graphicsLayerParentLocation);
updateOverflowControlsHostLayerGeometry(compositingStackingContext,
compositingContainer,
graphicsLayerParentLocation);
updateContentsOffsetInCompositingLayer(snappedOffsetFromCompositedAncestor,
graphicsLayerParentLocation);
updateStickyConstraints(layoutObject().styleRef(), compositingContainer);
updateSquashingLayerGeometry(
graphicsLayerParentLocation, compositingContainer, m_squashedLayers,
m_squashingLayer.get(), &m_squashingLayerOffsetFromTransformedAncestor,
layersNeedingPaintInvalidation);
// If we have a layer that clips children, position it.
IntRect clippingBox;
if (m_childContainmentLayer && layoutObject().isBox())
clippingBox = clipBox(toLayoutBox(layoutObject()));
updateChildTransformLayerGeometry();
updateChildContainmentLayerGeometry(clippingBox, localCompositingBounds);
updateMaskLayerGeometry();
updateTransformGeometry(snappedOffsetFromCompositedAncestor,
relativeCompositingBounds);
updateForegroundLayerGeometry(contentsSize, clippingBox);
updateBackgroundLayerGeometry(contentsSize);
// TODO(yigu): Currently the decoration layer uses the same contentSize
// as background layer and foreground layer. There are scenarios that
// the sizes could be different. The actual size of the decoration layer
// should be calculated separately.
// The size of the background layer should be different as well. We need to
// check whether we are painting the decoration layer into the background and
// then ignore or consider the outline when determining the contentSize.
updateDecorationOutlineLayerGeometry(contentsSize);
updateScrollingLayerGeometry(localCompositingBounds);
updateChildClippingMaskLayerGeometry();
if (m_owningLayer.getScrollableArea() &&
m_owningLayer.getScrollableArea()->scrollsOverflow())
m_owningLayer.getScrollableArea()->positionOverflowControls();
updateLayerBlendMode(layoutObject().styleRef());
updateIsRootForIsolatedGroup();
updateContentsRect();
updateBackgroundColor();
updateDrawsContent();
updateElementIdAndCompositorMutableProperties();
updateBackgroundPaintsOntoScrollingContentsLayer();
updateContentsOpaque();
updateRasterizationPolicy();
updateAfterPartResize();
updateRenderingContext();
updateShouldFlattenTransform();
updateChildrenTransform();
updateScrollParent(scrollParent());
registerScrollingLayers();
updateCompositingReasons();
}
void CompositedLayerMapping::updateMainGraphicsLayerGeometry(
const IntRect& relativeCompositingBounds,
const IntRect& localCompositingBounds,
const IntPoint& graphicsLayerParentLocation) {
m_graphicsLayer->setPosition(FloatPoint(relativeCompositingBounds.location() -
graphicsLayerParentLocation));
m_graphicsLayer->setOffsetFromLayoutObject(
toIntSize(localCompositingBounds.location()));
FloatSize oldSize = m_graphicsLayer->size();
const FloatSize contentsSize(relativeCompositingBounds.size());
if (oldSize != contentsSize)
m_graphicsLayer->setSize(contentsSize);
// m_graphicsLayer is the corresponding GraphicsLayer for this PaintLayer and
// its non-compositing descendants. So, the visibility flag for
// m_graphicsLayer should be true if there are any non-compositing visible
// layers.
bool contentsVisible = m_owningLayer.hasVisibleContent() ||
hasVisibleNonCompositingDescendant(&m_owningLayer);
m_graphicsLayer->setContentsVisible(contentsVisible);
m_graphicsLayer->setBackfaceVisibility(
layoutObject().style()->backfaceVisibility() ==
BackfaceVisibilityVisible);
}
void CompositedLayerMapping::computeGraphicsLayerParentLocation(
const PaintLayer* compositingContainer,
const IntRect& ancestorCompositingBounds,
IntPoint& graphicsLayerParentLocation) {
if (compositingContainer &&
compositingContainer->compositedLayerMapping()->hasClippingLayer() &&
compositingContainer->layoutObject().isBox()) {
// If the compositing ancestor has a layer to clip children, we parent in
// that, and therefore position relative to it.
IntRect clippingBox =
clipBox(toLayoutBox(compositingContainer->layoutObject()));
graphicsLayerParentLocation =
clippingBox.location() +
roundedIntSize(compositingContainer->subpixelAccumulation());
} else if (compositingContainer &&
compositingContainer->compositedLayerMapping()
->childTransformLayer()) {
// Similarly, if the compositing ancestor has a child transform layer, we
// parent in that, and therefore position relative to it. It's already taken
// into account the contents offset, so we do not need to here.
graphicsLayerParentLocation =
roundedIntPoint(compositingContainer->subpixelAccumulation());
} else if (compositingContainer) {
graphicsLayerParentLocation = ancestorCompositingBounds.location();
} else {
graphicsLayerParentLocation =
layoutObject().view()->documentRect().location();
}
if (compositingContainer &&
compositingContainer->needsCompositedScrolling()) {
LayoutBox& layoutBox = toLayoutBox(compositingContainer->layoutObject());
IntSize scrollOffset = layoutBox.scrolledContentOffset();
IntPoint scrollOrigin =
compositingContainer->getScrollableArea()->scrollOrigin();
scrollOrigin.move(-layoutBox.borderLeft().toInt(),
-layoutBox.borderTop().toInt());
graphicsLayerParentLocation = -(scrollOrigin + scrollOffset);
}
}
void CompositedLayerMapping::updateAncestorClippingLayerGeometry(
const PaintLayer* compositingContainer,
const IntPoint& snappedOffsetFromCompositedAncestor,
IntPoint& graphicsLayerParentLocation) {
if (!compositingContainer || !m_ancestorClippingLayer)
return;
ClipRectsContext clipRectsContext(compositingContainer,
PaintingClipRectsIgnoringOverflowClip,
IgnorePlatformOverlayScrollbarSize);
ClipRect parentClipRect;
m_owningLayer.clipper(PaintLayer::DoNotUseGeometryMapper)
.calculateBackgroundClipRect(clipRectsContext, parentClipRect);
IntRect snappedParentClipRect(pixelSnappedIntRect(parentClipRect.rect()));
DCHECK(snappedParentClipRect != LayoutRect::infiniteIntRect());
m_ancestorClippingLayer->setPosition(FloatPoint(
snappedParentClipRect.location() - graphicsLayerParentLocation));
m_ancestorClippingLayer->setSize(FloatSize(snappedParentClipRect.size()));
// backgroundRect is relative to compositingContainer, so subtract
// snappedOffsetFromCompositedAncestor.X/snappedOffsetFromCompositedAncestor.Y
// to get back to local coords.
m_ancestorClippingLayer->setOffsetFromLayoutObject(
snappedParentClipRect.location() - snappedOffsetFromCompositedAncestor);
if (m_ancestorClippingMaskLayer) {
m_ancestorClippingMaskLayer->setOffsetFromLayoutObject(
m_ancestorClippingLayer->offsetFromLayoutObject());
m_ancestorClippingMaskLayer->setSize(m_ancestorClippingLayer->size());
m_ancestorClippingMaskLayer->setNeedsDisplay();
}
// The primary layer is then parented in, and positioned relative to this
// clipping layer.
graphicsLayerParentLocation = snappedParentClipRect.location();
}
void CompositedLayerMapping::updateOverflowControlsHostLayerGeometry(
const PaintLayer* compositingStackingContext,
const PaintLayer* compositingContainer,
IntPoint graphicsLayerParentLocation) {
if (!m_overflowControlsHostLayer)
return;
// To position and clip the scrollbars correctly, m_overflowControlsHostLayer
// should match our border box rect, which is at the origin of our
// LayoutObject. Its position is computed in various ways depending on who its
// parent GraphicsLayer is going to be.
LayoutPoint hostLayerPosition;
if (needsToReparentOverflowControls()) {
CompositedLayerMapping* stackingCLM =
compositingStackingContext->compositedLayerMapping();
DCHECK(stackingCLM);
// Either m_overflowControlsHostLayer or
// m_overflowControlsAncestorClippingLayer (if it exists) will be a child of
// the main GraphicsLayer of the compositing stacking context.
IntSize stackingOffsetFromLayoutObject =
stackingCLM->mainGraphicsLayer()->offsetFromLayoutObject();
if (m_overflowControlsAncestorClippingLayer) {
m_overflowControlsAncestorClippingLayer->setSize(
m_ancestorClippingLayer->size());
m_overflowControlsAncestorClippingLayer->setOffsetFromLayoutObject(
m_ancestorClippingLayer->offsetFromLayoutObject());
m_overflowControlsAncestorClippingLayer->setMasksToBounds(true);
FloatPoint position;
if (compositingStackingContext == compositingContainer) {
position = m_ancestorClippingLayer->position();
} else {
// graphicsLayerParentLocation is the location of
// m_ancestorClippingLayer relative to compositingContainer (including
// any offset from compositingContainer's m_childContainmentLayer).
LayoutPoint offset = LayoutPoint(graphicsLayerParentLocation);
compositingContainer->convertToLayerCoords(compositingStackingContext,
offset);
position =
FloatPoint(offset) - FloatSize(stackingOffsetFromLayoutObject);
}
m_overflowControlsAncestorClippingLayer->setPosition(position);
hostLayerPosition.move(
-m_ancestorClippingLayer->offsetFromLayoutObject());
} else {
// The controls are in the same 2D space as the compositing container, so
// we can map them into the space of the container.
TransformState transformState(TransformState::ApplyTransformDirection,
FloatPoint());
m_owningLayer.layoutObject().mapLocalToAncestor(
&compositingStackingContext->layoutObject(), transformState,
ApplyContainerFlip);
transformState.flatten();
hostLayerPosition = LayoutPoint(transformState.lastPlanarPoint());
if (PaintLayerScrollableArea* scrollableArea =
compositingStackingContext->getScrollableArea()) {
hostLayerPosition.move(
LayoutSize(toFloatSize(scrollableArea->scrollPosition())));
}
hostLayerPosition.move(-stackingOffsetFromLayoutObject);
}
} else {
hostLayerPosition.move(-m_graphicsLayer->offsetFromLayoutObject());
}
m_overflowControlsHostLayer->setPosition(FloatPoint(hostLayerPosition));
const IntRect borderBox =
toLayoutBox(m_owningLayer.layoutObject()).pixelSnappedBorderBoxRect();
m_overflowControlsHostLayer->setSize(FloatSize(borderBox.size()));
m_overflowControlsHostLayer->setMasksToBounds(true);
}
void CompositedLayerMapping::updateChildContainmentLayerGeometry(
const IntRect& clippingBox,
const IntRect& localCompositingBounds) {
if (!m_childContainmentLayer)
return;
FloatPoint clipPositionInLayoutObjectSpace(
clippingBox.location() - localCompositingBounds.location() +
roundedIntSize(m_owningLayer.subpixelAccumulation()));
// If there are layers between the the child containment layer and
// m_graphicsLayer (eg, the child transform layer), we must adjust the clip
// position to get it in the correct space.
FloatPoint clipPositionInParentSpace = clipPositionInLayoutObjectSpace;
for (GraphicsLayer* ancestor = m_childContainmentLayer->parent();
ancestor != mainGraphicsLayer(); ancestor = ancestor->parent())
clipPositionInParentSpace -= toFloatSize(ancestor->position());
m_childContainmentLayer->setPosition(clipPositionInParentSpace);
m_childContainmentLayer->setSize(FloatSize(clippingBox.size()));
m_childContainmentLayer->setOffsetFromLayoutObject(
toIntSize(clippingBox.location()));
if (m_childClippingMaskLayer && !m_scrollingLayer &&
!layoutObject().style()->clipPath()) {
m_childClippingMaskLayer->setSize(m_childContainmentLayer->size());
m_childClippingMaskLayer->setOffsetFromLayoutObject(
m_childContainmentLayer->offsetFromLayoutObject());
}
}
void CompositedLayerMapping::updateChildTransformLayerGeometry() {
if (!m_childTransformLayer)
return;
const IntRect borderBox =
toLayoutBox(m_owningLayer.layoutObject()).pixelSnappedBorderBoxRect();
m_childTransformLayer->setSize(FloatSize(borderBox.size()));
m_childTransformLayer->setPosition(
FloatPoint(contentOffsetInCompositingLayer()));
}
void CompositedLayerMapping::updateMaskLayerGeometry() {
if (!m_maskLayer)
return;
if (m_maskLayer->size() != m_graphicsLayer->size()) {
m_maskLayer->setSize(m_graphicsLayer->size());
m_maskLayer->setNeedsDisplay();
}
m_maskLayer->setPosition(FloatPoint());
m_maskLayer->setOffsetFromLayoutObject(
m_graphicsLayer->offsetFromLayoutObject());
}
void CompositedLayerMapping::updateTransformGeometry(
const IntPoint& snappedOffsetFromCompositedAncestor,
const IntRect& relativeCompositingBounds) {
if (m_owningLayer.hasTransformRelatedProperty()) {
const LayoutRect borderBox = toLayoutBox(layoutObject()).borderBoxRect();
// Get layout bounds in the coords of compositingContainer to match
// relativeCompositingBounds.
IntRect layerBounds = pixelSnappedIntRect(
toLayoutPoint(m_owningLayer.subpixelAccumulation()), borderBox.size());
layerBounds.moveBy(snappedOffsetFromCompositedAncestor);
// Update properties that depend on layer dimensions
FloatPoint3D transformOrigin =
computeTransformOrigin(IntRect(IntPoint(), layerBounds.size()));
// |transformOrigin| is in the local space of this layer.
// layerBounds - relativeCompositingBounds converts to the space of the
// compositing bounds relative to the composited ancestor. This does not
// apply to the z direction, since the page is 2D.
FloatPoint3D compositedTransformOrigin(
layerBounds.x() - relativeCompositingBounds.x() + transformOrigin.x(),
layerBounds.y() - relativeCompositingBounds.y() + transformOrigin.y(),
transformOrigin.z());
m_graphicsLayer->setTransformOrigin(compositedTransformOrigin);
} else {
FloatPoint3D compositedTransformOrigin(
relativeCompositingBounds.width() * 0.5f,
relativeCompositingBounds.height() * 0.5f, 0.f);
m_graphicsLayer->setTransformOrigin(compositedTransformOrigin);
}
}
void CompositedLayerMapping::updateScrollingLayerGeometry(
const IntRect& localCompositingBounds) {
if (!m_scrollingLayer)
return;
DCHECK(m_scrollingContentsLayer);
LayoutBox& layoutBox = toLayoutBox(layoutObject());
IntRect overflowClipRect =
pixelSnappedIntRect(layoutBox.overflowClipRect(LayoutPoint()));
// When a m_childTransformLayer exists, local content offsets for the
// m_scrollingLayer have already been applied. Otherwise, we apply them here.
IntSize localContentOffset(0, 0);
if (!m_childTransformLayer) {
localContentOffset = roundedIntPoint(m_owningLayer.subpixelAccumulation()) -
localCompositingBounds.location();
}
m_scrollingLayer->setPosition(
FloatPoint(overflowClipRect.location() + localContentOffset));
m_scrollingLayer->setSize(FloatSize(overflowClipRect.size()));
IntSize oldScrollingLayerOffset = m_scrollingLayer->offsetFromLayoutObject();
m_scrollingLayer->setOffsetFromLayoutObject(
-toIntSize(overflowClipRect.location()));
if (m_childClippingMaskLayer && !layoutObject().style()->clipPath()) {
m_childClippingMaskLayer->setPosition(m_scrollingLayer->position());
m_childClippingMaskLayer->setSize(m_scrollingLayer->size());
m_childClippingMaskLayer->setOffsetFromLayoutObject(
toIntSize(overflowClipRect.location()));
}
bool overflowClipRectOffsetChanged =
oldScrollingLayerOffset != m_scrollingLayer->offsetFromLayoutObject();
IntSize scrollSize(layoutBox.pixelSnappedScrollWidth(),
layoutBox.pixelSnappedScrollHeight());
if (overflowClipRectOffsetChanged)
m_scrollingContentsLayer->setNeedsDisplay();
FloatPoint scrollPosition =
m_owningLayer.getScrollableArea()->scrollPosition();
DoubleSize scrollingContentsOffset(
overflowClipRect.location().x() - scrollPosition.x(),
overflowClipRect.location().y() - scrollPosition.y());
// The scroll offset change is compared using floating point so that
// fractional scroll offset change can be propagated to compositor.
if (scrollingContentsOffset != m_scrollingContentsOffset ||
scrollSize != m_scrollingContentsLayer->size()) {
bool coordinatorHandlesOffset =
compositor()->scrollingLayerDidChange(&m_owningLayer);
m_scrollingContentsLayer->setPosition(
coordinatorHandlesOffset ? FloatPoint()
: FloatPoint(-toFloatSize(scrollPosition)));
}
m_scrollingContentsOffset = scrollingContentsOffset;
m_scrollingContentsLayer->setSize(FloatSize(scrollSize));
IntPoint scrollingContentsLayerOffsetFromLayoutObject;
if (PaintLayerScrollableArea* scrollableArea =
m_owningLayer.getScrollableArea()) {
scrollingContentsLayerOffsetFromLayoutObject =
-scrollableArea->scrollOrigin();
}
scrollingContentsLayerOffsetFromLayoutObject.moveBy(
overflowClipRect.location());
m_scrollingContentsLayer->setOffsetDoubleFromLayoutObject(
toIntSize(scrollingContentsLayerOffsetFromLayoutObject),
GraphicsLayer::DontSetNeedsDisplay);
if (m_foregroundLayer) {
if (m_foregroundLayer->size() != m_scrollingContentsLayer->size())
m_foregroundLayer->setSize(m_scrollingContentsLayer->size());
m_foregroundLayer->setNeedsDisplay();
m_foregroundLayer->setOffsetFromLayoutObject(
m_scrollingContentsLayer->offsetFromLayoutObject());
}
}
void CompositedLayerMapping::updateChildClippingMaskLayerGeometry() {
if (!m_childClippingMaskLayer || !layoutObject().style()->clipPath() ||
!layoutObject().isBox())
return;
LayoutBox& layoutBox = toLayoutBox(layoutObject());
IntRect clientBox = enclosingIntRect(layoutBox.clientBoxRect());
m_childClippingMaskLayer->setPosition(m_graphicsLayer->position());
m_childClippingMaskLayer->setSize(m_graphicsLayer->size());
m_childClippingMaskLayer->setOffsetFromLayoutObject(
toIntSize(clientBox.location()));
// NOTE: also some stuff happening in updateChildContainmentLayerGeometry().
}
void CompositedLayerMapping::updateForegroundLayerGeometry(
const FloatSize& relativeCompositingBoundsSize,
const IntRect& clippingBox) {
if (!m_foregroundLayer)
return;
FloatSize foregroundSize = relativeCompositingBoundsSize;
IntSize foregroundOffset = m_graphicsLayer->offsetFromLayoutObject();
m_foregroundLayer->setPosition(FloatPoint());
if (hasClippingLayer()) {
// If we have a clipping layer (which clips descendants), then the
// foreground layer is a child of it, so that it gets correctly sorted with
// children. In that case, position relative to the clipping layer.
foregroundSize = FloatSize(clippingBox.size());
foregroundOffset = toIntSize(clippingBox.location());
} else if (m_childTransformLayer) {
// Things are different if we have a child transform layer rather
// than a clipping layer. In this case, we want to actually change
// the position of the layer (to compensate for our ancestor
// compositing PaintLayer's position) rather than leave the position the
// same and use offset-from-layoutObject + size to describe a clipped
// "window" onto the clipped layer.
m_foregroundLayer->setPosition(-m_childTransformLayer->position());
}
if (foregroundSize != m_foregroundLayer->size()) {
m_foregroundLayer->setSize(foregroundSize);
m_foregroundLayer->setNeedsDisplay();
}
m_foregroundLayer->setOffsetFromLayoutObject(foregroundOffset);
// NOTE: there is some more configuring going on in
// updateScrollingLayerGeometry().
}
void CompositedLayerMapping::updateBackgroundLayerGeometry(
const FloatSize& relativeCompositingBoundsSize) {
if (!m_backgroundLayer)
return;
FloatSize backgroundSize = relativeCompositingBoundsSize;
if (backgroundLayerPaintsFixedRootBackground()) {
FrameView* frameView = toLayoutView(layoutObject()).frameView();
backgroundSize = FloatSize(frameView->visibleContentRect().size());
}
m_backgroundLayer->setPosition(FloatPoint());
if (backgroundSize != m_backgroundLayer->size()) {
m_backgroundLayer->setSize(backgroundSize);
m_backgroundLayer->setNeedsDisplay();
}
m_backgroundLayer->setOffsetFromLayoutObject(
m_graphicsLayer->offsetFromLayoutObject());
}
void CompositedLayerMapping::updateDecorationOutlineLayerGeometry(
const FloatSize& relativeCompositingBoundsSize) {
if (!m_decorationOutlineLayer)
return;
FloatSize decorationSize = relativeCompositingBoundsSize;
m_decorationOutlineLayer->setPosition(FloatPoint());
if (decorationSize != m_decorationOutlineLayer->size()) {
m_decorationOutlineLayer->setSize(decorationSize);
m_decorationOutlineLayer->setNeedsDisplay();
}
m_decorationOutlineLayer->setOffsetFromLayoutObject(
m_graphicsLayer->offsetFromLayoutObject());
}
void CompositedLayerMapping::registerScrollingLayers() {
// Register fixed position layers and their containers with the scrolling
// coordinator.
ScrollingCoordinator* scrollingCoordinator =
scrollingCoordinatorFromLayer(m_owningLayer);
if (!scrollingCoordinator)
return;
scrollingCoordinator->updateLayerPositionConstraint(&m_owningLayer);
// Page scale is applied as a transform on the root layout view layer. Because
// the scroll layer is further up in the hierarchy, we need to avoid marking
// the root layout view layer as a container.
bool isContainer =
m_owningLayer.layoutObject().canContainFixedPositionObjects() &&
!m_owningLayer.isRootLayer();
scrollingCoordinator->setLayerIsContainerForFixedPositionLayers(
m_graphicsLayer.get(), isContainer);
}
void CompositedLayerMapping::updateInternalHierarchy() {
// m_foregroundLayer has to be inserted in the correct order with child
// layers, so it's not inserted here.
if (m_ancestorClippingLayer)
m_ancestorClippingLayer->removeAllChildren();
m_graphicsLayer->removeFromParent();
if (m_ancestorClippingLayer)
m_ancestorClippingLayer->addChild(m_graphicsLayer.get());
// Layer to which children should be attached as we build the hierarchy.
GraphicsLayer* bottomLayer = m_graphicsLayer.get();
auto updateBottomLayer = [&bottomLayer](GraphicsLayer* layer) {
if (layer) {
bottomLayer->addChild(layer);
bottomLayer = layer;
}
};
updateBottomLayer(m_childTransformLayer.get());
updateBottomLayer(m_childContainmentLayer.get());
updateBottomLayer(m_scrollingLayer.get());
// Now constructing the subtree for the overflow controls.
bottomLayer = m_graphicsLayer.get();
// TODO(pdr): Ensure painting uses the correct GraphicsLayer when root layer
// scrolls is enabled. crbug.com/638719
if (m_isMainFrameLayoutViewLayer &&
!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
bottomLayer =
layoutObject().frame()->page()->visualViewport().containerLayer();
}
updateBottomLayer(m_overflowControlsAncestorClippingLayer.get());
updateBottomLayer(m_overflowControlsHostLayer.get());
if (m_layerForHorizontalScrollbar)
m_overflowControlsHostLayer->addChild(m_layerForHorizontalScrollbar.get());
if (m_layerForVerticalScrollbar)
m_overflowControlsHostLayer->addChild(m_layerForVerticalScrollbar.get());
if (m_layerForScrollCorner)
m_overflowControlsHostLayer->addChild(m_layerForScrollCorner.get());
// Now add the DecorationOutlineLayer as a subtree to GraphicsLayer
if (m_decorationOutlineLayer.get())
m_graphicsLayer->addChild(m_decorationOutlineLayer.get());
// The squashing containment layer, if it exists, becomes a no-op parent.
if (m_squashingLayer) {
DCHECK((m_ancestorClippingLayer && !m_squashingContainmentLayer) ||
(!m_ancestorClippingLayer && m_squashingContainmentLayer));
if (m_squashingContainmentLayer) {
m_squashingContainmentLayer->removeAllChildren();
m_squashingContainmentLayer->addChild(m_graphicsLayer.get());
m_squashingContainmentLayer->addChild(m_squashingLayer.get());
} else {
// The ancestor clipping layer is already set up and has m_graphicsLayer
// under it.
m_ancestorClippingLayer->addChild(m_squashingLayer.get());
}
}
}
void CompositedLayerMapping::updatePaintingPhases() {
m_graphicsLayer->setPaintingPhase(paintingPhaseForPrimaryLayer());
if (m_scrollingContentsLayer) {
GraphicsLayerPaintingPhase paintPhase =
GraphicsLayerPaintOverflowContents | GraphicsLayerPaintCompositedScroll;
if (!m_foregroundLayer)
paintPhase |= GraphicsLayerPaintForeground;
m_scrollingContentsLayer->setPaintingPhase(paintPhase);
}
if (m_foregroundLayer) {
GraphicsLayerPaintingPhase paintPhase = GraphicsLayerPaintForeground;
if (m_scrollingContentsLayer)
paintPhase |= GraphicsLayerPaintOverflowContents;
m_foregroundLayer->setPaintingPhase(paintPhase);
}
}
void CompositedLayerMapping::updateContentsRect() {
m_graphicsLayer->setContentsRect(pixelSnappedIntRect(contentsBox()));
}
void CompositedLayerMapping::updateContentsOffsetInCompositingLayer(
const IntPoint& snappedOffsetFromCompositedAncestor,
const IntPoint& graphicsLayerParentLocation) {
// m_graphicsLayer is positioned relative to our compositing ancestor
// PaintLayer, but it's not positioned at the origin of m_owningLayer, it's
// offset by m_contentBounds.location(). This is what
// contentOffsetInCompositingLayer is meant to capture, roughly speaking
// (ignoring rounding and subpixel accumulation).
//
// Our ancestor graphics layers in this CLM (m_graphicsLayer and potentially
// m_ancestorClippingLayer) have pixel snapped, so if we don't adjust this
// offset, we'll see accumulated rounding errors due to that snapping.
//
// In order to ensure that we account for this rounding, we compute
// contentsOffsetInCompositingLayer in a somewhat roundabout way.
//
// our position = (desired position) - (inherited graphics layer offset).
//
// Precisely,
// Offset = snappedOffsetFromCompositedAncestor -
// offsetDueToAncestorGraphicsLayers (See code below)
// = snappedOffsetFromCompositedAncestor -
// (m_graphicsLayer->position() + graphicsLayerParentLocation)
// = snappedOffsetFromCompositedAncestor -
// (relativeCompositingBounds.location() -
// graphicsLayerParentLocation +
// graphicsLayerParentLocation)
// (See updateMainGraphicsLayerGeometry)
// = snappedOffsetFromCompositedAncestor -
// relativeCompositingBounds.location()
// = snappedOffsetFromCompositedAncestor -
// (pixelSnappedIntRect(contentBounds.location()) +
// snappedOffsetFromCompositedAncestor)
// (See computeBoundsOfOwningLayer)
// = -pixelSnappedIntRect(contentBounds.location())
//
// As you can see, we've ended up at the same spot
// (-contentBounds.location()), but by subtracting off our ancestor graphics
// layers positions, we can be sure we've accounted correctly for any pixel
// snapping due to ancestor graphics layers.
//
// And drawing of composited children takes into account the subpixel
// accumulation of this CLM already (through its own
// graphicsLayerParentLocation it appears).
FloatPoint offsetDueToAncestorGraphicsLayers =
m_graphicsLayer->position() + graphicsLayerParentLocation;
m_contentOffsetInCompositingLayer = LayoutSize(
snappedOffsetFromCompositedAncestor - offsetDueToAncestorGraphicsLayers);
m_contentOffsetInCompositingLayerDirty = false;
}
void CompositedLayerMapping::updateDrawsContent() {
bool inOverlayFullscreenVideo = false;
if (layoutObject().isVideo()) {
HTMLVideoElement* videoElement = toHTMLVideoElement(layoutObject().node());
if (videoElement->isFullscreen() &&
videoElement->usesOverlayFullscreenVideo())
inOverlayFullscreenVideo = true;
}
bool hasPaintedContent =
inOverlayFullscreenVideo ? false : containsPaintedContent();
m_graphicsLayer->setDrawsContent(hasPaintedContent);
if (m_scrollingLayer) {
// m_scrollingLayer never has backing store.
// m_scrollingContentsLayer only needs backing store if the scrolled
// contents need to paint.
m_scrollingContentsAreEmpty =
!m_owningLayer.hasVisibleContent() ||
!(layoutObject().styleRef().hasBackground() ||
layoutObject().hasBackdropFilter() || paintsChildren());
m_scrollingContentsLayer->setDrawsContent(!m_scrollingContentsAreEmpty);
}
m_drawsBackgroundOntoContentLayer = false;
if (hasPaintedContent && isCompositedCanvas(layoutObject())) {
CanvasRenderingContext* context =
toHTMLCanvasElement(layoutObject().node())->renderingContext();
// Content layer may be null if context is lost.
if (WebLayer* contentLayer = context->platformLayer()) {
Color bgColor(Color::transparent);
if (contentLayerSupportsDirectBackgroundComposition(layoutObject())) {
bgColor = layoutObjectBackgroundColor();
hasPaintedContent = false;
m_drawsBackgroundOntoContentLayer = true;
}
contentLayer->setBackgroundColor(bgColor.rgb());
}
}
// FIXME: we could refine this to only allocate backings for one of these
// layers if possible.
if (m_foregroundLayer)
m_foregroundLayer->setDrawsContent(hasPaintedContent);
// TODO(yigu): The background should no longer setDrawsContent(true) if we
// only have an outline and we are drawing the outline into the decoration
// layer (i.e. if there is nothing actually drawn into the
// background anymore.)
// "hasPaintedContent" should be calculated in a way that does not take the
// outline into consideration.
if (m_backgroundLayer)
m_backgroundLayer->setDrawsContent(hasPaintedContent);
if (m_decorationOutlineLayer)
m_decorationOutlineLayer->setDrawsContent(true);
if (m_ancestorClippingMaskLayer)
m_ancestorClippingMaskLayer->setDrawsContent(true);
if (m_maskLayer)
m_maskLayer->setDrawsContent(true);
if (m_childClippingMaskLayer)
m_childClippingMaskLayer->setDrawsContent(true);
}
void CompositedLayerMapping::updateChildrenTransform() {
if (GraphicsLayer* childTransformLayer = this->childTransformLayer()) {
childTransformLayer->setTransform(owningLayer().perspectiveTransform());
childTransformLayer->setTransformOrigin(owningLayer().perspectiveOrigin());
}
updateShouldFlattenTransform();
}
// Return true if the layers changed.
bool CompositedLayerMapping::updateClippingLayers(
bool needsAncestorClip,
bool needsAncestorClippingMask,
bool needsDescendantClip) {
bool layersChanged = false;
if (needsAncestorClip) {
if (!m_ancestorClippingLayer) {
m_ancestorClippingLayer =
createGraphicsLayer(CompositingReasonLayerForAncestorClip);
m_ancestorClippingLayer->setMasksToBounds(true);
m_ancestorClippingLayer->setShouldFlattenTransform(false);
layersChanged = true;
}
} else if (m_ancestorClippingLayer) {
if (m_ancestorClippingMaskLayer) {
m_ancestorClippingMaskLayer->removeFromParent();
m_ancestorClippingMaskLayer = nullptr;
}
m_ancestorClippingLayer->removeFromParent();
m_ancestorClippingLayer = nullptr;
layersChanged = true;
}
if (needsAncestorClippingMask) {
DCHECK(m_ancestorClippingLayer);
if (!m_ancestorClippingMaskLayer) {
m_ancestorClippingMaskLayer =
createGraphicsLayer(CompositingReasonLayerForAncestorClippingMask);
m_ancestorClippingMaskLayer->setPaintingPhase(
GraphicsLayerPaintAncestorClippingMask);
m_ancestorClippingLayer->setMaskLayer(m_ancestorClippingMaskLayer.get());
layersChanged = true;
}
} else if (m_ancestorClippingMaskLayer) {
m_ancestorClippingMaskLayer->removeFromParent();
m_ancestorClippingMaskLayer = nullptr;
m_ancestorClippingLayer->setMaskLayer(nullptr);
layersChanged = true;
}
if (needsDescendantClip) {
// We don't need a child containment layer if we're the main frame layout
// view layer. It's redundant as the frame clip above us will handle this
// clipping.
if (!m_childContainmentLayer && !m_isMainFrameLayoutViewLayer) {
m_childContainmentLayer =
createGraphicsLayer(CompositingReasonLayerForDescendantClip);
m_childContainmentLayer->setMasksToBounds(true);
layersChanged = true;
}
} else if (hasClippingLayer()) {
m_childContainmentLayer->removeFromParent();
m_childContainmentLayer = nullptr;
layersChanged = true;
}
return layersChanged;
}
bool CompositedLayerMapping::updateChildTransformLayer(
bool needsChildTransformLayer) {
bool layersChanged = false;
if (needsChildTransformLayer) {
if (!m_childTransformLayer) {
m_childTransformLayer =
createGraphicsLayer(CompositingReasonLayerForPerspective);
m_childTransformLayer->setDrawsContent(false);
layersChanged = true;
}
} else if (m_childTransformLayer) {
m_childTransformLayer->removeFromParent();
m_childTransformLayer = nullptr;
layersChanged = true;
}
return layersChanged;
}
void CompositedLayerMapping::setBackgroundLayerPaintsFixedRootBackground(
bool backgroundLayerPaintsFixedRootBackground) {
m_backgroundLayerPaintsFixedRootBackground =
backgroundLayerPaintsFixedRootBackground;
}
bool CompositedLayerMapping::toggleScrollbarLayerIfNeeded(
std::unique_ptr<GraphicsLayer>& layer,
bool needsLayer,
CompositingReasons reason) {
if (needsLayer == !!layer)
return false;
layer = needsLayer ? createGraphicsLayer(reason) : nullptr;
if (PaintLayerScrollableArea* scrollableArea =
m_owningLayer.getScrollableArea()) {
if (ScrollingCoordinator* scrollingCoordinator =
scrollingCoordinatorFromLayer(m_owningLayer)) {
if (reason == CompositingReasonLayerForHorizontalScrollbar)
scrollingCoordinator->scrollableAreaScrollbarLayerDidChange(
scrollableArea, HorizontalScrollbar);
else if (reason == CompositingReasonLayerForVerticalScrollbar)
scrollingCoordinator->scrollableAreaScrollbarLayerDidChange(
scrollableArea, VerticalScrollbar);
}
}
return true;
}
bool CompositedLayerMapping::updateOverflowControlsLayers(
bool needsHorizontalScrollbarLayer,
bool needsVerticalScrollbarLayer,
bool needsScrollCornerLayer,
bool needsAncestorClip) {
if (PaintLayerScrollableArea* scrollableArea =
m_owningLayer.getScrollableArea()) {
// If the scrollable area is marked as needing a new scrollbar layer,
// destroy the layer now so that it will be created again below.
if (m_layerForHorizontalScrollbar && needsHorizontalScrollbarLayer &&
scrollableArea->shouldRebuildHorizontalScrollbarLayer())
toggleScrollbarLayerIfNeeded(
m_layerForHorizontalScrollbar, false,
CompositingReasonLayerForHorizontalScrollbar);
if (m_layerForVerticalScrollbar && needsVerticalScrollbarLayer &&
scrollableArea->shouldRebuildVerticalScrollbarLayer())
toggleScrollbarLayerIfNeeded(m_layerForVerticalScrollbar, false,
CompositingReasonLayerForVerticalScrollbar);
scrollableArea->resetRebuildScrollbarLayerFlags();
if (m_scrollingContentsLayer &&
scrollableArea->needsShowScrollbarLayers()) {
m_scrollingContentsLayer->platformLayer()->showScrollbars();
scrollableArea->didShowScrollbarLayers();
}
}
// If the subtree is invisible, we don't actually need scrollbar layers.
// Only do this check if at least one of the bits is currently true.
// This is important because this method is called during the destructor
// of CompositedLayerMapping, which may happen during style recalc,
// and therefore visible content status may be invalid.
if (needsHorizontalScrollbarLayer || needsVerticalScrollbarLayer ||
needsScrollCornerLayer) {
bool invisible = m_owningLayer.subtreeIsInvisible();
needsHorizontalScrollbarLayer &= !invisible;
needsVerticalScrollbarLayer &= !invisible;
needsScrollCornerLayer &= !invisible;
}
bool horizontalScrollbarLayerChanged = toggleScrollbarLayerIfNeeded(
m_layerForHorizontalScrollbar, needsHorizontalScrollbarLayer,
CompositingReasonLayerForHorizontalScrollbar);
bool verticalScrollbarLayerChanged = toggleScrollbarLayerIfNeeded(
m_layerForVerticalScrollbar, needsVerticalScrollbarLayer,
CompositingReasonLayerForVerticalScrollbar);
bool scrollCornerLayerChanged = toggleScrollbarLayerIfNeeded(
m_layerForScrollCorner, needsScrollCornerLayer,
CompositingReasonLayerForScrollCorner);
bool needsOverflowControlsHostLayer = needsHorizontalScrollbarLayer ||
needsVerticalScrollbarLayer ||
needsScrollCornerLayer;
toggleScrollbarLayerIfNeeded(m_overflowControlsHostLayer,
needsOverflowControlsHostLayer,
CompositingReasonLayerForOverflowControlsHost);
bool needsOverflowAncestorClipLayer =
needsOverflowControlsHostLayer && needsAncestorClip;
toggleScrollbarLayerIfNeeded(m_overflowControlsAncestorClippingLayer,
needsOverflowAncestorClipLayer,
CompositingReasonLayerForOverflowControlsHost);
return horizontalScrollbarLayerChanged || verticalScrollbarLayerChanged ||
scrollCornerLayerChanged;
}
void CompositedLayerMapping::positionOverflowControlsLayers() {
if (GraphicsLayer* layer = layerForHorizontalScrollbar()) {
Scrollbar* hBar = m_owningLayer.getScrollableArea()->horizontalScrollbar();
if (hBar) {
layer->setPosition(hBar->frameRect().location());
layer->setSize(FloatSize(hBar->frameRect().size()));
if (layer->hasContentsLayer())
layer->setContentsRect(IntRect(IntPoint(), hBar->frameRect().size()));
}
layer->setDrawsContent(hBar && !layer->hasContentsLayer());
}
if (GraphicsLayer* layer = layerForVerticalScrollbar()) {
Scrollbar* vBar = m_owningLayer.getScrollableArea()->verticalScrollbar();
if (vBar) {
layer->setPosition(vBar->frameRect().location());
layer->setSize(FloatSize(vBar->frameRect().size()));
if (layer->hasContentsLayer())
layer->setContentsRect(IntRect(IntPoint(), vBar->frameRect().size()));
}
layer->setDrawsContent(vBar && !layer->hasContentsLayer());
}
if (GraphicsLayer* layer = layerForScrollCorner()) {
const IntRect& scrollCornerAndResizer =
m_owningLayer.getScrollableArea()->scrollCornerAndResizerRect();
layer->setPosition(FloatPoint(scrollCornerAndResizer.location()));
layer->setSize(FloatSize(scrollCornerAndResizer.size()));
layer->setDrawsContent(!scrollCornerAndResizer.isEmpty());
}
}
enum ApplyToGraphicsLayersModeFlags {
ApplyToLayersAffectedByPreserve3D = (1 << 0),
ApplyToSquashingLayer = (1 << 1),
ApplyToScrollbarLayers = (1 << 2),
ApplyToBackgroundLayer = (1 << 3),
ApplyToMaskLayers = (1 << 4),
ApplyToContentLayers = (1 << 5),
ApplyToChildContainingLayers =
(1 << 6), // layers between m_graphicsLayer and children
ApplyToNonScrollingContentLayers = (1 << 7),
ApplyToScrollingContentLayers = (1 << 8),
ApplyToDecorationOutlineLayer = (1 << 9),
ApplyToAllGraphicsLayers =
(ApplyToSquashingLayer | ApplyToScrollbarLayers | ApplyToBackgroundLayer |
ApplyToMaskLayers |
ApplyToLayersAffectedByPreserve3D |
ApplyToContentLayers |
ApplyToScrollingContentLayers |
ApplyToDecorationOutlineLayer)
};
typedef unsigned ApplyToGraphicsLayersMode;
template <typename Func>
static void ApplyToGraphicsLayers(const CompositedLayerMapping* mapping,
const Func& f,
ApplyToGraphicsLayersMode mode) {
DCHECK(mode);
if ((mode & ApplyToLayersAffectedByPreserve3D) &&
mapping->childTransformLayer())
f(mapping->childTransformLayer());
if (((mode & ApplyToLayersAffectedByPreserve3D) ||
(mode & ApplyToContentLayers) ||
(mode & ApplyToNonScrollingContentLayers)) &&
mapping->mainGraphicsLayer())
f(mapping->mainGraphicsLayer());
if (((mode & ApplyToLayersAffectedByPreserve3D) ||
(mode & ApplyToChildContainingLayers)) &&
mapping->clippingLayer())
f(mapping->clippingLayer());
if (((mode & ApplyToLayersAffectedByPreserve3D) ||
(mode & ApplyToChildContainingLayers)) &&
mapping->scrollingLayer())
f(mapping->scrollingLayer());
if (((mode & ApplyToLayersAffectedByPreserve3D) ||
(mode & ApplyToContentLayers) || (mode & ApplyToChildContainingLayers) ||
(mode & ApplyToScrollingContentLayers)) &&
mapping->scrollingContentsLayer())
f(mapping->scrollingContentsLayer());
if (((mode & ApplyToLayersAffectedByPreserve3D) ||
(mode & ApplyToContentLayers) ||
(mode & ApplyToScrollingContentLayers)) &&
mapping->foregroundLayer())
f(mapping->foregroundLayer());
if ((mode & ApplyToChildContainingLayers) && mapping->childTransformLayer())
f(mapping->childTransformLayer());
if ((mode & ApplyToSquashingLayer) && mapping->squashingLayer())
f(mapping->squashingLayer());
if (((mode & ApplyToMaskLayers) || (mode & ApplyToContentLayers) ||
(mode & ApplyToNonScrollingContentLayers)) &&
mapping->maskLayer())
f(mapping->maskLayer());
if (((mode & ApplyToMaskLayers) || (mode & ApplyToContentLayers) ||
(mode & ApplyToNonScrollingContentLayers)) &&
mapping->childClippingMaskLayer())
f(mapping->childClippingMaskLayer());
if (((mode & ApplyToMaskLayers) || (mode & ApplyToContentLayers) ||
(mode & ApplyToNonScrollingContentLayers)) &&
mapping->ancestorClippingMaskLayer())
f(mapping->ancestorClippingMaskLayer());
if (((mode & ApplyToBackgroundLayer) || (mode & ApplyToContentLayers) ||
(mode & ApplyToNonScrollingContentLayers)) &&
mapping->backgroundLayer())
f(mapping->backgroundLayer());
if ((mode & ApplyToScrollbarLayers) && mapping->layerForHorizontalScrollbar())
f(mapping->layerForHorizontalScrollbar());
if ((mode & ApplyToScrollbarLayers) && mapping->layerForVerticalScrollbar())
f(mapping->layerForVerticalScrollbar());
if ((mode & ApplyToScrollbarLayers) && mapping->layerForScrollCorner())
f(mapping->layerForScrollCorner());
if (((mode & ApplyToDecorationOutlineLayer) ||
(mode & ApplyToNonScrollingContentLayers)) &&
mapping->decorationOutlineLayer())
f(mapping->decorationOutlineLayer());
}
struct UpdateRenderingContextFunctor {
void operator()(GraphicsLayer* layer) const {
layer->setRenderingContext(renderingContext);
}
int renderingContext;
};
void CompositedLayerMapping::updateRenderingContext() {
// All layers but the squashing layer (which contains 'alien' content) should
// be included in this rendering context.
int id = 0;
// NB, it is illegal at this point to query an ancestor's compositing state.
// Some compositing reasons depend on the compositing state of ancestors. So
// if we want a rendering context id for the context root, we cannot ask for
// the id of its associated WebLayer now; it may not have one yet. We could do
// a second pass after doing the compositing updates to get these ids, but
// this would actually be harmful. We do not want to attach any semantic
// meaning to the context id other than the fact that they group a number of
// layers together for the sake of 3d sorting. So instead we will ask the
// compositor to vend us an arbitrary, but consistent id.
if (PaintLayer* root = m_owningLayer.renderingContextRoot()) {
if (Node* node = root->layoutObject().node())
id = static_cast<int>(PtrHash<Node>::hash(node));
}
UpdateRenderingContextFunctor functor = {id};
ApplyToGraphicsLayers<UpdateRenderingContextFunctor>(
this, functor, ApplyToAllGraphicsLayers);
}
struct UpdateShouldFlattenTransformFunctor {
void operator()(GraphicsLayer* layer) const {
layer->setShouldFlattenTransform(shouldFlatten);
}
bool shouldFlatten;
};
void CompositedLayerMapping::updateShouldFlattenTransform() {
// All CLM-managed layers that could affect a descendant layer should update
// their should-flatten-transform value (the other layers' transforms don't
// matter here).
UpdateShouldFlattenTransformFunctor functor = {
!m_owningLayer.shouldPreserve3D()};
ApplyToGraphicsLayersMode mode = ApplyToLayersAffectedByPreserve3D;
ApplyToGraphicsLayers(this, functor, mode);
// Note, if we apply perspective, we have to set should flatten differently
// so that the transform propagates to child layers correctly.
if (hasChildTransformLayer()) {
ApplyToGraphicsLayers(
this,
[](GraphicsLayer* layer) { layer->setShouldFlattenTransform(false); },
ApplyToChildContainingLayers);
}
// Regardless, mark the graphics layer, scrolling layer and scrolling block
// selection layer (if they exist) as not flattening. Having them flatten
// causes unclipped render surfaces which cause bugs.
// http://crbug.com/521768
if (hasScrollingLayer()) {
m_graphicsLayer->setShouldFlattenTransform(false);
m_scrollingLayer->setShouldFlattenTransform(false);
}
}
// Some background on when you receive an element id or mutable properties.
//
// element id:
// If you have a compositor proxy, an animation, or you're a scroller (and
// might impl animate).
//
// mutable properties:
// Only if you have a compositor proxy.
//
// The element id for the scroll layers is assigned when they're constructed,
// since this is unconditional. However, the element id for the primary layer as
// well as the mutable properties for all layers may change according to the
// rules above so we update those values here.
void CompositedLayerMapping::updateElementIdAndCompositorMutableProperties() {
int elementId = 0;
uint32_t primaryMutableProperties = CompositorMutableProperty::kNone;
uint32_t scrollMutableProperties = CompositorMutableProperty::kNone;
Node* owningNode = m_owningLayer.layoutObject().node();
Element* animatingElement = nullptr;
const ComputedStyle* animatingStyle = nullptr;
if (owningNode) {
Document& document = owningNode->document();
Element* scrollingElement = document.scrollingElementNoLayout();
if (owningNode->isElementNode() &&
(!RuntimeEnabledFeatures::rootLayerScrollingEnabled() ||
owningNode != scrollingElement)) {
animatingElement = toElement(owningNode);
animatingStyle = m_owningLayer.layoutObject().style();
} else if (owningNode->isDocumentNode() &&
RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
owningNode = animatingElement = scrollingElement;
if (scrollingElement && scrollingElement->layoutObject())
animatingStyle = scrollingElement->layoutObject()->style();
}
}
if (RuntimeEnabledFeatures::compositorWorkerEnabled() && animatingStyle &&
animatingStyle->hasCompositorProxy()) {
uint32_t compositorMutableProperties =
animatingElement->compositorMutableProperties();
elementId = DOMNodeIds::idForNode(owningNode);
primaryMutableProperties = (CompositorMutableProperty::kOpacity |
CompositorMutableProperty::kTransform) &
compositorMutableProperties;
scrollMutableProperties = (CompositorMutableProperty::kScrollLeft |
CompositorMutableProperty::kScrollTop) &
compositorMutableProperties;
}
if (animatingStyle && animatingStyle->shouldCompositeForCurrentAnimations())
elementId = DOMNodeIds::idForNode(owningNode);
CompositorElementId compositorElementId;
if (elementId)
compositorElementId =
createCompositorElementId(elementId, CompositorSubElementId::Primary);
m_graphicsLayer->setElementId(compositorElementId);
m_graphicsLayer->setCompositorMutableProperties(primaryMutableProperties);
// We always set the elementId for m_scrollingContentsLayer since it can be
// animated for smooth scrolling, so we don't need to set it conditionally
// here.
if (m_scrollingContentsLayer.get())
m_scrollingContentsLayer->setCompositorMutableProperties(
scrollMutableProperties);
}
bool CompositedLayerMapping::updateForegroundLayer(bool needsForegroundLayer) {
bool layerChanged = false;
if (needsForegroundLayer) {
if (!m_foregroundLayer) {
m_foregroundLayer =
createGraphicsLayer(CompositingReasonLayerForForeground);
layerChanged = true;
}
} else if (m_foregroundLayer) {
m_foregroundLayer->removeFromParent();
m_foregroundLayer = nullptr;
layerChanged = true;
}
return layerChanged;
}
bool CompositedLayerMapping::updateBackgroundLayer(bool needsBackgroundLayer) {
bool layerChanged = false;
if (needsBackgroundLayer) {
if (!m_backgroundLayer) {
m_backgroundLayer =
createGraphicsLayer(CompositingReasonLayerForBackground);
m_backgroundLayer->setTransformOrigin(FloatPoint3D());
m_backgroundLayer->setPaintingPhase(GraphicsLayerPaintBackground);
layerChanged = true;
}
} else {
if (m_backgroundLayer) {
m_backgroundLayer->removeFromParent();
m_backgroundLayer = nullptr;
layerChanged = true;
}
}
if (layerChanged && !m_owningLayer.layoutObject().documentBeingDestroyed())
compositor()->rootFixedBackgroundsChanged();
return layerChanged;
}
bool CompositedLayerMapping::updateDecorationOutlineLayer(
bool needsDecorationOutlineLayer) {
bool layerChanged = false;
if (needsDecorationOutlineLayer) {
if (!m_decorationOutlineLayer) {
m_decorationOutlineLayer =
createGraphicsLayer(CompositingReasonLayerForDecoration);
m_decorationOutlineLayer->setPaintingPhase(GraphicsLayerPaintDecoration);
layerChanged = true;
}
} else if (m_decorationOutlineLayer) {
m_decorationOutlineLayer = nullptr;
layerChanged = true;
}
return layerChanged;
}
bool CompositedLayerMapping::updateMaskLayer(bool needsMaskLayer) {
bool layerChanged = false;
if (needsMaskLayer) {
if (!m_maskLayer) {
m_maskLayer = createGraphicsLayer(CompositingReasonLayerForMask);
m_maskLayer->setPaintingPhase(GraphicsLayerPaintMask);
layerChanged = true;
}
} else if (m_maskLayer) {
m_maskLayer = nullptr;
layerChanged = true;
}
return layerChanged;
}
void CompositedLayerMapping::updateChildClippingMaskLayer(
bool needsChildClippingMaskLayer) {
if (needsChildClippingMaskLayer) {
if (!m_childClippingMaskLayer) {
m_childClippingMaskLayer =
createGraphicsLayer(CompositingReasonLayerForClippingMask);
m_childClippingMaskLayer->setPaintingPhase(
GraphicsLayerPaintChildClippingMask);
}
return;
}
m_childClippingMaskLayer = nullptr;
}
bool CompositedLayerMapping::updateScrollingLayers(bool needsScrollingLayers) {
ScrollingCoordinator* scrollingCoordinator =
scrollingCoordinatorFromLayer(m_owningLayer);
bool layerChanged = false;
if (needsScrollingLayers) {
if (!m_scrollingLayer) {
// Outer layer which corresponds with the scroll view.
m_scrollingLayer =
createGraphicsLayer(CompositingReasonLayerForScrollingContainer);
m_scrollingLayer->setDrawsContent(false);
m_scrollingLayer->setMasksToBounds(true);
// Inner layer which renders the content that scrolls.
m_scrollingContentsLayer =
createGraphicsLayer(CompositingReasonLayerForScrollingContents);
if (Node* owningNode = m_owningLayer.layoutObject().node())
m_scrollingContentsLayer->setElementId(createCompositorElementId(
DOMNodeIds::idForNode(owningNode), CompositorSubElementId::Scroll));
m_scrollingLayer->addChild(m_scrollingContentsLayer.get());
layerChanged = true;
if (scrollingCoordinator) {
scrollingCoordinator->scrollableAreaScrollLayerDidChange(
m_owningLayer.getScrollableArea());
scrollingCoordinator->scrollableAreasDidChange();
}
}
} else if (m_scrollingLayer) {
m_scrollingLayer = nullptr;
m_scrollingContentsLayer = nullptr;
layerChanged = true;
if (scrollingCoordinator) {
scrollingCoordinator->scrollableAreaScrollLayerDidChange(
m_owningLayer.getScrollableArea());
scrollingCoordinator->scrollableAreasDidChange();
}
}
return layerChanged;
}
static void updateScrollParentForGraphicsLayer(
GraphicsLayer* layer,
GraphicsLayer* topmostLayer,
const PaintLayer* scrollParent,
ScrollingCoordinator* scrollingCoordinator) {
if (!layer)
return;
// Only the topmost layer has a scroll parent. All other layers have a null
// scroll parent.
if (layer != topmostLayer)
scrollParent = 0;
scrollingCoordinator->updateScrollParentForGraphicsLayer(layer, scrollParent);
}
void CompositedLayerMapping::updateScrollParent(
const PaintLayer* scrollParent) {
if (ScrollingCoordinator* scrollingCoordinator =
scrollingCoordinatorFromLayer(m_owningLayer)) {
GraphicsLayer* topmostLayer = childForSuperlayers();
updateScrollParentForGraphicsLayer(m_squashingContainmentLayer.get(),
topmostLayer, scrollParent,
scrollingCoordinator);
updateScrollParentForGraphicsLayer(m_ancestorClippingLayer.get(),
topmostLayer, scrollParent,
scrollingCoordinator);
updateScrollParentForGraphicsLayer(m_graphicsLayer.get(), topmostLayer,
scrollParent, scrollingCoordinator);
}
}
static void updateClipParentForGraphicsLayer(
GraphicsLayer* layer,
GraphicsLayer* topmostLayer,
const PaintLayer* clipParent,
ScrollingCoordinator* scrollingCoordinator) {
if (!layer)
return;
// Only the topmost layer has a scroll parent. All other layers have a null
// scroll parent.
if (layer != topmostLayer)
clipParent = 0;
scrollingCoordinator->updateClipParentForGraphicsLayer(layer, clipParent);
}
void CompositedLayerMapping::updateClipParent(const PaintLayer* scrollParent) {
const PaintLayer* clipParent = nullptr;
bool haveAncestorClipLayer = false;
bool haveAncestorMaskLayer = false;
owningLayerClippedOrMaskedByLayerNotAboveCompositedAncestor(
scrollParent, haveAncestorClipLayer, haveAncestorMaskLayer);
if (!haveAncestorClipLayer) {
clipParent = m_owningLayer.clipParent();
if (clipParent)
clipParent =
clipParent->enclosingLayerWithCompositedLayerMapping(IncludeSelf);
}
if (ScrollingCoordinator* scrollingCoordinator =
scrollingCoordinatorFromLayer(m_owningLayer)) {
GraphicsLayer* topmostLayer = childForSuperlayers();
updateClipParentForGraphicsLayer(m_squashingContainmentLayer.get(),
topmostLayer, clipParent,
scrollingCoordinator);
updateClipParentForGraphicsLayer(m_ancestorClippingLayer.get(),
topmostLayer, clipParent,
scrollingCoordinator);
updateClipParentForGraphicsLayer(m_graphicsLayer.get(), topmostLayer,
clipParent, scrollingCoordinator);
}
}
bool CompositedLayerMapping::updateSquashingLayers(bool needsSquashingLayers) {
bool layersChanged = false;
if (needsSquashingLayers) {
if (!m_squashingLayer) {
m_squashingLayer =
createGraphicsLayer(CompositingReasonLayerForSquashingContents);
m_squashingLayer->setDrawsContent(true);
layersChanged = true;
}
if (m_ancestorClippingLayer) {
if (m_squashingContainmentLayer) {
m_squashingContainmentLayer->removeFromParent();
m_squashingContainmentLayer = nullptr;
layersChanged = true;
}
} else {
if (!m_squashingContainmentLayer) {
m_squashingContainmentLayer =
createGraphicsLayer(CompositingReasonLayerForSquashingContainer);
m_squashingContainmentLayer->setShouldFlattenTransform(false);
layersChanged = true;
}
}
DCHECK((m_ancestorClippingLayer && !m_squashingContainmentLayer) ||
(!m_ancestorClippingLayer && m_squashingContainmentLayer));
DCHECK(m_squashingLayer);
} else {
if (m_squashingLayer) {
m_squashingLayer->removeFromParent();
m_squashingLayer = nullptr;
layersChanged = true;
}
if (m_squashingContainmentLayer) {
m_squashingContainmentLayer->removeFromParent();
m_squashingContainmentLayer = nullptr;
layersChanged = true;
}
DCHECK(!m_squashingLayer);
DCHECK(!m_squashingContainmentLayer);
}
return layersChanged;
}
GraphicsLayerPaintingPhase
CompositedLayerMapping::paintingPhaseForPrimaryLayer() const {
unsigned phase = 0;
if (!m_backgroundLayer)
phase |= GraphicsLayerPaintBackground;
if (!m_foregroundLayer)
phase |= GraphicsLayerPaintForeground;
if (!m_maskLayer)
phase |= GraphicsLayerPaintMask;
if (!m_decorationOutlineLayer)
phase |= GraphicsLayerPaintDecoration;
if (m_scrollingContentsLayer) {
phase &= ~GraphicsLayerPaintForeground;
phase |= GraphicsLayerPaintCompositedScroll;
}
return static_cast<GraphicsLayerPaintingPhase>(phase);
}
float CompositedLayerMapping::compositingOpacity(
float layoutObjectOpacity) const {
float finalOpacity = layoutObjectOpacity;
for (PaintLayer* curr = m_owningLayer.parent(); curr; curr = curr->parent()) {
// We only care about parents that are stacking contexts.
// Recall that opacity creates stacking context.
if (!curr->stackingNode()->isStackingContext())
continue;
// If we found a composited layer, regardless of whether it actually
// paints into it, we want to compute opacity relative to it. So we can
// break here.
//
// FIXME: with grouped backings, a composited descendant will have to
// continue past the grouped (squashed) layers that its parents may
// contribute to. This whole confusion can be avoided by specifying
// explicitly the composited ancestor where we would stop accumulating
// opacity.
if (curr->compositingState() == PaintsIntoOwnBacking)
break;
finalOpacity *= curr->layoutObject().opacity();
}
return finalOpacity;
}
Color CompositedLayerMapping::layoutObjectBackgroundColor() const {
return layoutObject().resolveColor(CSSPropertyBackgroundColor);
}
void CompositedLayerMapping::updateBackgroundColor() {
m_graphicsLayer->setBackgroundColor(layoutObjectBackgroundColor());
}
bool CompositedLayerMapping::paintsChildren() const {
if (m_owningLayer.hasVisibleContent() &&
m_owningLayer.hasNonEmptyChildLayoutObjects())
return true;
if (hasVisibleNonCompositingDescendant(&m_owningLayer))
return true;
return false;
}
static bool isCompositedPlugin(LayoutObject& layoutObject) {
return layoutObject.isEmbeddedObject() &&
toLayoutEmbeddedObject(layoutObject).requiresAcceleratedCompositing();
}
bool CompositedLayerMapping::hasVisibleNonCompositingDescendant(
PaintLayer* parent) {
if (!parent->hasVisibleDescendant())
return false;
// FIXME: We shouldn't be called with a stale z-order lists. See bug 85512.
parent->stackingNode()->updateLayerListsIfNeeded();
#if DCHECK_IS_ON()
LayerListMutationDetector mutationChecker(parent->stackingNode());
#endif
PaintLayerStackingNodeIterator normalFlowIterator(*parent->stackingNode(),
AllChildren);
while (PaintLayerStackingNode* curNode = normalFlowIterator.next()) {
PaintLayer* curLayer = curNode->layer();
if (curLayer->hasCompositedLayerMapping())
continue;
if (curLayer->hasVisibleContent() ||
hasVisibleNonCompositingDescendant(curLayer))
return true;
}
return false;
}
bool CompositedLayerMapping::containsPaintedContent() const {
if (layoutObject().isImage() && isDirectlyCompositedImage())
return false;
LayoutObject& layoutObject = this->layoutObject();
// FIXME: we could optimize cases where the image, video or canvas is known to
// fill the border box entirely, and set background color on the layer in that
// case, instead of allocating backing store and painting.
if (layoutObject.isVideo() &&
toLayoutVideo(layoutObject).shouldDisplayVideo())
return m_owningLayer.hasBoxDecorationsOrBackground();
if (m_owningLayer.hasVisibleBoxDecorations())
return true;
if (layoutObject.hasMask()) // masks require special treatment
return true;
if (layoutObject.isAtomicInlineLevel() && !isCompositedPlugin(layoutObject))
return true;
if (layoutObject.isLayoutMultiColumnSet())
return true;
if (layoutObject.node() && layoutObject.node()->isDocumentNode()) {
// Look to see if the root object has a non-simple background
LayoutObject* rootObject =
layoutObject.document().documentElement()
? layoutObject.document().documentElement()->layoutObject()
: 0;
// Reject anything that has a border, a border-radius or outline,
// or is not a simple background (no background, or solid color).
if (rootObject &&
hasBoxDecorationsOrBackgroundImage(rootObject->styleRef()))
return true;
// Now look at the body's layoutObject.
HTMLElement* body = layoutObject.document().body();
LayoutObject* bodyObject =
isHTMLBodyElement(body) ? body->layoutObject() : 0;
if (bodyObject &&
hasBoxDecorationsOrBackgroundImage(bodyObject->styleRef()))
return true;
}
// FIXME: it's O(n^2). A better solution is needed.
return paintsChildren();
}
// An image can be directly composited if it's the sole content of the layer,
// and has no box decorations or clipping that require painting. Direct
// compositing saves a backing store.
bool CompositedLayerMapping::isDirectlyCompositedImage() const {
DCHECK(layoutObject().isImage());
LayoutImage& imageLayoutObject = toLayoutImage(layoutObject());
if (m_owningLayer.hasBoxDecorationsOrBackground() ||
imageLayoutObject.hasClip() || imageLayoutObject.hasClipPath() ||
imageLayoutObject.hasObjectFit())
return false;
if (ImageResourceContent* cachedImage = imageLayoutObject.cachedImage()) {
if (!cachedImage->hasImage())
return false;
Image* image = cachedImage->getImage();
if (!image->isBitmapImage())
return false;
return true;
}
return false;
}
void CompositedLayerMapping::contentChanged(ContentChangeType changeType) {
if ((changeType == ImageChanged) && layoutObject().isImage() &&
isDirectlyCompositedImage()) {
updateImageContents();
return;
}
if (changeType == CanvasChanged && isCompositedCanvas(layoutObject())) {
m_graphicsLayer->setContentsNeedsDisplay();
return;
}
}
void CompositedLayerMapping::updateImageContents() {
DCHECK(layoutObject().isImage());
LayoutImage& imageLayoutObject = toLayoutImage(layoutObject());
ImageResourceContent* cachedImage = imageLayoutObject.cachedImage();
if (!cachedImage)
return;
Image* image = cachedImage->getImage();
if (!image)
return;
// This is a no-op if the layer doesn't have an inner layer for the image.
m_graphicsLayer->setContentsToImage(
image, LayoutObject::shouldRespectImageOrientation(&imageLayoutObject));
m_graphicsLayer->setFilterQuality(layoutObject().style()->imageRendering() ==
ImageRenderingPixelated
? kNone_SkFilterQuality
: kLow_SkFilterQuality);
// Prevent double-drawing: https://bugs.webkit.org/show_bug.cgi?id=58632
updateDrawsContent();
// Image animation is "lazy", in that it automatically stops unless someone is
// drawing the image. So we have to kick the animation each time; this has the
// downside that the image will keep animating, even if its layer is not
// visible.
image->startAnimation();
}
FloatPoint3D CompositedLayerMapping::computeTransformOrigin(
const IntRect& borderBox) const {
const ComputedStyle& style = layoutObject().styleRef();
FloatPoint3D origin;
origin.setX(floatValueForLength(style.transformOriginX(), borderBox.width()));
origin.setY(
floatValueForLength(style.transformOriginY(), borderBox.height()));
origin.setZ(style.transformOriginZ());
return origin;
}
// Return the offset from the top-left of this compositing layer at which the
// LayoutObject's contents are painted.
LayoutSize CompositedLayerMapping::contentOffsetInCompositingLayer() const {
DCHECK(!m_contentOffsetInCompositingLayerDirty);
return m_contentOffsetInCompositingLayer;
}
LayoutRect CompositedLayerMapping::contentsBox() const {
LayoutRect contentsBox = LayoutRect(contentsRect(layoutObject()));
contentsBox.move(contentOffsetInCompositingLayer());
return contentsBox;
}
bool CompositedLayerMapping::needsToReparentOverflowControls() const {
return m_owningLayer.getScrollableArea() &&
m_owningLayer.getScrollableArea()->hasOverlayScrollbars() &&
m_owningLayer.getScrollableArea()->topmostScrollChild();
}
GraphicsLayer* CompositedLayerMapping::detachLayerForOverflowControls() {
GraphicsLayer* host = m_overflowControlsAncestorClippingLayer.get();
if (!host)
host = m_overflowControlsHostLayer.get();
host->removeFromParent();
return host;
}
GraphicsLayer* CompositedLayerMapping::parentForSublayers() const {
if (m_scrollingContentsLayer)
return m_scrollingContentsLayer.get();
if (m_childContainmentLayer)
return m_childContainmentLayer.get();
if (m_childTransformLayer)
return m_childTransformLayer.get();
return m_graphicsLayer.get();
}
void CompositedLayerMapping::setSublayers(
const GraphicsLayerVector& sublayers) {
GraphicsLayer* overflowControlsContainer =
m_overflowControlsAncestorClippingLayer
? m_overflowControlsAncestorClippingLayer.get()
: m_overflowControlsHostLayer.get();
GraphicsLayer* parent = parentForSublayers();
bool needsOverflowControlsReattached =
overflowControlsContainer &&
overflowControlsContainer->parent() == parent;
parent->setChildren(sublayers);
// If we have scrollbars, but are not using composited scrolling, then
// parentForSublayers may return m_graphicsLayer. In that case, the above
// call to setChildren has clobbered the overflow controls host layer, so we
// need to reattach it.
if (needsOverflowControlsReattached)
parent->addChild(overflowControlsContainer);
}
GraphicsLayer* CompositedLayerMapping::childForSuperlayers() const {
if (m_squashingContainmentLayer)
return m_squashingContainmentLayer.get();
if (m_ancestorClippingLayer)
return m_ancestorClippingLayer.get();
return m_graphicsLayer.get();
}
void CompositedLayerMapping::setBlendMode(WebBlendMode blendMode) {
if (m_ancestorClippingLayer) {
m_ancestorClippingLayer->setBlendMode(blendMode);
m_graphicsLayer->setBlendMode(WebBlendModeNormal);
} else {
m_graphicsLayer->setBlendMode(blendMode);
}
}
GraphicsLayerUpdater::UpdateType CompositedLayerMapping::updateTypeForChildren(
GraphicsLayerUpdater::UpdateType updateType) const {
if (m_pendingUpdateScope >= GraphicsLayerUpdateSubtree)
return GraphicsLayerUpdater::ForceUpdate;
return updateType;
}
struct SetContentsNeedsDisplayFunctor {
void operator()(GraphicsLayer* layer) const {
if (layer->drawsContent())
layer->setNeedsDisplay();
}
};
void CompositedLayerMapping::setSquashingContentsNeedDisplay() {
ApplyToGraphicsLayers(this, SetContentsNeedsDisplayFunctor(),
ApplyToSquashingLayer);
}
void CompositedLayerMapping::setContentsNeedDisplay() {
// FIXME: need to split out paint invalidations for the background.
ApplyToGraphicsLayers(this, SetContentsNeedsDisplayFunctor(),
ApplyToContentLayers);
}
struct SetContentsNeedsDisplayInRectFunctor {
void operator()(GraphicsLayer* layer) const {
if (layer->drawsContent()) {
IntRect layerDirtyRect = r;
layerDirtyRect.move(-layer->offsetFromLayoutObject());
layer->setNeedsDisplayInRect(layerDirtyRect, invalidationReason, client);
}
}
IntRect r;
PaintInvalidationReason invalidationReason;
const DisplayItemClient& client;
};
void CompositedLayerMapping::setContentsNeedDisplayInRect(
const LayoutRect& r,
PaintInvalidationReason invalidationReason,
const DisplayItemClient& client) {
DCHECK(!m_owningLayer.layoutObject().usesCompositedScrolling());
// TODO(wangxianzhu): Enable the following assert after paint invalidation for
// spv2 is ready.
// DCHECK(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
SetContentsNeedsDisplayInRectFunctor functor = {
enclosingIntRect(LayoutRect(
r.location() + m_owningLayer.subpixelAccumulation(), r.size())),
invalidationReason, client};
ApplyToGraphicsLayers(this, functor, ApplyToContentLayers);
}
void CompositedLayerMapping::setNonScrollingContentsNeedDisplayInRect(
const LayoutRect& r,
PaintInvalidationReason invalidationReason,
const DisplayItemClient& client) {
DCHECK(m_owningLayer.layoutObject().usesCompositedScrolling());
// TODO(wangxianzhu): Enable the following assert after paint invalidation for
// spv2 is ready.
// DCHECK(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
SetContentsNeedsDisplayInRectFunctor functor = {
enclosingIntRect(LayoutRect(
r.location() + m_owningLayer.subpixelAccumulation(), r.size())),
invalidationReason, client};
ApplyToGraphicsLayers(this, functor, ApplyToNonScrollingContentLayers);
}
void CompositedLayerMapping::setScrollingContentsNeedDisplayInRect(
const LayoutRect& r,
PaintInvalidationReason invalidationReason,
const DisplayItemClient& client) {
DCHECK(m_owningLayer.layoutObject().usesCompositedScrolling());
// TODO(wangxianzhu): Enable the following assert after paint invalidation for
// spv2 is ready.
// DCHECK(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
SetContentsNeedsDisplayInRectFunctor functor = {
enclosingIntRect(LayoutRect(
r.location() + m_owningLayer.subpixelAccumulation(), r.size())),
invalidationReason, client};
ApplyToGraphicsLayers(this, functor, ApplyToScrollingContentLayers);
}
const GraphicsLayerPaintInfo* CompositedLayerMapping::containingSquashedLayer(
const LayoutObject* layoutObject,
const Vector<GraphicsLayerPaintInfo>& layers,
unsigned maxSquashedLayerIndex) {
if (!layoutObject)
return nullptr;
for (size_t i = 0; i < layers.size() && i < maxSquashedLayerIndex; ++i) {
if (layoutObject->isDescendantOf(&layers[i].paintLayer->layoutObject()))
return &layers[i];
}
return nullptr;
}
const GraphicsLayerPaintInfo* CompositedLayerMapping::containingSquashedLayer(
const LayoutObject* layoutObject,
unsigned maxSquashedLayerIndex) {
return CompositedLayerMapping::containingSquashedLayer(
layoutObject, m_squashedLayers, maxSquashedLayerIndex);
}
IntRect CompositedLayerMapping::localClipRectForSquashedLayer(
const PaintLayer& referenceLayer,
const GraphicsLayerPaintInfo& paintInfo,
const Vector<GraphicsLayerPaintInfo>& layers) {
const LayoutObject* clippingContainer =
paintInfo.paintLayer->clippingContainer();
if (clippingContainer == referenceLayer.clippingContainer())
return LayoutRect::infiniteIntRect();
DCHECK(clippingContainer);
const GraphicsLayerPaintInfo* ancestorPaintInfo =
containingSquashedLayer(clippingContainer, layers, layers.size());
// Must be there, otherwise
// CompositingLayerAssigner::canSquashIntoCurrentSquashingOwner would have
// disallowed squashing.
DCHECK(ancestorPaintInfo);
// FIXME: this is a potential performance issue. We should consider caching
// these clip rects or otherwise optimizing.
ClipRectsContext clipRectsContext(ancestorPaintInfo->paintLayer,
UncachedClipRects);
ClipRect parentClipRect;
paintInfo.paintLayer->clipper(PaintLayer::DoNotUseGeometryMapper)
.calculateBackgroundClipRect(clipRectsContext, parentClipRect);
IntRect snappedParentClipRect(pixelSnappedIntRect(parentClipRect.rect()));
DCHECK(snappedParentClipRect != LayoutRect::infiniteIntRect());
// Convert from ancestor to local coordinates.
IntSize ancestorToLocalOffset = paintInfo.offsetFromLayoutObject -
ancestorPaintInfo->offsetFromLayoutObject;
snappedParentClipRect.move(ancestorToLocalOffset);
return snappedParentClipRect;
}
void CompositedLayerMapping::doPaintTask(
const GraphicsLayerPaintInfo& paintInfo,
const GraphicsLayer& graphicsLayer,
const PaintLayerFlags& paintLayerFlags,
GraphicsContext& context,
const IntRect& clip /* In the coords of rootLayer */) const {
FontCachePurgePreventer fontCachePurgePreventer;
IntSize offset = paintInfo.offsetFromLayoutObject;
AffineTransform translation;
translation.translate(-offset.width(), -offset.height());
TransformRecorder transformRecorder(context, graphicsLayer, translation);
// The dirtyRect is in the coords of the painting root.
IntRect dirtyRect(clip);
dirtyRect.move(offset);
if (paintLayerFlags & (PaintLayerPaintingOverflowContents |
PaintLayerPaintingAncestorClippingMaskPhase)) {
dirtyRect.move(
roundedIntSize(paintInfo.paintLayer->subpixelAccumulation()));
} else {
LayoutRect bounds = paintInfo.compositedBounds;
bounds.move(paintInfo.paintLayer->subpixelAccumulation());
dirtyRect.intersect(pixelSnappedIntRect(bounds));
}
#if DCHECK_IS_ON()
if (!layoutObject().view()->frame() ||
!layoutObject().view()->frame()->shouldThrottleRendering())
paintInfo.paintLayer->layoutObject().assertSubtreeIsLaidOut();
#endif
float deviceScaleFactor = blink::deviceScaleFactorDeprecated(
paintInfo.paintLayer->layoutObject().frame());
context.setDeviceScaleFactor(deviceScaleFactor);
if (paintInfo.paintLayer->compositingState() != PaintsIntoGroupedBacking) {
// FIXME: GraphicsLayers need a way to split for multicol.
PaintLayerPaintingInfo paintingInfo(
paintInfo.paintLayer, LayoutRect(dirtyRect), GlobalPaintNormalPhase,
paintInfo.paintLayer->subpixelAccumulation());
PaintLayerPainter(*paintInfo.paintLayer)
.paintLayerContents(context, paintingInfo, paintLayerFlags);
if (paintInfo.paintLayer->containsDirtyOverlayScrollbars())
PaintLayerPainter(*paintInfo.paintLayer)
.paintLayerContents(
context, paintingInfo,
paintLayerFlags | PaintLayerPaintingOverlayScrollbars);
} else {
PaintLayerPaintingInfo paintingInfo(
paintInfo.paintLayer, LayoutRect(dirtyRect), GlobalPaintNormalPhase,
paintInfo.paintLayer->subpixelAccumulation());
// PaintLayer::paintLayer assumes that the caller clips to the passed rect.
// Squashed layers need to do this clipping in software, since there is no
// graphics layer to clip them precisely. Furthermore, in some cases we
// squash layers that need clipping in software from clipping ancestors (see
// CompositedLayerMapping::localClipRectForSquashedLayer()).
// FIXME: Is it correct to clip to dirtyRect in slimming paint mode?
// FIXME: Combine similar code here and LayerClipRecorder.
dirtyRect.intersect(paintInfo.localClipRectForSquashedLayer);
context.getPaintController().createAndAppend<ClipDisplayItem>(
graphicsLayer, DisplayItem::kClipLayerOverflowControls, dirtyRect);
PaintLayerPainter(*paintInfo.paintLayer)
.paint(context, paintingInfo, paintLayerFlags);
context.getPaintController().endItem<EndClipDisplayItem>(
graphicsLayer, DisplayItem::clipTypeToEndClipType(
DisplayItem::kClipLayerOverflowControls));
}
}
static void paintScrollbar(const Scrollbar* scrollbar,
GraphicsContext& context,
const IntRect& clip) {
if (!scrollbar)
return;
const IntRect& scrollbarRect = scrollbar->frameRect();
TransformRecorder transformRecorder(
context, *scrollbar,
AffineTransform::translation(-scrollbarRect.x(), -scrollbarRect.y()));
IntRect transformedClip = clip;
transformedClip.moveBy(scrollbarRect.location());
scrollbar->paint(context, CullRect(transformedClip));
}
// TODO(eseckler): Make recording distance configurable, e.g. for use in
// headless, where we would like to record an exact area.
// Note however that the minimum value for this constant is the size of a
// raster tile. This is because the raster system is not able to raster a
// tile that is not completely covered by a display list. If the constant
// were less than the size of a tile, then a tile which partially overlaps
// the screen may not be rastered.
static const int kPixelDistanceToRecord = 4000;
IntRect CompositedLayerMapping::recomputeInterestRect(
const GraphicsLayer* graphicsLayer) const {
FloatRect graphicsLayerBounds(FloatPoint(), graphicsLayer->size());
IntSize offsetFromAnchorLayoutObject;
const LayoutBoxModelObject* anchorLayoutObject;
if (graphicsLayer == m_squashingLayer.get()) {
// TODO(chrishtr): this is a speculative fix for crbug.com/561306. However,
// it should never be the case that m_squashingLayer exists,
// yet m_squashedLayers.size() == 0. There must be a bug elsewhere.
if (m_squashedLayers.size() == 0)
return IntRect();
// All squashed layers have the same clip and transform space, so we can use
// the first squashed layer's layoutObject to map the squashing layer's
// bounds into viewport space, with offsetFromAnchorLayoutObject to
// translate squashing layer's bounds into the first squashed layer's space.
anchorLayoutObject = &m_squashedLayers[0].paintLayer->layoutObject();
offsetFromAnchorLayoutObject = m_squashedLayers[0].offsetFromLayoutObject;
} else {
DCHECK(graphicsLayer == m_graphicsLayer.get() ||
graphicsLayer == m_scrollingContentsLayer.get());
anchorLayoutObject = &m_owningLayer.layoutObject();
offsetFromAnchorLayoutObject = graphicsLayer->offsetFromLayoutObject();
adjustForCompositedScrolling(graphicsLayer, offsetFromAnchorLayoutObject);
}
// Start with the bounds of the graphics layer in the space of the anchor
// LayoutObject.
FloatRect graphicsLayerBoundsInObjectSpace(graphicsLayerBounds);
graphicsLayerBoundsInObjectSpace.move(offsetFromAnchorLayoutObject);
// Now map the bounds to its visible content rect in root view space,
// including applying clips along the way.
LayoutRect graphicsLayerBoundsInRootViewSpace(
graphicsLayerBoundsInObjectSpace);
LayoutView* rootView = anchorLayoutObject->view();
while (!rootView->frame()->ownerLayoutItem().isNull())
rootView =
LayoutAPIShim::layoutObjectFrom(rootView->frame()->ownerLayoutItem())
->view();
anchorLayoutObject->mapToVisualRectInAncestorSpace(
rootView, graphicsLayerBoundsInRootViewSpace);
FloatRect visibleContentRect(graphicsLayerBoundsInRootViewSpace);
rootView->frameView()->clipPaintRect(&visibleContentRect);
IntRect enclosingGraphicsLayerBounds(enclosingIntRect(graphicsLayerBounds));
// Map the visible content rect from root view space to local graphics layer
// space.
IntRect localInterestRect;
// If the visible content rect is empty, then it makes no sense to map it back
// since there is nothing to map.
if (!visibleContentRect.isEmpty()) {
localInterestRect =
anchorLayoutObject
->absoluteToLocalQuad(visibleContentRect,
UseTransforms | TraverseDocumentBoundaries)
.enclosingBoundingBox();
localInterestRect.move(-offsetFromAnchorLayoutObject);
// TODO(chrishtr): the code below is a heuristic, instead we should detect
// and return whether the mapping failed. In some cases,
// absoluteToLocalQuad can fail to map back to the local space, due to
// passing through non-invertible transforms or floating-point accuracy
// issues. Examples include rotation near 90 degrees or perspective. In such
// cases, fall back to painting the first kPixelDistanceToRecord pixels in
// each direction.
localInterestRect.intersect(enclosingGraphicsLayerBounds);
}
// Expand by interest rect padding amount.
localInterestRect.inflate(kPixelDistanceToRecord);
localInterestRect.intersect(enclosingGraphicsLayerBounds);
return localInterestRect;
}
static const int kMinimumDistanceBeforeRepaint = 512;
bool CompositedLayerMapping::interestRectChangedEnoughToRepaint(
const IntRect& previousInterestRect,
const IntRect& newInterestRect,
const IntSize& layerSize) {
if (previousInterestRect.isEmpty() && newInterestRect.isEmpty())
return false;
// Repaint when going from empty to not-empty, to cover cases where the layer
// is painted for the first time, or otherwise becomes visible.
if (previousInterestRect.isEmpty())
return true;
// Repaint if the new interest rect includes area outside of a skirt around
// the existing interest rect.
IntRect expandedPreviousInterestRect(previousInterestRect);
expandedPreviousInterestRect.inflate(kMinimumDistanceBeforeRepaint);
if (!expandedPreviousInterestRect.contains(newInterestRect))
return true;
// Even if the new interest rect doesn't include enough new area to satisfy
// the condition above, repaint anyway if it touches a layer edge not touched
// by the existing interest rect. Because it's impossible to expose more area
// in the direction, repainting cannot be deferred until the exposed new area
// satisfies the condition above.
if (newInterestRect.x() == 0 && previousInterestRect.x() != 0)
return true;
if (newInterestRect.y() == 0 && previousInterestRect.y() != 0)
return true;
if (newInterestRect.maxX() == layerSize.width() &&
previousInterestRect.maxX() != layerSize.width())
return true;
if (newInterestRect.maxY() == layerSize.height() &&
previousInterestRect.maxY() != layerSize.height())
return true;
return false;
}
IntRect CompositedLayerMapping::computeInterestRect(
const GraphicsLayer* graphicsLayer,
const IntRect& previousInterestRect) const {
// Use the previous interest rect if it covers the whole layer.
IntRect wholeLayerRect =
IntRect(IntPoint(), expandedIntSize(graphicsLayer->size()));
if (!needsRepaint(*graphicsLayer) && previousInterestRect == wholeLayerRect)
return previousInterestRect;
if (graphicsLayer != m_graphicsLayer.get() &&
graphicsLayer != m_squashingLayer.get() &&
graphicsLayer != m_scrollingContentsLayer.get())
return wholeLayerRect;
IntRect newInterestRect = recomputeInterestRect(graphicsLayer);
if (needsRepaint(*graphicsLayer) ||
interestRectChangedEnoughToRepaint(
previousInterestRect, newInterestRect,
expandedIntSize(graphicsLayer->size())))
return newInterestRect;
return previousInterestRect;
}
LayoutSize CompositedLayerMapping::subpixelAccumulation() const {
return m_owningLayer.subpixelAccumulation();
}
bool CompositedLayerMapping::needsRepaint(
const GraphicsLayer& graphicsLayer) const {
return isScrollableAreaLayer(&graphicsLayer) ? true
: m_owningLayer.needsRepaint();
}
void CompositedLayerMapping::adjustForCompositedScrolling(
const GraphicsLayer* graphicsLayer,
IntSize& offset) const {
if (graphicsLayer == m_scrollingContentsLayer.get() ||
graphicsLayer == m_foregroundLayer.get()) {
if (PaintLayerScrollableArea* scrollableArea =
m_owningLayer.getScrollableArea()) {
if (scrollableArea->usesCompositedScrolling()) {
// Note: this is the offset from the beginning of flow of the block, not
// the offset from the top/left of the overflow rect.
// offsetFromLayoutObject adds the origin offset from top/left to the
// beginning of flow.
ScrollOffset scrollOffset = scrollableArea->getScrollOffset();
offset.expand(-scrollOffset.width(), -scrollOffset.height());
}
}
}
}
void CompositedLayerMapping::paintContents(
const GraphicsLayer* graphicsLayer,
GraphicsContext& context,
GraphicsLayerPaintingPhase graphicsLayerPaintingPhase,
const IntRect& interestRect) const {
// https://code.google.com/p/chromium/issues/detail?id=343772
DisableCompositingQueryAsserts disabler;
// Allow throttling to make sure no painting paths (e.g.,
// ContentLayerDelegate::paintContents) try to paint throttled content.
DocumentLifecycle::AllowThrottlingScope allowThrottling(
m_owningLayer.layoutObject().document().lifecycle());
#if DCHECK_IS_ON()
// FIXME: once the state machine is ready, this can be removed and we can
// refer to that instead.
if (Page* page = layoutObject().frame()->page())
page->setIsPainting(true);
#endif
TRACE_EVENT1(
"devtools.timeline,rail", "Paint", "data",
InspectorPaintEvent::data(&m_owningLayer.layoutObject(),
LayoutRect(interestRect), graphicsLayer));
PaintLayerFlags paintLayerFlags = 0;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintBackground)
paintLayerFlags |= PaintLayerPaintingCompositingBackgroundPhase;
else
paintLayerFlags |= PaintLayerPaintingSkipRootBackground;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintForeground)
paintLayerFlags |= PaintLayerPaintingCompositingForegroundPhase;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintMask)
paintLayerFlags |= PaintLayerPaintingCompositingMaskPhase;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintChildClippingMask)
paintLayerFlags |= PaintLayerPaintingChildClippingMaskPhase;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintAncestorClippingMask)
paintLayerFlags |= PaintLayerPaintingAncestorClippingMaskPhase;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintOverflowContents)
paintLayerFlags |= PaintLayerPaintingOverflowContents;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintCompositedScroll)
paintLayerFlags |= PaintLayerPaintingCompositingScrollingPhase;
if (graphicsLayerPaintingPhase & GraphicsLayerPaintDecoration)
paintLayerFlags |= PaintLayerPaintingCompositingDecorationPhase;
if (graphicsLayer == m_backgroundLayer.get())
paintLayerFlags |= PaintLayerPaintingRootBackgroundOnly;
else if (compositor()->fixedRootBackgroundLayer() &&
m_owningLayer.isRootLayer())
paintLayerFlags |= PaintLayerPaintingSkipRootBackground;
if (graphicsLayer == m_graphicsLayer.get() ||
graphicsLayer == m_foregroundLayer.get() ||
graphicsLayer == m_backgroundLayer.get() ||
graphicsLayer == m_maskLayer.get() ||
graphicsLayer == m_childClippingMaskLayer.get() ||
graphicsLayer == m_scrollingContentsLayer.get() ||
graphicsLayer == m_decorationOutlineLayer.get() ||
graphicsLayer == m_ancestorClippingMaskLayer.get()) {
bool paintRootBackgroundOntoScrollingContentsLayer =
m_backgroundPaintsOntoScrollingContentsLayer;
DCHECK(!paintRootBackgroundOntoScrollingContentsLayer ||
(!m_backgroundLayer && !m_foregroundLayer));
if (paintRootBackgroundOntoScrollingContentsLayer) {
if (graphicsLayer == m_scrollingContentsLayer.get())
paintLayerFlags &= ~PaintLayerPaintingSkipRootBackground;
else if (!m_backgroundPaintsOntoGraphicsLayer)
paintLayerFlags |= PaintLayerPaintingSkipRootBackground;
}
GraphicsLayerPaintInfo paintInfo;
paintInfo.paintLayer = &m_owningLayer;
paintInfo.compositedBounds = compositedBounds();
paintInfo.offsetFromLayoutObject = graphicsLayer->offsetFromLayoutObject();
adjustForCompositedScrolling(graphicsLayer,
paintInfo.offsetFromLayoutObject);
// We have to use the same root as for hit testing, because both methods can
// compute and cache clipRects.
doPaintTask(paintInfo, *graphicsLayer, paintLayerFlags, context,
interestRect);
} else if (graphicsLayer == m_squashingLayer.get()) {
for (size_t i = 0; i < m_squashedLayers.size(); ++i)
doPaintTask(m_squashedLayers[i], *graphicsLayer, paintLayerFlags, context,
interestRect);
} else if (isScrollableAreaLayer(graphicsLayer)) {
paintScrollableArea(graphicsLayer, context, interestRect);
}
probe::didPaint(m_owningLayer.layoutObject().frame(), graphicsLayer, context,
LayoutRect(interestRect));
#if DCHECK_IS_ON()
if (Page* page = layoutObject().frame()->page())
page->setIsPainting(false);
#endif
}
void CompositedLayerMapping::paintScrollableArea(
const GraphicsLayer* graphicsLayer,
GraphicsContext& context,
const IntRect& interestRect) const {
// Note the composited scrollable area painted here is never associated with a
// frame. For painting frame ScrollableAreas, see
// PaintLayerCompositor::paintContents.
if (DrawingRecorder::useCachedDrawingIfPossible(
context, *graphicsLayer, DisplayItem::kScrollbarCompositedScrollbar))
return;
FloatRect layerBounds(FloatPoint(), graphicsLayer->size());
PaintRecordBuilder builder(layerBounds, nullptr, &context);
PaintLayerScrollableArea* scrollableArea = m_owningLayer.getScrollableArea();
if (graphicsLayer == layerForHorizontalScrollbar()) {
paintScrollbar(scrollableArea->horizontalScrollbar(), builder.context(),
interestRect);
} else if (graphicsLayer == layerForVerticalScrollbar()) {
paintScrollbar(scrollableArea->verticalScrollbar(), builder.context(),
interestRect);
} else if (graphicsLayer == layerForScrollCorner()) {
// Note that scroll corners always paint into local space, whereas
// scrollbars paint in the space of their containing frame.
IntPoint scrollCornerAndResizerLocation =
scrollableArea->scrollCornerAndResizerRect().location();
CullRect cullRect(enclosingIntRect(interestRect));
ScrollableAreaPainter(*scrollableArea)
.paintScrollCorner(builder.context(), -scrollCornerAndResizerLocation,
cullRect);
ScrollableAreaPainter(*scrollableArea)
.paintResizer(builder.context(), -scrollCornerAndResizerLocation,
cullRect);
}
// Replay the painted scrollbar content with the GraphicsLayer backing as the
// DisplayItemClient in order for the resulting DrawingDisplayItem to produce
// the correct visualRect (i.e., the bounds of the involved GraphicsLayer).
DrawingRecorder drawingRecorder(context, *graphicsLayer,
DisplayItem::kScrollbarCompositedScrollbar,
layerBounds);
context.canvas()->PlaybackPaintRecord(builder.endRecording());
}
bool CompositedLayerMapping::isScrollableAreaLayer(
const GraphicsLayer* graphicsLayer) const {
return graphicsLayer == layerForHorizontalScrollbar() ||
graphicsLayer == layerForVerticalScrollbar() ||
graphicsLayer == layerForScrollCorner();
}
bool CompositedLayerMapping::isTrackingRasterInvalidations() const {
GraphicsLayerClient* client = compositor();
return client ? client->isTrackingRasterInvalidations() : false;
}
#if DCHECK_IS_ON()
void CompositedLayerMapping::verifyNotPainting() {
DCHECK(!layoutObject().frame()->page() ||
!layoutObject().frame()->page()->isPainting());
}
#endif
// Only used for performance benchmark testing. Intended to be a
// sufficiently-unique element id name to allow picking out the target element
// for invalidation.
static const char* kTestPaintInvalidationTargetName =
"blinkPaintInvalidationTarget";
void CompositedLayerMapping::invalidateTargetElementForTesting() {
// The below is an artificial construct formed intentionally to focus a
// microbenchmark on the cost of paint with a partial invalidation.
Element* targetElement =
m_owningLayer.layoutObject().document().getElementById(
AtomicString(kTestPaintInvalidationTargetName));
// TODO(wkorman): If we don't find the expected target element, we could
// consider walking to the first leaf node so that the partial-invalidation
// benchmark mode still provides some value when running on generic pages.
if (!targetElement)
return;
LayoutObject* targetObject = targetElement->layoutObject();
if (!targetObject)
return;
targetObject->enclosingLayer()->setNeedsRepaint();
// TODO(wkorman): Consider revising the below to invalidate all
// non-compositing descendants as well.
targetObject->invalidateDisplayItemClients(PaintInvalidationForTesting);
}
IntRect CompositedLayerMapping::pixelSnappedCompositedBounds() const {
LayoutRect bounds = m_compositedBounds;
bounds.move(m_owningLayer.subpixelAccumulation());
return pixelSnappedIntRect(bounds);
}
bool CompositedLayerMapping::invalidateLayerIfNoPrecedingEntry(
size_t indexToClear) {
PaintLayer* layerToRemove = m_squashedLayers[indexToClear].paintLayer;
size_t previousIndex = 0;
for (; previousIndex < indexToClear; ++previousIndex) {
if (m_squashedLayers[previousIndex].paintLayer == layerToRemove)
break;
}
if (previousIndex == indexToClear &&
layerToRemove->groupedMapping() == this) {
compositor()->paintInvalidationOnCompositingChange(layerToRemove);
return true;
}
return false;
}
bool CompositedLayerMapping::updateSquashingLayerAssignment(
PaintLayer* squashedLayer,
size_t nextSquashedLayerIndex) {
GraphicsLayerPaintInfo paintInfo;
paintInfo.paintLayer = squashedLayer;
// NOTE: composited bounds are updated elsewhere
// NOTE: offsetFromLayoutObject is updated elsewhere
// Change tracking on squashing layers: at the first sign of something
// changed, just invalidate the layer.
// FIXME: Perhaps we can find a tighter more clever mechanism later.
if (nextSquashedLayerIndex < m_squashedLayers.size()) {
if (paintInfo.paintLayer ==
m_squashedLayers[nextSquashedLayerIndex].paintLayer)
return false;
// Must invalidate before adding the squashed layer to the mapping.
compositor()->paintInvalidationOnCompositingChange(squashedLayer);
// If the layer which was previously at |nextSquashedLayerIndex| is not
// earlier in the grouped mapping, invalidate its current backing now, since
// it will move later or be removed from the squashing layer.
invalidateLayerIfNoPrecedingEntry(nextSquashedLayerIndex);
m_squashedLayers.insert(nextSquashedLayerIndex, paintInfo);
} else {
// Must invalidate before adding the squashed layer to the mapping.
compositor()->paintInvalidationOnCompositingChange(squashedLayer);
m_squashedLayers.push_back(paintInfo);
}
squashedLayer->setGroupedMapping(
this, PaintLayer::InvalidateLayerAndRemoveFromMapping);
return true;
}
void CompositedLayerMapping::removeLayerFromSquashingGraphicsLayer(
const PaintLayer* layer) {
size_t layerIndex = 0;
for (; layerIndex < m_squashedLayers.size(); ++layerIndex) {
if (m_squashedLayers[layerIndex].paintLayer == layer)
break;
}
// Assert on incorrect mappings between layers and groups
DCHECK_LT(layerIndex, m_squashedLayers.size());
if (layerIndex == m_squashedLayers.size())
return;
m_squashedLayers.erase(layerIndex);
}
#if DCHECK_IS_ON()
bool CompositedLayerMapping::verifyLayerInSquashingVector(
const PaintLayer* layer) {
for (size_t layerIndex = 0; layerIndex < m_squashedLayers.size();
++layerIndex) {
if (m_squashedLayers[layerIndex].paintLayer == layer)
return true;
}
return false;
}
#endif
void CompositedLayerMapping::finishAccumulatingSquashingLayers(
size_t nextSquashedLayerIndex,
Vector<PaintLayer*>& layersNeedingPaintInvalidation) {
if (nextSquashedLayerIndex < m_squashedLayers.size()) {
// Any additional squashed Layers in the array no longer belong here, but
// they might have been added already at an earlier index. Clear pointers on
// those that do not appear in the valid set before removing all the extra
// entries.
for (size_t i = nextSquashedLayerIndex; i < m_squashedLayers.size(); ++i) {
if (invalidateLayerIfNoPrecedingEntry(i))
m_squashedLayers[i].paintLayer->setGroupedMapping(
nullptr, PaintLayer::DoNotInvalidateLayerAndRemoveFromMapping);
layersNeedingPaintInvalidation.push_back(m_squashedLayers[i].paintLayer);
}
m_squashedLayers.erase(nextSquashedLayerIndex,
m_squashedLayers.size() - nextSquashedLayerIndex);
}
}
String CompositedLayerMapping::debugName(
const GraphicsLayer* graphicsLayer) const {
String name;
if (graphicsLayer == m_graphicsLayer.get()) {
name = m_owningLayer.debugName();
} else if (graphicsLayer == m_squashingContainmentLayer.get()) {
name = "Squashing Containment Layer";
} else if (graphicsLayer == m_squashingLayer.get()) {
name = "Squashing Layer (first squashed layer: " +
(m_squashedLayers.size() > 0
? m_squashedLayers[0].paintLayer->debugName()
: "") +
")";
} else if (graphicsLayer == m_ancestorClippingLayer.get()) {
name = "Ancestor Clipping Layer";
} else if (graphicsLayer == m_ancestorClippingMaskLayer.get()) {
name = "Ancestor Clipping Mask Layer";
} else if (graphicsLayer == m_foregroundLayer.get()) {
name = m_owningLayer.debugName() + " (foreground) Layer";
} else if (graphicsLayer == m_backgroundLayer.get()) {
name = m_owningLayer.debugName() + " (background) Layer";
} else if (graphicsLayer == m_childContainmentLayer.get()) {
name = "Child Containment Layer";
} else if (graphicsLayer == m_childTransformLayer.get()) {
name = "Child Transform Layer";
} else if (graphicsLayer == m_maskLayer.get()) {
name = "Mask Layer";
} else if (graphicsLayer == m_childClippingMaskLayer.get()) {
name = "Child Clipping Mask Layer";
} else if (graphicsLayer == m_layerForHorizontalScrollbar.get()) {
name = "Horizontal Scrollbar Layer";
} else if (graphicsLayer == m_layerForVerticalScrollbar.get()) {
name = "Vertical Scrollbar Layer";
} else if (graphicsLayer == m_layerForScrollCorner.get()) {
name = "Scroll Corner Layer";
} else if (graphicsLayer == m_overflowControlsHostLayer.get()) {
name = "Overflow Controls Host Layer";
} else if (graphicsLayer == m_overflowControlsAncestorClippingLayer.get()) {
name = "Overflow Controls Ancestor Clipping Layer";
} else if (graphicsLayer == m_scrollingLayer.get()) {
name = "Scrolling Layer";
} else if (graphicsLayer == m_scrollingContentsLayer.get()) {
name = "Scrolling Contents Layer";
} else if (graphicsLayer == m_decorationOutlineLayer.get()) {
name = "Decoration Layer";
} else {
NOTREACHED();
}
return name;
}
} // namespace blink