blob: 1ca2e32ed7f073085436266a76309bb76974c354 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "core/paint/LayerPainter.h"
#include "core/frame/Settings.h"
#include "core/page/Page.h"
#include "core/rendering/ClipPathOperation.h"
#include "core/rendering/FilterEffectRenderer.h"
#include "core/rendering/PaintInfo.h"
#include "core/rendering/RenderBlock.h"
#include "core/rendering/RenderLayer.h"
#include "core/rendering/RenderView.h"
#include "core/rendering/svg/RenderSVGResourceClipper.h"
namespace blink {
static inline bool shouldSuppressPaintingLayer(RenderLayer* layer)
{
// Avoid painting descendants of the root layer when stylesheets haven't loaded. This eliminates FOUC.
// It's ok not to draw, because later on, when all the stylesheets do load, updateStyleSelector on the Document
// will do a full paintInvalidationForWholeRenderer().
if (layer->renderer()->document().didLayoutWithPendingStylesheets() && !layer->isRootLayer() && !layer->renderer()->isDocumentElement())
return true;
return false;
}
void LayerPainter::paint(GraphicsContext* context, const LayoutRect& damageRect, PaintBehavior paintBehavior, RenderObject* paintingRoot, PaintLayerFlags paintFlags)
{
LayerPaintingInfo paintingInfo(&m_renderLayer, enclosingIntRect(damageRect), paintBehavior, LayoutSize(), paintingRoot);
if (shouldPaintLayerInSoftwareMode(paintingInfo, paintFlags))
paintLayer(context, paintingInfo, paintFlags);
}
static ShouldRespectOverflowClip shouldRespectOverflowClip(PaintLayerFlags paintFlags, const RenderObject* renderer)
{
return (paintFlags & PaintLayerPaintingOverflowContents || (paintFlags & PaintLayerPaintingChildClippingMaskPhase && renderer->hasClipPath())) ? IgnoreOverflowClip : RespectOverflowClip;
}
void LayerPainter::paintLayer(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
// https://code.google.com/p/chromium/issues/detail?id=343772
DisableCompositingQueryAsserts disabler;
if (m_renderLayer.compositingState() != NotComposited) {
if (paintingInfo.paintBehavior & PaintBehaviorFlattenCompositingLayers) {
// FIXME: ok, but what about PaintBehaviorFlattenCompositingLayers? That's for printing.
// FIXME: why isn't the code here global, as opposed to being set on each paintLayer() call?
paintFlags |= PaintLayerUncachedClipRects;
}
}
// Non self-painting leaf layers don't need to be painted as their renderer() should properly paint itself.
if (!m_renderLayer.isSelfPaintingLayer() && !m_renderLayer.hasSelfPaintingLayerDescendant())
return;
if (shouldSuppressPaintingLayer(&m_renderLayer))
return;
// If this layer is totally invisible then there is nothing to paint.
if (!m_renderLayer.renderer()->opacity())
return;
if (m_renderLayer.paintsWithTransparency(paintingInfo.paintBehavior))
paintFlags |= PaintLayerHaveTransparency;
// PaintLayerAppliedTransform is used in RenderReplica, to avoid applying the transform twice.
if (m_renderLayer.paintsWithTransform(paintingInfo.paintBehavior) && !(paintFlags & PaintLayerAppliedTransform)) {
TransformationMatrix layerTransform = m_renderLayer.renderableTransform(paintingInfo.paintBehavior);
// If the transform can't be inverted, then don't paint anything.
if (!layerTransform.isInvertible())
return;
// If we have a transparency layer enclosing us and we are the root of a transform, then we need to establish the transparency
// layer from the parent now, assuming there is a parent
if (paintFlags & PaintLayerHaveTransparency) {
if (m_renderLayer.parent())
LayerPainter(*m_renderLayer.parent()).beginTransparencyLayers(context, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation, paintingInfo.paintBehavior);
else
beginTransparencyLayers(context, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation, paintingInfo.paintBehavior);
}
if (m_renderLayer.enclosingPaginationLayer()) {
paintTransformedLayerIntoFragments(context, paintingInfo, paintFlags);
return;
}
// Make sure the parent's clip rects have been calculated.
ClipRect clipRect = paintingInfo.paintDirtyRect;
OwnPtr<ClipRecorder> clipRecorder;
if (m_renderLayer.parent()) {
ClipRectsContext clipRectsContext(paintingInfo.rootLayer, (paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize);
if (shouldRespectOverflowClip(paintFlags, m_renderLayer.renderer()) == IgnoreOverflowClip)
clipRectsContext.setIgnoreOverflowClip();
clipRect = m_renderLayer.clipper().backgroundClipRect(clipRectsContext);
clipRect.intersect(paintingInfo.paintDirtyRect);
if (needsToClip(paintingInfo, clipRect)) {
clipRecorder = adoptPtr(new ClipRecorder(m_renderLayer.parent(), context, DisplayItem::ClipLayerParent, clipRect));
LayerPainter(*m_renderLayer.parent()).applyRoundedRectClips(paintingInfo, context, clipRect, paintFlags, *clipRecorder);
}
}
paintLayerByApplyingTransform(context, paintingInfo, paintFlags);
return;
}
paintLayerContentsAndReflection(context, paintingInfo, paintFlags);
}
void LayerPainter::beginTransparencyLayers(GraphicsContext* context, const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, const LayoutSize& subPixelAccumulation, PaintBehavior paintBehavior)
{
bool createTransparencyLayerForBlendMode = m_renderLayer.stackingNode()->isStackingContext() && m_renderLayer.hasNonIsolatedDescendantWithBlendMode();
if ((m_renderLayer.paintsWithTransparency(paintBehavior) || createTransparencyLayerForBlendMode) && m_renderLayer.usedTransparency())
return;
RenderLayer* ancestor = m_renderLayer.transparentPaintingAncestor();
if (ancestor)
LayerPainter(*ancestor).beginTransparencyLayers(context, rootLayer, paintDirtyRect, subPixelAccumulation, paintBehavior);
if (m_renderLayer.paintsWithTransparency(paintBehavior) || createTransparencyLayerForBlendMode) {
m_renderLayer.setUsedTransparency(true);
context->save();
LayoutRect clipRect = m_renderLayer.paintingExtent(rootLayer, paintDirtyRect, subPixelAccumulation, paintBehavior);
context->clip(clipRect);
if (m_renderLayer.renderer()->hasBlendMode())
context->setCompositeOperation(context->compositeOperation(), m_renderLayer.renderer()->style()->blendMode());
context->beginTransparencyLayer(m_renderLayer.renderer()->opacity());
if (m_renderLayer.renderer()->hasBlendMode())
context->setCompositeOperation(context->compositeOperation(), WebBlendModeNormal);
#ifdef REVEAL_TRANSPARENCY_LAYERS
context->fillRect(clipRect, Color(0.0f, 0.0f, 0.5f, 0.2f));
#endif
}
}
void LayerPainter::paintLayerContentsAndReflection(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
ASSERT(m_renderLayer.isSelfPaintingLayer() || m_renderLayer.hasSelfPaintingLayerDescendant());
PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform);
// Paint the reflection first if we have one.
if (m_renderLayer.reflectionInfo())
m_renderLayer.reflectionInfo()->paint(context, paintingInfo, localPaintFlags | PaintLayerPaintingReflection);
localPaintFlags |= PaintLayerPaintingCompositingAllPhases;
paintLayerContents(context, paintingInfo, localPaintFlags);
}
void LayerPainter::paintLayerContents(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
ASSERT(m_renderLayer.isSelfPaintingLayer() || m_renderLayer.hasSelfPaintingLayerDescendant());
ASSERT(!(paintFlags & PaintLayerAppliedTransform));
bool haveTransparency = paintFlags & PaintLayerHaveTransparency;
bool isSelfPaintingLayer = m_renderLayer.isSelfPaintingLayer();
bool isPaintingOverlayScrollbars = paintFlags & PaintLayerPaintingOverlayScrollbars;
bool isPaintingScrollingContent = paintFlags & PaintLayerPaintingCompositingScrollingPhase;
bool isPaintingCompositedForeground = paintFlags & PaintLayerPaintingCompositingForegroundPhase;
bool isPaintingCompositedBackground = paintFlags & PaintLayerPaintingCompositingBackgroundPhase;
bool isPaintingOverflowContents = paintFlags & PaintLayerPaintingOverflowContents;
// Outline always needs to be painted even if we have no visible content. Also,
// the outline is painted in the background phase during composited scrolling.
// If it were painted in the foreground phase, it would move with the scrolled
// content. When not composited scrolling, the outline is painted in the
// foreground phase. Since scrolled contents are moved by paint invalidation in this
// case, the outline won't get 'dragged along'.
bool shouldPaintOutline = isSelfPaintingLayer && !isPaintingOverlayScrollbars
&& ((isPaintingScrollingContent && isPaintingCompositedBackground)
|| (!isPaintingScrollingContent && isPaintingCompositedForeground));
bool shouldPaintContent = m_renderLayer.hasVisibleContent() && isSelfPaintingLayer && !isPaintingOverlayScrollbars;
float deviceScaleFactor = blink::deviceScaleFactor(m_renderLayer.renderer()->frame());
context->setDeviceScaleFactor(deviceScaleFactor);
if (paintFlags & PaintLayerPaintingRootBackgroundOnly && !m_renderLayer.renderer()->isRenderView() && !m_renderLayer.renderer()->isDocumentElement())
return;
// Ensure our lists are up-to-date.
m_renderLayer.stackingNode()->updateLayerListsIfNeeded();
LayoutPoint offsetFromRoot;
m_renderLayer.convertToLayerCoords(paintingInfo.rootLayer, offsetFromRoot);
if (m_renderLayer.compositingState() == PaintsIntoOwnBacking)
offsetFromRoot.move(m_renderLayer.subpixelAccumulation());
LayoutRect rootRelativeBounds;
bool rootRelativeBoundsComputed = false;
// Apply clip-path to context.
GraphicsContextStateSaver clipStateSaver(*context, false);
RenderStyle* style = m_renderLayer.renderer()->style();
RenderSVGResourceClipper* resourceClipper = 0;
RenderSVGResourceClipper::ClipperState clipperState = RenderSVGResourceClipper::ClipperNotApplied;
// Clip-path, like border radius, must not be applied to the contents of a composited-scrolling container.
// It must, however, still be applied to the mask layer, so that the compositor can properly mask the
// scrolling contents and scrollbars.
if (m_renderLayer.renderer()->hasClipPath() && style && (!m_renderLayer.needsCompositedScrolling() || paintFlags & PaintLayerPaintingChildClippingMaskPhase)) {
ASSERT(style->clipPath());
if (style->clipPath()->type() == ClipPathOperation::SHAPE) {
ShapeClipPathOperation* clipPath = toShapeClipPathOperation(style->clipPath());
if (clipPath->isValid()) {
clipStateSaver.save();
if (!rootRelativeBoundsComputed) {
rootRelativeBounds = m_renderLayer.physicalBoundingBoxIncludingReflectionAndStackingChildren(paintingInfo.rootLayer, offsetFromRoot);
rootRelativeBoundsComputed = true;
}
context->clipPath(clipPath->path(rootRelativeBounds), clipPath->windRule());
}
} else if (style->clipPath()->type() == ClipPathOperation::REFERENCE) {
ReferenceClipPathOperation* referenceClipPathOperation = toReferenceClipPathOperation(style->clipPath());
Document& document = m_renderLayer.renderer()->document();
// FIXME: It doesn't work with forward or external SVG references (https://bugs.webkit.org/show_bug.cgi?id=90405)
Element* element = document.getElementById(referenceClipPathOperation->fragment());
if (isSVGClipPathElement(element) && element->renderer()) {
// FIXME: Saving at this point is not required in the 'mask'-
// case, or if the clip ends up empty.
clipStateSaver.save();
if (!rootRelativeBoundsComputed) {
rootRelativeBounds = m_renderLayer.physicalBoundingBoxIncludingReflectionAndStackingChildren(paintingInfo.rootLayer, offsetFromRoot);
rootRelativeBoundsComputed = true;
}
resourceClipper = toRenderSVGResourceClipper(toRenderSVGResourceContainer(element->renderer()));
if (!resourceClipper->applyClippingToContext(m_renderLayer.renderer(), rootRelativeBounds,
paintingInfo.paintDirtyRect, context, clipperState)) {
// No need to post-apply the clipper if this failed.
resourceClipper = 0;
}
}
}
}
// Blending operations must be performed only with the nearest ancestor stacking context.
// Note that there is no need to create a transparency layer if we're painting the root.
bool createTransparencyLayerForBlendMode = !m_renderLayer.renderer()->isDocumentElement() && m_renderLayer.stackingNode()->isStackingContext() && m_renderLayer.hasNonIsolatedDescendantWithBlendMode();
if (createTransparencyLayerForBlendMode)
beginTransparencyLayers(context, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation, paintingInfo.paintBehavior);
LayerPaintingInfo localPaintingInfo(paintingInfo);
bool haveFilterEffect = m_renderLayer.filterRenderer() && m_renderLayer.paintsWithFilters();
LayerFragments layerFragments;
if (shouldPaintContent || shouldPaintOutline || isPaintingOverlayScrollbars) {
// Collect the fragments. This will compute the clip rectangles and paint offsets for each layer fragment.
m_renderLayer.collectFragments(layerFragments, localPaintingInfo.rootLayer, localPaintingInfo.paintDirtyRect,
(paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize,
shouldRespectOverflowClip(paintFlags, m_renderLayer.renderer()), &offsetFromRoot, localPaintingInfo.subPixelAccumulation);
if (shouldPaintContent)
shouldPaintContent = atLeastOneFragmentIntersectsDamageRect(layerFragments, localPaintingInfo, paintFlags, offsetFromRoot);
}
bool selectionOnly = localPaintingInfo.paintBehavior & PaintBehaviorSelectionOnly;
// If this layer's renderer is a child of the paintingRoot, we render unconditionally, which
// is done by passing a nil paintingRoot down to our renderer (as if no paintingRoot was ever set).
// Else, our renderer tree may or may not contain the painting root, so we pass that root along
// so it will be tested against as we descend through the renderers.
RenderObject* paintingRootForRenderer = 0;
if (localPaintingInfo.paintingRoot && !m_renderLayer.renderer()->isDescendantOf(localPaintingInfo.paintingRoot))
paintingRootForRenderer = localPaintingInfo.paintingRoot;
{ // Begin block for the lifetime of any filter clip.
OwnPtr<ClipRecorder> clipRecorder;
if (haveFilterEffect) {
ASSERT(m_renderLayer.filterInfo());
if (!rootRelativeBoundsComputed)
rootRelativeBounds = m_renderLayer.physicalBoundingBoxIncludingReflectionAndStackingChildren(paintingInfo.rootLayer, offsetFromRoot);
// Do transparency and clipping before starting filter processing.
if (haveTransparency) {
// If we have a filter and transparency, we have to eagerly start a transparency layer here, rather than risk a child layer lazily starts one after filter processing.
beginTransparencyLayers(context, localPaintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation, localPaintingInfo.paintBehavior);
}
// We'll handle clipping to the dirty rect before filter rasterization.
// Filter processing will automatically expand the clip rect and the offscreen to accommodate any filter outsets.
// FIXME: It is incorrect to just clip to the damageRect here once multiple fragments are involved.
ClipRect backgroundRect = layerFragments.isEmpty() ? ClipRect() : layerFragments[0].backgroundRect;
if (needsToClip(localPaintingInfo, backgroundRect)) {
clipRecorder = adoptPtr(new ClipRecorder(&m_renderLayer, context, DisplayItem::ClipLayerFilter, backgroundRect));
applyRoundedRectClips(localPaintingInfo, context, backgroundRect, paintFlags, *clipRecorder);
}
// Subsequent code should not clip to the dirty rect, since we've already
// done it above, and doing it later will defeat the outsets.
localPaintingInfo.clipToDirtyRect = false;
haveFilterEffect = m_renderLayer.filterRenderer()->beginFilterEffect(context, rootRelativeBounds);
if (!haveFilterEffect) {
// If the the filter failed to start, undo the clip immediately
clipRecorder.clear();
}
}
ASSERT(!(localPaintingInfo.paintBehavior & PaintBehaviorForceBlackText));
bool shouldPaintBackground = isPaintingCompositedBackground && shouldPaintContent && !selectionOnly;
bool shouldPaintNegZOrderList = (isPaintingScrollingContent && isPaintingOverflowContents) || (!isPaintingScrollingContent && isPaintingCompositedBackground);
bool shouldPaintOwnContents = isPaintingCompositedForeground && shouldPaintContent;
bool shouldPaintNormalFlowAndPosZOrderLists = isPaintingCompositedForeground;
bool shouldPaintOverlayScrollbars = isPaintingOverlayScrollbars;
PaintBehavior paintBehavior = PaintBehaviorNormal;
if (paintFlags & PaintLayerPaintingSkipRootBackground)
paintBehavior |= PaintBehaviorSkipRootBackground;
else if (paintFlags & PaintLayerPaintingRootBackgroundOnly)
paintBehavior |= PaintBehaviorRootBackgroundOnly;
if (shouldPaintBackground) {
paintBackgroundForFragments(layerFragments, context, paintingInfo.paintDirtyRect, haveTransparency,
localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags);
}
if (shouldPaintNegZOrderList)
paintChildren(NegativeZOrderChildren, context, paintingInfo, paintFlags);
if (shouldPaintOwnContents) {
paintForegroundForFragments(layerFragments, context, paintingInfo.paintDirtyRect, haveTransparency,
localPaintingInfo, paintBehavior, paintingRootForRenderer, selectionOnly, paintFlags);
}
if (shouldPaintOutline)
paintOutlineForFragments(layerFragments, context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags);
if (shouldPaintNormalFlowAndPosZOrderLists)
paintChildren(NormalFlowChildren | PositiveZOrderChildren, context, paintingInfo, paintFlags);
if (shouldPaintOverlayScrollbars)
paintOverflowControlsForFragments(layerFragments, context, localPaintingInfo, paintFlags);
if (haveFilterEffect) {
// Apply the correct clipping (ie. overflow: hidden).
// FIXME: It is incorrect to just clip to the damageRect here once multiple fragments are involved.
m_renderLayer.filterRenderer()->endFilterEffect(context);
}
} // Filter clip block
bool shouldPaintMask = (paintFlags & PaintLayerPaintingCompositingMaskPhase) && shouldPaintContent && m_renderLayer.renderer()->hasMask() && !selectionOnly;
bool shouldPaintClippingMask = (paintFlags & PaintLayerPaintingChildClippingMaskPhase) && shouldPaintContent && !selectionOnly;
if (shouldPaintMask)
paintMaskForFragments(layerFragments, context, localPaintingInfo, paintingRootForRenderer, paintFlags);
if (shouldPaintClippingMask) {
// Paint the border radius mask for the fragments.
paintChildClippingMaskForFragments(layerFragments, context, localPaintingInfo, paintingRootForRenderer, paintFlags);
}
// End our transparency layer
if ((haveTransparency || createTransparencyLayerForBlendMode) && m_renderLayer.usedTransparency()
&& !(m_renderLayer.reflectionInfo() && m_renderLayer.reflectionInfo()->isPaintingInsideReflection())) {
context->endLayer();
context->restore();
m_renderLayer.setUsedTransparency(false);
}
if (resourceClipper)
resourceClipper->postApplyStatefulResource(m_renderLayer.renderer(), context, clipperState);
}
static bool inContainingBlockChain(RenderLayer* startLayer, RenderLayer* endLayer)
{
if (startLayer == endLayer)
return true;
RenderView* view = startLayer->renderer()->view();
for (RenderBlock* currentBlock = startLayer->renderer()->containingBlock(); currentBlock && currentBlock != view; currentBlock = currentBlock->containingBlock()) {
if (currentBlock->layer() == endLayer)
return true;
}
return false;
}
bool LayerPainter::needsToClip(const LayerPaintingInfo& localPaintingInfo, const ClipRect& clipRect)
{
return clipRect.rect() != localPaintingInfo.paintDirtyRect || clipRect.hasRadius();
}
void LayerPainter::applyRoundedRectClips(const LayerPaintingInfo& localPaintingInfo, GraphicsContext* context, const ClipRect& clipRect,
PaintLayerFlags paintFlags, ClipRecorder& clipRecorder, BorderRadiusClippingRule rule)
{
if (!clipRect.hasRadius())
return;
// If the clip rect has been tainted by a border radius, then we have to walk up our layer chain applying the clips from
// any layers with overflow. The condition for being able to apply these clips is that the overflow object be in our
// containing block chain so we check that also.
for (RenderLayer* layer = rule == IncludeSelfForBorderRadius ? &m_renderLayer : m_renderLayer.parent(); layer; layer = layer->parent()) {
// Composited scrolling layers handle border-radius clip in the compositor via a mask layer. We do not
// want to apply a border-radius clip to the layer contents itself, because that would require re-rastering
// every frame to update the clip. We only want to make sure that the mask layer is properly clipped so
// that it can in turn clip the scrolled contents in the compositor.
if (layer->needsCompositedScrolling() && !(paintFlags & PaintLayerPaintingChildClippingMaskPhase))
break;
if (layer->renderer()->hasOverflowClip() && layer->renderer()->style()->hasBorderRadius() && inContainingBlockChain(&m_renderLayer, layer)) {
LayoutPoint delta;
layer->convertToLayerCoords(localPaintingInfo.rootLayer, delta);
clipRecorder.addRoundedRectClip(layer->renderer()->style()->getRoundedInnerBorderFor(LayoutRect(delta, layer->size())));
}
if (layer == localPaintingInfo.rootLayer)
break;
}
}
bool LayerPainter::atLeastOneFragmentIntersectsDamageRect(LayerFragments& fragments, const LayerPaintingInfo& localPaintingInfo, PaintLayerFlags localPaintFlags, const LayoutPoint& offsetFromRoot)
{
if (m_renderLayer.enclosingPaginationLayer())
return true; // The fragments created have already been found to intersect with the damage rect.
if (&m_renderLayer == localPaintingInfo.rootLayer && (localPaintFlags & PaintLayerPaintingOverflowContents))
return true;
for (LayerFragment& fragment: fragments) {
LayoutPoint newOffsetFromRoot = offsetFromRoot + fragment.paginationOffset;
// Note that this really only works reliably on the first fragment. If the layer has visible
// overflow and a subsequent fragment doesn't intersect with the border box of the layer
// (i.e. only contains an overflow portion of the layer), intersection will fail. The reason
// for this is that fragment.layerBounds is set to the border box, not the bounding box, of
// the layer.
if (m_renderLayer.intersectsDamageRect(fragment.layerBounds, fragment.backgroundRect.rect(), localPaintingInfo.rootLayer, &newOffsetFromRoot))
return true;
}
return false;
}
void LayerPainter::paintLayerByApplyingTransform(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags, const LayoutPoint& translationOffset)
{
// This involves subtracting out the position of the layer in our current coordinate space, but preserving
// the accumulated error for sub-pixel layout.
LayoutPoint delta;
m_renderLayer.convertToLayerCoords(paintingInfo.rootLayer, delta);
delta.moveBy(translationOffset);
TransformationMatrix transform(m_renderLayer.renderableTransform(paintingInfo.paintBehavior));
IntPoint roundedDelta = roundedIntPoint(delta);
transform.translateRight(roundedDelta.x(), roundedDelta.y());
LayoutSize adjustedSubPixelAccumulation = paintingInfo.subPixelAccumulation + (delta - roundedDelta);
// Apply the transform.
GraphicsContextStateSaver stateSaver(*context, false);
if (!transform.isIdentity()) {
stateSaver.save();
context->concatCTM(transform.toAffineTransform());
}
// Now do a paint with the root layer shifted to be us.
LayerPaintingInfo transformedPaintingInfo(&m_renderLayer, enclosingIntRect(transform.inverse().mapRect(paintingInfo.paintDirtyRect)), paintingInfo.paintBehavior,
adjustedSubPixelAccumulation, paintingInfo.paintingRoot);
paintLayerContentsAndReflection(context, transformedPaintingInfo, paintFlags);
}
void LayerPainter::paintChildren(unsigned childrenToVisit, GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
if (!m_renderLayer.hasSelfPaintingLayerDescendant())
return;
#if ENABLE(ASSERT)
LayerListMutationDetector mutationChecker(m_renderLayer.stackingNode());
#endif
RenderLayerStackingNodeIterator iterator(*m_renderLayer.stackingNode(), childrenToVisit);
while (RenderLayerStackingNode* child = iterator.next()) {
LayerPainter childPainter(*child->layer());
// If this RenderLayer should paint into its own backing or a grouped backing, that will be done via CompositedLayerMapping::paintContents()
// and CompositedLayerMapping::doPaintTask().
if (!childPainter.shouldPaintLayerInSoftwareMode(paintingInfo, paintFlags))
continue;
if (!child->layer()->isPaginated())
childPainter.paintLayer(context, paintingInfo, paintFlags);
else
childPainter.paintPaginatedChildLayer(child->layer(), context, paintingInfo, paintFlags);
}
}
// FIXME: inline this.
static bool paintForFixedRootBackground(const RenderLayer* layer, PaintLayerFlags paintFlags)
{
return layer->renderer()->isDocumentElement() && (paintFlags & PaintLayerPaintingRootBackgroundOnly);
}
bool LayerPainter::shouldPaintLayerInSoftwareMode(const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
DisableCompositingQueryAsserts disabler;
return m_renderLayer.compositingState() == NotComposited
|| (paintingInfo.paintBehavior & PaintBehaviorFlattenCompositingLayers)
|| ((paintFlags & PaintLayerPaintingReflection) && !m_renderLayer.has3DTransform())
|| paintForFixedRootBackground(&m_renderLayer, paintFlags);
}
static inline LayoutSize subPixelAccumulationIfNeeded(const LayoutSize& subPixelAccumulation, CompositingState compositingState)
{
// Only apply the sub-pixel accumulation if we don't paint into our own backing layer, otherwise the position
// of the renderer already includes any sub-pixel offset.
if (compositingState == PaintsIntoOwnBacking)
return LayoutSize();
return subPixelAccumulation;
}
void LayerPainter::paintOverflowControlsForFragments(const LayerFragments& layerFragments, GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags)
{
for (size_t i = 0; i < layerFragments.size(); ++i) {
const LayerFragment& fragment = layerFragments.at(i);
OwnPtr<ClipRecorder> clipRecorder;
if (needsToClip(localPaintingInfo, fragment.backgroundRect)) {
clipRecorder = adoptPtr(new ClipRecorder(&m_renderLayer, context, DisplayItem::ClipLayerOverflowControls, fragment.backgroundRect));
applyRoundedRectClips(localPaintingInfo, context, fragment.backgroundRect, paintFlags, *clipRecorder);
}
if (RenderLayerScrollableArea* scrollableArea = m_renderLayer.scrollableArea())
scrollableArea->paintOverflowControls(context, roundedIntPoint(toPoint(fragment.layerBounds.location() - m_renderLayer.renderBoxLocation() + subPixelAccumulationIfNeeded(localPaintingInfo.subPixelAccumulation, m_renderLayer.compositingState()))), pixelSnappedIntRect(fragment.backgroundRect.rect()), true);
}
}
static bool checkContainingBlockChainForPagination(RenderLayerModelObject* renderer, RenderBox* ancestorColumnsRenderer)
{
RenderView* view = renderer->view();
RenderLayerModelObject* prevBlock = renderer;
RenderBlock* containingBlock;
for (containingBlock = renderer->containingBlock();
containingBlock && containingBlock != view && containingBlock != ancestorColumnsRenderer;
containingBlock = containingBlock->containingBlock())
prevBlock = containingBlock;
// If the columns block wasn't in our containing block chain, then we aren't paginated by it.
if (containingBlock != ancestorColumnsRenderer)
return false;
// If the previous block is absolutely positioned, then we can't be paginated by the columns block.
if (prevBlock->isOutOfFlowPositioned())
return false;
// Otherwise we are paginated by the columns block.
return true;
}
void LayerPainter::paintPaginatedChildLayer(RenderLayer* childLayer, GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
// We need to do multiple passes, breaking up our child layer into strips.
Vector<RenderLayer*> columnLayers;
RenderLayerStackingNode* ancestorNode = m_renderLayer.stackingNode()->isNormalFlowOnly() ? m_renderLayer.parent()->stackingNode() : m_renderLayer.stackingNode()->ancestorStackingContextNode();
for (RenderLayer* curr = childLayer->parent(); curr; curr = curr->parent()) {
if (curr->renderer()->hasColumns() && checkContainingBlockChainForPagination(childLayer->renderer(), curr->renderBox()))
columnLayers.append(curr);
if (curr->stackingNode() == ancestorNode)
break;
}
// It is possible for paintLayer() to be called after the child layer ceases to be paginated but before
// updatePaginationRecusive() is called and resets the isPaginated() flag, see <rdar://problem/10098679>.
// If this is the case, just bail out, since the upcoming call to updatePaginationRecusive() will paint invalidate the layer.
// FIXME: Is this true anymore? This seems very suspicious.
if (!columnLayers.size())
return;
paintChildLayerIntoColumns(childLayer, context, paintingInfo, paintFlags, columnLayers, columnLayers.size() - 1);
}
void LayerPainter::paintChildLayerIntoColumns(RenderLayer* childLayer, GraphicsContext* context, const LayerPaintingInfo& paintingInfo,
PaintLayerFlags paintFlags, const Vector<RenderLayer*>& columnLayers, size_t colIndex)
{
RenderBlock* columnBlock = toRenderBlock(columnLayers[colIndex]->renderer());
ASSERT(columnBlock && columnBlock->hasColumns());
if (!columnBlock || !columnBlock->hasColumns())
return;
LayoutPoint layerOffset;
// FIXME: It looks suspicious to call convertToLayerCoords here
// as canUseConvertToLayerCoords is true for this layer.
columnBlock->layer()->convertToLayerCoords(paintingInfo.rootLayer, layerOffset);
bool isHorizontal = columnBlock->style()->isHorizontalWritingMode();
ColumnInfo* colInfo = columnBlock->columnInfo();
unsigned colCount = columnBlock->columnCount(colInfo);
LayoutUnit currLogicalTopOffset = 0;
for (unsigned i = 0; i < colCount; i++) {
// For each rect, we clip to the rect, and then we adjust our coords.
LayoutRect colRect = columnBlock->columnRectAt(colInfo, i);
columnBlock->flipForWritingMode(colRect);
LayoutUnit logicalLeftOffset = (isHorizontal ? colRect.x() : colRect.y()) - columnBlock->logicalLeftOffsetForContent();
LayoutSize offset;
if (isHorizontal) {
if (colInfo->progressionAxis() == ColumnInfo::InlineAxis)
offset = LayoutSize(logicalLeftOffset, currLogicalTopOffset);
else
offset = LayoutSize(0, colRect.y() + currLogicalTopOffset - columnBlock->borderTop() - columnBlock->paddingTop());
} else {
if (colInfo->progressionAxis() == ColumnInfo::InlineAxis)
offset = LayoutSize(currLogicalTopOffset, logicalLeftOffset);
else
offset = LayoutSize(colRect.x() + currLogicalTopOffset - columnBlock->borderLeft() - columnBlock->paddingLeft(), 0);
}
colRect.moveBy(layerOffset);
LayoutRect localDirtyRect(paintingInfo.paintDirtyRect);
localDirtyRect.intersect(colRect);
if (!localDirtyRect.isEmpty()) {
GraphicsContextStateSaver stateSaver(*context);
// Each strip pushes a clip, since column boxes are specified as being
// like overflow:hidden.
context->clip(enclosingIntRect(colRect));
if (!colIndex) {
// Apply a translation transform to change where the layer paints.
TransformationMatrix oldTransform;
bool oldHasTransform = childLayer->transform();
if (oldHasTransform)
oldTransform = *childLayer->transform();
TransformationMatrix newTransform(oldTransform);
newTransform.translateRight(roundToInt(offset.width()), roundToInt(offset.height()));
childLayer->setTransform(adoptPtr(new TransformationMatrix(newTransform)));
LayerPaintingInfo localPaintingInfo(paintingInfo);
localPaintingInfo.paintDirtyRect = localDirtyRect;
LayerPainter(*childLayer).paintLayer(context, localPaintingInfo, paintFlags);
if (oldHasTransform)
childLayer->setTransform(adoptPtr(new TransformationMatrix(oldTransform)));
else
childLayer->clearTransform();
} else {
// Adjust the transform such that the renderer's upper left corner will paint at (0,0) in user space.
// This involves subtracting out the position of the layer in our current coordinate space.
LayoutPoint childOffset;
columnLayers[colIndex - 1]->convertToLayerCoords(paintingInfo.rootLayer, childOffset);
TransformationMatrix transform;
transform.translateRight(roundToInt(childOffset.x() + offset.width()), roundToInt(childOffset.y() + offset.height()));
// Apply the transform.
context->concatCTM(transform.toAffineTransform());
// Now do a paint with the root layer shifted to be the next multicol block.
LayerPaintingInfo columnPaintingInfo(paintingInfo);
columnPaintingInfo.rootLayer = columnLayers[colIndex - 1];
columnPaintingInfo.paintDirtyRect = transform.inverse().mapRect(localDirtyRect);
paintChildLayerIntoColumns(childLayer, context, columnPaintingInfo, paintFlags, columnLayers, colIndex - 1);
}
}
// Move to the next position.
LayoutUnit blockDelta = isHorizontal ? colRect.height() : colRect.width();
if (columnBlock->style()->slowIsFlippedBlocksWritingMode())
currLogicalTopOffset += blockDelta;
else
currLogicalTopOffset -= blockDelta;
}
}
void LayerPainter::paintFragmentWithPhase(PaintPhase phase, const LayerFragment& fragment, GraphicsContext* context, const ClipRect& clipRect, const LayerPaintingInfo& paintingInfo, PaintBehavior paintBehavior, RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags, ClipState clipState)
{
OwnPtr<ClipRecorder> clipRecorder;
if (clipState != HasClipped && paintingInfo.clipToDirtyRect && needsToClip(paintingInfo, clipRect)) {
BorderRadiusClippingRule clippingRule = IncludeSelfForBorderRadius;
DisplayItem::Type clipType = DisplayItem::ClipLayerFragmentFloat;
switch (phase) {
case PaintPhaseFloat:
break;
case PaintPhaseForeground:
clipType = DisplayItem::ClipLayerFragmentForeground;
break;
case PaintPhaseChildOutlines:
clipType = DisplayItem::ClipLayerFragmentChildOutline;
break;
case PaintPhaseSelection:
clipType = DisplayItem::ClipLayerFragmentSelection;
break;
case PaintPhaseChildBlockBackgrounds:
clipType = DisplayItem::ClipLayerFragmentChildBlockBackgrounds;
break;
case PaintPhaseBlockBackground:
clipType = DisplayItem::ClipLayerBackground;
clippingRule = DoNotIncludeSelfForBorderRadius; // Background painting will handle clipping to self.
break;
case PaintPhaseSelfOutline:
clipType = DisplayItem::ClipLayerFragmentOutline;
clippingRule = DoNotIncludeSelfForBorderRadius;
break;
case PaintPhaseMask:
clipType = DisplayItem::ClipLayerFragmentMask;
clippingRule = DoNotIncludeSelfForBorderRadius; // Mask painting will handle clipping to self.
break;
case PaintPhaseClippingMask:
clipType = DisplayItem::ClipLayerFragmentClippingMask;
break;
default:
ASSERT_NOT_REACHED();
}
clipRecorder = adoptPtr(new ClipRecorder(&m_renderLayer, context, clipType, clipRect));
applyRoundedRectClips(paintingInfo, context, clipRect, paintFlags, *clipRecorder, clippingRule);
}
PaintInfo paintInfo(context, pixelSnappedIntRect(clipRect.rect()), phase, paintBehavior, paintingRootForRenderer, 0, paintingInfo.rootLayer->renderer());
m_renderLayer.renderer()->paint(paintInfo, toPoint(fragment.layerBounds.location() - m_renderLayer.renderBoxLocation() + subPixelAccumulationIfNeeded(paintingInfo.subPixelAccumulation, m_renderLayer.compositingState())));
}
void LayerPainter::paintBackgroundForFragments(const LayerFragments& layerFragments, GraphicsContext* context,
const LayoutRect& transparencyPaintDirtyRect, bool haveTransparency, const LayerPaintingInfo& localPaintingInfo, PaintBehavior paintBehavior,
RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags)
{
for (const auto& fragment: layerFragments) {
// Begin transparency layers lazily now that we know we have to paint something.
if (haveTransparency)
beginTransparencyLayers(context, localPaintingInfo.rootLayer, transparencyPaintDirtyRect, localPaintingInfo.subPixelAccumulation, localPaintingInfo.paintBehavior);
paintFragmentWithPhase(PaintPhaseBlockBackground, fragment, context, fragment.backgroundRect, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, HasNotClipped);
}
}
void LayerPainter::paintForegroundForFragments(const LayerFragments& layerFragments, GraphicsContext* context,
const LayoutRect& transparencyPaintDirtyRect, bool haveTransparency, const LayerPaintingInfo& localPaintingInfo, PaintBehavior paintBehavior,
RenderObject* paintingRootForRenderer, bool selectionOnly, PaintLayerFlags paintFlags)
{
// Begin transparency if we have something to paint.
if (haveTransparency) {
for (size_t i = 0; i < layerFragments.size(); ++i) {
const LayerFragment& fragment = layerFragments.at(i);
if (!fragment.foregroundRect.isEmpty()) {
beginTransparencyLayers(context, localPaintingInfo.rootLayer, transparencyPaintDirtyRect, localPaintingInfo.subPixelAccumulation, localPaintingInfo.paintBehavior);
break;
}
}
}
// Optimize clipping for the single fragment case.
bool shouldClip = localPaintingInfo.clipToDirtyRect && layerFragments.size() == 1 && !layerFragments[0].foregroundRect.isEmpty();
ClipState clipState = HasNotClipped;
OwnPtr<ClipRecorder> clipRecorder;
if (shouldClip && needsToClip(localPaintingInfo, layerFragments[0].foregroundRect)) {
clipRecorder = adoptPtr(new ClipRecorder(&m_renderLayer, context, DisplayItem::ClipLayerForeground, layerFragments[0].foregroundRect));
applyRoundedRectClips(localPaintingInfo, context, layerFragments[0].foregroundRect, paintFlags, *clipRecorder);
clipState = HasClipped;
}
// We have to loop through every fragment multiple times, since we have to issue paint invalidations in each specific phase in order for
// interleaving of the fragments to work properly.
paintForegroundForFragmentsWithPhase(selectionOnly ? PaintPhaseSelection : PaintPhaseChildBlockBackgrounds, layerFragments,
context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, clipState);
if (!selectionOnly) {
paintForegroundForFragmentsWithPhase(PaintPhaseFloat, layerFragments, context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, clipState);
paintForegroundForFragmentsWithPhase(PaintPhaseForeground, layerFragments, context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, clipState);
paintForegroundForFragmentsWithPhase(PaintPhaseChildOutlines, layerFragments, context, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, clipState);
}
}
void LayerPainter::paintForegroundForFragmentsWithPhase(PaintPhase phase, const LayerFragments& layerFragments, GraphicsContext* context,
const LayerPaintingInfo& localPaintingInfo, PaintBehavior paintBehavior, RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags, ClipState clipState)
{
for (const auto& fragment: layerFragments) {
if (!fragment.foregroundRect.isEmpty())
paintFragmentWithPhase(phase, fragment, context, fragment.foregroundRect, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, clipState);
}
}
void LayerPainter::paintOutlineForFragments(const LayerFragments& layerFragments, GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo,
PaintBehavior paintBehavior, RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags)
{
for (const auto& fragment: layerFragments) {
if (!fragment.outlineRect.isEmpty())
paintFragmentWithPhase(PaintPhaseSelfOutline, fragment, context, fragment.outlineRect, localPaintingInfo, paintBehavior, paintingRootForRenderer, paintFlags, HasNotClipped);
}
}
void LayerPainter::paintMaskForFragments(const LayerFragments& layerFragments, GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo,
RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags)
{
for (const auto& fragment: layerFragments)
paintFragmentWithPhase(PaintPhaseMask, fragment, context, fragment.backgroundRect, localPaintingInfo, PaintBehaviorNormal, paintingRootForRenderer, paintFlags, HasNotClipped);
}
void LayerPainter::paintChildClippingMaskForFragments(const LayerFragments& layerFragments, GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo,
RenderObject* paintingRootForRenderer, PaintLayerFlags paintFlags)
{
for (const auto& fragment: layerFragments)
paintFragmentWithPhase(PaintPhaseClippingMask, fragment, context, fragment.foregroundRect, localPaintingInfo, PaintBehaviorNormal, paintingRootForRenderer, paintFlags, HasNotClipped);
}
void LayerPainter::paintOverlayScrollbars(GraphicsContext* context, const LayoutRect& damageRect, PaintBehavior paintBehavior, RenderObject* paintingRoot)
{
if (!m_renderLayer.containsDirtyOverlayScrollbars())
return;
LayerPaintingInfo paintingInfo(&m_renderLayer, enclosingIntRect(damageRect), paintBehavior, LayoutSize(), paintingRoot);
paintLayer(context, paintingInfo, PaintLayerPaintingOverlayScrollbars);
m_renderLayer.setContainsDirtyOverlayScrollbars(false);
}
void LayerPainter::paintTransformedLayerIntoFragments(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
LayerFragments enclosingPaginationFragments;
LayoutPoint offsetOfPaginationLayerFromRoot;
LayoutRect transformedExtent = RenderLayer::transparencyClipBox(&m_renderLayer, m_renderLayer.enclosingPaginationLayer(), RenderLayer::PaintingTransparencyClipBox, RenderLayer::RootOfTransparencyClipBox, paintingInfo.subPixelAccumulation, paintingInfo.paintBehavior);
m_renderLayer.enclosingPaginationLayer()->collectFragments(enclosingPaginationFragments, paintingInfo.rootLayer, paintingInfo.paintDirtyRect,
(paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize,
shouldRespectOverflowClip(paintFlags, m_renderLayer.renderer()), &offsetOfPaginationLayerFromRoot, paintingInfo.subPixelAccumulation, &transformedExtent);
for (size_t i = 0; i < enclosingPaginationFragments.size(); ++i) {
const LayerFragment& fragment = enclosingPaginationFragments.at(i);
// Apply the page/column clip for this fragment, as well as any clips established by layers in between us and
// the enclosing pagination layer.
LayoutRect clipRect = fragment.backgroundRect.rect();
// Now compute the clips within a given fragment
if (m_renderLayer.parent() != m_renderLayer.enclosingPaginationLayer()) {
m_renderLayer.enclosingPaginationLayer()->convertToLayerCoords(paintingInfo.rootLayer, offsetOfPaginationLayerFromRoot);
ClipRectsContext clipRectsContext(m_renderLayer.enclosingPaginationLayer(), (paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize);
if (shouldRespectOverflowClip(paintFlags, m_renderLayer.renderer()) == IgnoreOverflowClip)
clipRectsContext.setIgnoreOverflowClip();
LayoutRect parentClipRect = m_renderLayer.clipper().backgroundClipRect(clipRectsContext).rect();
parentClipRect.moveBy(fragment.paginationOffset + offsetOfPaginationLayerFromRoot);
clipRect.intersect(parentClipRect);
}
OwnPtr<ClipRecorder> clipRecorder;
if (needsToClip(paintingInfo, clipRect)) {
clipRecorder = adoptPtr(new ClipRecorder(m_renderLayer.parent(), context, DisplayItem::ClipLayerFragmentParent, clipRect));
// FIXME: why should we have to deal with rounded rect clips here at all?
LayerPainter(*m_renderLayer.parent()).applyRoundedRectClips(paintingInfo, context, clipRect, paintFlags, *clipRecorder);
}
paintLayerByApplyingTransform(context, paintingInfo, paintFlags, fragment.paginationOffset);
}
}
} // namespace blink