| // 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 "core/paint/PaintLayerPainter.h" |
| |
| #include "core/frame/LocalFrame.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/paint/ClipPathClipper.h" |
| #include "core/paint/FilterPainter.h" |
| #include "core/paint/LayerClipRecorder.h" |
| #include "core/paint/ObjectPaintProperties.h" |
| #include "core/paint/PaintInfo.h" |
| #include "core/paint/PaintLayer.h" |
| #include "core/paint/ScrollRecorder.h" |
| #include "core/paint/ScrollableAreaPainter.h" |
| #include "core/paint/Transform3DRecorder.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/geometry/FloatPoint3D.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| #include "platform/graphics/paint/CompositingRecorder.h" |
| #include "platform/graphics/paint/DisplayItemCacheSkipper.h" |
| #include "platform/graphics/paint/PaintChunkProperties.h" |
| #include "platform/graphics/paint/ScopedPaintChunkProperties.h" |
| #include "platform/graphics/paint/SubsequenceRecorder.h" |
| #include "platform/graphics/paint/Transform3DDisplayItem.h" |
| #include "wtf/Optional.h" |
| |
| namespace blink { |
| |
| static inline bool shouldSuppressPaintingLayer(const PaintLayer& layer) { |
| // Avoid painting descendants of the root layer when stylesheets haven't |
| // loaded. This avoids some FOUC. It's ok not to draw, because later on, when |
| // all the stylesheets do load, Document::styleResolverMayHaveChanged() will |
| // invalidate all painted output via a call to |
| // LayoutView::invalidatePaintForViewAndCompositedLayers(). We also avoid |
| // caching subsequences in this mode; see shouldCreateSubsequence(). |
| if (layer.layoutObject()->document().didLayoutWithPendingStylesheets() && |
| !layer.isRootLayer() && !layer.layoutObject()->isDocumentElement()) |
| return true; |
| |
| return false; |
| } |
| |
| void PaintLayerPainter::paint(GraphicsContext& context, |
| const LayoutRect& damageRect, |
| const GlobalPaintFlags globalPaintFlags, |
| PaintLayerFlags paintFlags) { |
| PaintLayerPaintingInfo paintingInfo(&m_paintLayer, |
| LayoutRect(enclosingIntRect(damageRect)), |
| globalPaintFlags, LayoutSize()); |
| if (shouldPaintLayerInSoftwareMode(globalPaintFlags, paintFlags)) |
| paintLayer(context, paintingInfo, paintFlags); |
| } |
| |
| static ShouldRespectOverflowClipType shouldRespectOverflowClip( |
| PaintLayerFlags paintFlags, |
| const LayoutObject* layoutObject) { |
| return (paintFlags & PaintLayerPaintingOverflowContents || |
| (paintFlags & PaintLayerPaintingChildClippingMaskPhase && |
| layoutObject->hasClipPath())) |
| ? IgnoreOverflowClip |
| : RespectOverflowClip; |
| } |
| |
| bool PaintLayerPainter::paintedOutputInvisible( |
| const PaintLayerPaintingInfo& paintingInfo) { |
| if (m_paintLayer.layoutObject()->hasBackdropFilter()) |
| return false; |
| |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && |
| m_paintLayer.layoutObject()->styleRef().opacity()) |
| return false; |
| |
| // 0.0004f < 1/2048. With 10-bit color channels (only available on the |
| // newest Macs; otherwise it's 8-bit), we see that an alpha of 1/2048 or |
| // less leads to a color output of less than 0.5 in all channels, hence |
| // not visible. |
| static const float kMinimumVisibleOpacity = 0.0004f; |
| if (m_paintLayer.paintsWithTransparency(paintingInfo.getGlobalPaintFlags())) { |
| if (m_paintLayer.layoutObject()->styleRef().opacity() < |
| kMinimumVisibleOpacity) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| PaintResult PaintLayerPainter::paintLayer( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags) { |
| // https://code.google.com/p/chromium/issues/detail?id=343772 |
| DisableCompositingQueryAsserts disabler; |
| |
| if (m_paintLayer.compositingState() != NotComposited) { |
| if (paintingInfo.getGlobalPaintFlags() & |
| GlobalPaintFlattenCompositingLayers) { |
| // FIXME: ok, but what about GlobalPaintFlattenCompositingLayers? That's |
| // for printing and drag-image. |
| // FIXME: why isn't the code here global, as opposed to being set on each |
| // paintLayer() call? |
| paintFlags |= PaintLayerUncachedClipRects; |
| } |
| } |
| |
| // Non self-painting layers without self-painting descendants don't need to be |
| // painted as their layoutObject() should properly paint itself. |
| if (!m_paintLayer.isSelfPaintingLayer() && |
| !m_paintLayer.hasSelfPaintingLayerDescendant()) |
| return FullyPainted; |
| |
| if (shouldSuppressPaintingLayer(m_paintLayer)) |
| return FullyPainted; |
| |
| if (m_paintLayer.layoutObject()->view()->frame() && |
| m_paintLayer.layoutObject()->view()->frame()->shouldThrottleRendering()) |
| return FullyPainted; |
| |
| // If this layer is totally invisible then there is nothing to paint. |
| if (paintedOutputInvisible(paintingInfo)) |
| return FullyPainted; |
| |
| if (m_paintLayer.paintsWithTransparency(paintingInfo.getGlobalPaintFlags())) |
| paintFlags |= PaintLayerHaveTransparency; |
| |
| if (m_paintLayer.paintsWithTransform(paintingInfo.getGlobalPaintFlags()) && |
| !(paintFlags & PaintLayerAppliedTransform)) |
| return paintLayerWithTransform(context, paintingInfo, paintFlags); |
| |
| return paintLayerContentsCompositingAllPhases(context, paintingInfo, |
| paintFlags); |
| } |
| |
| PaintResult PaintLayerPainter::paintLayerContentsCompositingAllPhases( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags, |
| FragmentPolicy fragmentPolicy) { |
| DCHECK(m_paintLayer.isSelfPaintingLayer() || |
| m_paintLayer.hasSelfPaintingLayerDescendant()); |
| |
| PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform); |
| localPaintFlags |= PaintLayerPaintingCompositingAllPhases; |
| return paintLayerContents(context, paintingInfo, localPaintFlags, |
| fragmentPolicy); |
| } |
| |
| static bool shouldCreateSubsequence(const PaintLayer& paintLayer, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags) { |
| // Caching is not needed during printing. |
| if (context.printing()) |
| return false; |
| |
| // Don't create subsequence for a composited layer because if it can be |
| // cached, we can skip the whole painting in GraphicsLayer::paint() with |
| // CachedDisplayItemList. This also avoids conflict of |
| // PaintLayer::previousXXX() when paintLayer is composited scrolling and is |
| // painted twice for GraphicsLayers of container and scrolling contents. |
| if (paintLayer.compositingState() == PaintsIntoOwnBacking) |
| return false; |
| |
| // Don't create subsequence during special painting to avoid cache conflict |
| // with normal painting. |
| if (paintingInfo.getGlobalPaintFlags() & GlobalPaintFlattenCompositingLayers) |
| return false; |
| if (paintFlags & |
| (PaintLayerPaintingRootBackgroundOnly | |
| PaintLayerPaintingOverlayScrollbars | PaintLayerUncachedClipRects)) |
| return false; |
| |
| // Create subsequence for only stacking contexts whose painting are atomic. |
| // SVG is also painted atomically. |
| if (!paintLayer.stackingNode()->isStackingContext() && |
| !paintLayer.layoutObject()->isSVGRoot()) |
| return false; |
| |
| // The layer doesn't have children. Subsequence caching is not worth because |
| // normally the actual painting will be cheap. |
| // SVG is also painted atomically. |
| if (!PaintLayerStackingNodeIterator(*paintLayer.stackingNode(), AllChildren) |
| .next() && |
| !paintLayer.layoutObject()->isSVGRoot()) |
| return false; |
| |
| // When in FOUC-avoidance mode, don't cache any subsequences, to avoid having |
| // to invalidate all of them when leaving this mode. There is an early-out in |
| // BlockPainter::paintContents that may result in nothing getting painted in |
| // this mode, in addition to early-out logic in PaintLayerPainter. |
| if (paintLayer.layoutObject()->document().didLayoutWithPendingStylesheets()) |
| return false; |
| |
| return true; |
| } |
| |
| static bool shouldRepaintSubsequence( |
| PaintLayer& paintLayer, |
| const PaintLayerPaintingInfo& paintingInfo, |
| ShouldRespectOverflowClipType respectOverflowClip, |
| const LayoutSize& subpixelAccumulation, |
| bool& shouldClearEmptyPaintPhaseFlags) { |
| bool needsRepaint = false; |
| |
| // We should set shouldResetEmptyPaintPhaseFlags if some previously unpainted |
| // objects may begin to be painted, causing a previously empty paint phase to |
| // become non-empty. |
| |
| // Repaint subsequence if the layer is marked for needing repaint. |
| // We don't set needsResetEmptyPaintPhase here, but clear the empty paint |
| // phase flags in PaintLayer::setNeedsPaintPhaseXXX(), to ensure that we won't |
| // clear previousPaintPhaseXXXEmpty flags when unrelated things changed which |
| // won't cause the paint phases to become non-empty. |
| if (paintLayer.needsRepaint()) |
| needsRepaint = true; |
| |
| // Repaint if layer's clip changes. |
| // TODO(chrishtr): implement detecting clipping changes in SPv2. |
| // crbug.com/645667 |
| if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| ClipRects& clipRects = paintLayer.clipper().paintingClipRects( |
| paintingInfo.rootLayer, respectOverflowClip, subpixelAccumulation); |
| ClipRects* previousClipRects = paintLayer.previousPaintingClipRects(); |
| if (&clipRects != previousClipRects && |
| (!previousClipRects || clipRects != *previousClipRects)) { |
| needsRepaint = true; |
| shouldClearEmptyPaintPhaseFlags = true; |
| } |
| paintLayer.setPreviousPaintingClipRects(clipRects); |
| } |
| |
| // Repaint if previously the layer might be clipped by paintDirtyRect and |
| // paintDirtyRect changes. |
| if (paintLayer.previousPaintResult() == MayBeClippedByPaintDirtyRect && |
| paintLayer.previousPaintDirtyRect() != paintingInfo.paintDirtyRect) { |
| needsRepaint = true; |
| shouldClearEmptyPaintPhaseFlags = true; |
| } |
| paintLayer.setPreviousPaintDirtyRect(paintingInfo.paintDirtyRect); |
| |
| // Repaint if scroll offset accumulation changes. |
| if (paintingInfo.scrollOffsetAccumulation != |
| paintLayer.previousScrollOffsetAccumulationForPainting()) { |
| needsRepaint = true; |
| shouldClearEmptyPaintPhaseFlags = true; |
| } |
| paintLayer.setPreviousScrollOffsetAccumulationForPainting( |
| paintingInfo.scrollOffsetAccumulation); |
| |
| return needsRepaint; |
| } |
| |
| PaintResult PaintLayerPainter::paintLayerContents( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfoArg, |
| PaintLayerFlags paintFlags, |
| FragmentPolicy fragmentPolicy) { |
| Optional<ScopedPaintChunkProperties> scopedPaintChunkProperties; |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && |
| RuntimeEnabledFeatures::rootLayerScrollingEnabled() && |
| m_paintLayer.layoutObject() && |
| m_paintLayer.layoutObject()->isLayoutView()) { |
| const auto* objectPaintProperties = |
| m_paintLayer.layoutObject()->paintProperties(); |
| DCHECK(objectPaintProperties && |
| objectPaintProperties->localBorderBoxProperties()); |
| PaintChunkProperties properties( |
| context.getPaintController().currentPaintChunkProperties()); |
| auto& localBorderBoxProperties = |
| *objectPaintProperties->localBorderBoxProperties(); |
| properties.transform = |
| localBorderBoxProperties.propertyTreeState.transform(); |
| properties.scroll = localBorderBoxProperties.propertyTreeState.scroll(); |
| properties.clip = localBorderBoxProperties.propertyTreeState.clip(); |
| properties.effect = localBorderBoxProperties.propertyTreeState.effect(); |
| properties.backfaceHidden = |
| m_paintLayer.layoutObject()->hasHiddenBackface(); |
| scopedPaintChunkProperties.emplace(context.getPaintController(), |
| m_paintLayer, properties); |
| } |
| |
| DCHECK(m_paintLayer.isSelfPaintingLayer() || |
| m_paintLayer.hasSelfPaintingLayerDescendant()); |
| DCHECK(!(paintFlags & PaintLayerAppliedTransform)); |
| |
| bool isSelfPaintingLayer = m_paintLayer.isSelfPaintingLayer(); |
| bool isPaintingOverlayScrollbars = |
| paintFlags & PaintLayerPaintingOverlayScrollbars; |
| bool isPaintingScrollingContent = |
| paintFlags & PaintLayerPaintingCompositingScrollingPhase; |
| bool isPaintingCompositedForeground = |
| paintFlags & PaintLayerPaintingCompositingForegroundPhase; |
| bool isPaintingCompositedBackground = |
| paintFlags & PaintLayerPaintingCompositingBackgroundPhase; |
| bool isPaintingCompositedDecoration = |
| paintFlags & PaintLayerPaintingCompositingDecorationPhase; |
| bool isPaintingOverflowContents = |
| paintFlags & PaintLayerPaintingOverflowContents; |
| // Outline always needs to be painted even if we have no visible content. |
| // It is painted as part of the decoration phase which paints content that |
| // is not scrolled and should be above scrolled content. |
| bool shouldPaintSelfOutline = |
| isSelfPaintingLayer && !isPaintingOverlayScrollbars && |
| (isPaintingCompositedDecoration || !isPaintingScrollingContent) && |
| m_paintLayer.layoutObject()->styleRef().hasOutline(); |
| |
| PaintResult result = FullyPainted; |
| |
| if (paintFlags & PaintLayerPaintingRootBackgroundOnly && |
| !m_paintLayer.layoutObject()->isLayoutView()) |
| return result; |
| |
| if (m_paintLayer.layoutObject()->view()->frame() && |
| m_paintLayer.layoutObject()->view()->frame()->shouldThrottleRendering()) |
| return result; |
| |
| // Ensure our lists are up to date. |
| m_paintLayer.stackingNode()->updateLayerListsIfNeeded(); |
| |
| LayoutSize subpixelAccumulation = |
| m_paintLayer.compositingState() == PaintsIntoOwnBacking |
| ? m_paintLayer.subpixelAccumulation() |
| : paintingInfoArg.subPixelAccumulation; |
| ShouldRespectOverflowClipType respectOverflowClip = |
| shouldRespectOverflowClip(paintFlags, m_paintLayer.layoutObject()); |
| |
| Optional<SubsequenceRecorder> subsequenceRecorder; |
| bool shouldClearEmptyPaintPhaseFlags = false; |
| if (shouldCreateSubsequence(m_paintLayer, context, paintingInfoArg, |
| paintFlags)) { |
| if (!shouldRepaintSubsequence(m_paintLayer, paintingInfoArg, |
| respectOverflowClip, subpixelAccumulation, |
| shouldClearEmptyPaintPhaseFlags) && |
| SubsequenceRecorder::useCachedSubsequenceIfPossible(context, |
| m_paintLayer)) |
| return result; |
| subsequenceRecorder.emplace(context, m_paintLayer); |
| } else { |
| shouldClearEmptyPaintPhaseFlags = true; |
| } |
| |
| if (shouldClearEmptyPaintPhaseFlags) { |
| m_paintLayer.setPreviousPaintPhaseDescendantOutlinesEmpty(false); |
| m_paintLayer.setPreviousPaintPhaseFloatEmpty(false); |
| m_paintLayer.setPreviousPaintPhaseDescendantBlockBackgroundsEmpty(false); |
| } |
| |
| PaintLayerPaintingInfo paintingInfo = paintingInfoArg; |
| |
| LayoutPoint offsetFromRoot; |
| m_paintLayer.convertToLayerCoords(paintingInfo.rootLayer, offsetFromRoot); |
| offsetFromRoot.move(subpixelAccumulation); |
| |
| LayoutRect bounds = m_paintLayer.physicalBoundingBox(offsetFromRoot); |
| if (!paintingInfo.paintDirtyRect.contains(bounds)) |
| result = MayBeClippedByPaintDirtyRect; |
| |
| if (paintingInfo.ancestorHasClipPathClipping && |
| m_paintLayer.layoutObject()->isPositioned()) |
| UseCounter::count(m_paintLayer.layoutObject()->document(), |
| UseCounter::ClipPathOfPositionedElement); |
| |
| // These helpers output clip and compositing operations using a RAII pattern. |
| // Stack-allocated-varibles are destructed in the reverse order of |
| // construction, so they are nested properly. |
| Optional<ClipPathClipper> clipPathClipper; |
| // 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_paintLayer.layoutObject()->hasClipPath() && |
| (!m_paintLayer.needsCompositedScrolling() || |
| (paintFlags & (PaintLayerPaintingChildClippingMaskPhase | |
| PaintLayerPaintingAncestorClippingMaskPhase)))) { |
| paintingInfo.ancestorHasClipPathClipping = true; |
| |
| LayoutRect referenceBox(m_paintLayer.boxForClipPath()); |
| // Note that this isn't going to work correctly if crossing a column |
| // boundary. The reference box should be determined per-fragment, and hence |
| // this ought to be performed after fragmentation. |
| if (m_paintLayer.enclosingPaginationLayer()) |
| m_paintLayer.convertFromFlowThreadToVisualBoundingBoxInAncestor( |
| paintingInfo.rootLayer, referenceBox); |
| else |
| referenceBox.moveBy(offsetFromRoot); |
| clipPathClipper.emplace( |
| context, *m_paintLayer.layoutObject()->styleRef().clipPath(), |
| *m_paintLayer.layoutObject(), FloatRect(referenceBox), |
| FloatPoint(referenceBox.location())); |
| } |
| |
| Optional<CompositingRecorder> compositingRecorder; |
| // Blending operations must be performed only with the nearest ancestor |
| // stacking context. Note that there is no need to composite if we're |
| // painting the root. |
| // FIXME: this should be unified further into |
| // PaintLayer::paintsWithTransparency(). |
| bool shouldCompositeForBlendMode = |
| (!m_paintLayer.layoutObject()->isDocumentElement() || |
| m_paintLayer.layoutObject()->isSVGRoot()) && |
| m_paintLayer.stackingNode()->isStackingContext() && |
| m_paintLayer.hasNonIsolatedDescendantWithBlendMode(); |
| if (shouldCompositeForBlendMode || |
| m_paintLayer.paintsWithTransparency(paintingInfo.getGlobalPaintFlags())) { |
| FloatRect compositingBounds = FloatRect(m_paintLayer.paintingExtent( |
| paintingInfo.rootLayer, paintingInfo.subPixelAccumulation, |
| paintingInfo.getGlobalPaintFlags())); |
| compositingRecorder.emplace( |
| context, *m_paintLayer.layoutObject(), |
| WebCoreCompositeToSkiaComposite( |
| CompositeSourceOver, |
| m_paintLayer.layoutObject()->style()->blendMode()), |
| m_paintLayer.layoutObject()->opacity(), &compositingBounds); |
| } |
| |
| PaintLayerPaintingInfo localPaintingInfo(paintingInfo); |
| localPaintingInfo.subPixelAccumulation = subpixelAccumulation; |
| |
| bool shouldPaintContent = m_paintLayer.hasVisibleContent() && |
| isSelfPaintingLayer && !isPaintingOverlayScrollbars; |
| |
| PaintLayerFragments layerFragments; |
| if (shouldPaintContent || shouldPaintSelfOutline || |
| isPaintingOverlayScrollbars) { |
| // Collect the fragments. This will compute the clip rectangles and paint |
| // offsets for each layer fragment. |
| ClipRectsCacheSlot cacheSlot = (paintFlags & PaintLayerUncachedClipRects) |
| ? UncachedClipRects |
| : PaintingClipRects; |
| LayoutPoint offsetToClipper; |
| PaintLayer* paintLayerForFragments = &m_paintLayer; |
| if (paintFlags & PaintLayerPaintingAncestorClippingMaskPhase) { |
| // Compute fragments and their clips with respect to the clipping |
| // container. The paint rect is in this layer's space, so convert it |
| // to the clipper's layer's space. The rootLayer is also changed to |
| // the clipper's layer to simplify coordinate system adjustments. |
| // The change to rootLayer must persist to correctly record the clips. |
| paintLayerForFragments = |
| m_paintLayer.clippingContainer()->enclosingLayer(); |
| localPaintingInfo.rootLayer = paintLayerForFragments; |
| m_paintLayer.convertToLayerCoords(localPaintingInfo.rootLayer, |
| offsetToClipper); |
| localPaintingInfo.paintDirtyRect.moveBy(offsetToClipper); |
| } |
| |
| // TODO(trchen): We haven't decided how to handle visual fragmentation with |
| // SPv2. Related thread |
| // https://groups.google.com/a/chromium.org/forum/#!topic/graphics-dev/81XuWFf-mxM |
| if (fragmentPolicy == ForceSingleFragment || |
| RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| paintLayerForFragments->appendSingleFragmentIgnoringPagination( |
| layerFragments, localPaintingInfo.rootLayer, |
| localPaintingInfo.paintDirtyRect, cacheSlot, |
| IgnoreOverlayScrollbarSize, respectOverflowClip, &offsetFromRoot, |
| localPaintingInfo.subPixelAccumulation); |
| } else { |
| paintLayerForFragments->collectFragments( |
| layerFragments, localPaintingInfo.rootLayer, |
| localPaintingInfo.paintDirtyRect, cacheSlot, |
| IgnoreOverlayScrollbarSize, respectOverflowClip, &offsetFromRoot, |
| localPaintingInfo.subPixelAccumulation); |
| } |
| |
| if (paintFlags & PaintLayerPaintingAncestorClippingMaskPhase) { |
| // Fragment offsets have been computed in the clipping container's |
| // layer's coordinate system, but for the rest of painting we need |
| // them in the layer coordinate. So move them and the foreground rect |
| // that is also in the clipper's space. |
| LayoutSize negativeOffset(-offsetToClipper.x(), -offsetToClipper.y()); |
| for (auto& fragment : layerFragments) { |
| fragment.foregroundRect.move(negativeOffset); |
| fragment.paginationOffset.move(negativeOffset); |
| } |
| } |
| |
| if (shouldPaintContent) { |
| // TODO(wangxianzhu): This is for old slow scrolling. Implement similar |
| // optimization for slimming paint v2. |
| shouldPaintContent = atLeastOneFragmentIntersectsDamageRect( |
| layerFragments, localPaintingInfo, paintFlags, offsetFromRoot); |
| if (!shouldPaintContent) |
| result = MayBeClippedByPaintDirtyRect; |
| } |
| } |
| |
| bool selectionOnly = |
| localPaintingInfo.getGlobalPaintFlags() & GlobalPaintSelectionOnly; |
| |
| { // Begin block for the lifetime of any filter. |
| FilterPainter filterPainter(m_paintLayer, context, offsetFromRoot, |
| layerFragments.isEmpty() |
| ? ClipRect() |
| : layerFragments[0].backgroundRect, |
| localPaintingInfo, paintFlags); |
| |
| Optional<ScopedPaintChunkProperties> contentScopedPaintChunkProperties; |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && |
| !scopedPaintChunkProperties.has_value()) { |
| // If layoutObject() is a LayoutView and root layer scrolling is enabled, |
| // the LayoutView's paint properties will already have been applied at |
| // the top of this method, in scopedPaintChunkProperties. |
| DCHECK(!(RuntimeEnabledFeatures::rootLayerScrollingEnabled() && |
| m_paintLayer.layoutObject() && |
| m_paintLayer.layoutObject()->isLayoutView())); |
| const auto* objectPaintProperties = |
| m_paintLayer.layoutObject()->paintProperties(); |
| DCHECK(objectPaintProperties && |
| objectPaintProperties->localBorderBoxProperties()); |
| PaintChunkProperties properties( |
| context.getPaintController().currentPaintChunkProperties()); |
| auto& localBorderBoxProperties = |
| *objectPaintProperties->localBorderBoxProperties(); |
| properties.transform = |
| localBorderBoxProperties.propertyTreeState.transform(); |
| properties.scroll = localBorderBoxProperties.propertyTreeState.scroll(); |
| properties.clip = localBorderBoxProperties.propertyTreeState.clip(); |
| properties.effect = localBorderBoxProperties.propertyTreeState.effect(); |
| properties.backfaceHidden = |
| m_paintLayer.layoutObject()->hasHiddenBackface(); |
| contentScopedPaintChunkProperties.emplace(context.getPaintController(), |
| m_paintLayer, properties); |
| } |
| |
| bool isPaintingRootLayer = (&m_paintLayer) == paintingInfo.rootLayer; |
| bool shouldPaintBackground = |
| shouldPaintContent && !selectionOnly && |
| (isPaintingCompositedBackground || |
| (isPaintingRootLayer && |
| !(paintFlags & PaintLayerPaintingSkipRootBackground))); |
| bool shouldPaintNegZOrderList = |
| (isPaintingScrollingContent && isPaintingOverflowContents) || |
| (!isPaintingScrollingContent && isPaintingCompositedBackground); |
| bool shouldPaintOwnContents = |
| isPaintingCompositedForeground && shouldPaintContent; |
| bool shouldPaintNormalFlowAndPosZOrderLists = |
| isPaintingCompositedForeground; |
| bool shouldPaintOverlayScrollbars = isPaintingOverlayScrollbars; |
| |
| if (shouldPaintBackground) { |
| paintBackgroundForFragments(layerFragments, context, |
| paintingInfo.paintDirtyRect, |
| localPaintingInfo, paintFlags); |
| } |
| |
| if (shouldPaintNegZOrderList) { |
| if (paintChildren(NegativeZOrderChildren, context, paintingInfo, |
| paintFlags) == MayBeClippedByPaintDirtyRect) |
| result = MayBeClippedByPaintDirtyRect; |
| } |
| |
| if (shouldPaintOwnContents) { |
| paintForegroundForFragments(layerFragments, context, |
| paintingInfo.paintDirtyRect, |
| localPaintingInfo, selectionOnly, paintFlags); |
| } |
| |
| if (shouldPaintSelfOutline) |
| paintSelfOutlineForFragments(layerFragments, context, localPaintingInfo, |
| paintFlags); |
| |
| if (shouldPaintNormalFlowAndPosZOrderLists) { |
| if (paintChildren(NormalFlowChildren | PositiveZOrderChildren, context, |
| paintingInfo, |
| paintFlags) == MayBeClippedByPaintDirtyRect) |
| result = MayBeClippedByPaintDirtyRect; |
| } |
| |
| if (shouldPaintOverlayScrollbars) |
| paintOverflowControlsForFragments(layerFragments, context, |
| localPaintingInfo, paintFlags); |
| } // FilterPainter block |
| |
| bool shouldPaintMask = |
| (paintFlags & PaintLayerPaintingCompositingMaskPhase) && |
| shouldPaintContent && m_paintLayer.layoutObject()->hasMask() && |
| !selectionOnly; |
| bool shouldPaintClippingMask = |
| (paintFlags & (PaintLayerPaintingChildClippingMaskPhase | |
| PaintLayerPaintingAncestorClippingMaskPhase)) && |
| shouldPaintContent && !selectionOnly; |
| |
| if (shouldPaintMask) |
| paintMaskForFragments(layerFragments, context, localPaintingInfo, |
| paintFlags); |
| if (shouldPaintClippingMask) { |
| // Paint the border radius mask for the fragments. |
| paintChildClippingMaskForFragments(layerFragments, context, |
| localPaintingInfo, paintFlags); |
| } |
| |
| if (subsequenceRecorder) |
| m_paintLayer.setPreviousPaintResult(result); |
| return result; |
| } |
| |
| bool PaintLayerPainter::needsToClip( |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| const ClipRect& clipRect) { |
| // Clipping will be applied by property nodes directly for SPv2. |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| return false; |
| |
| return clipRect.rect() != localPaintingInfo.paintDirtyRect || |
| clipRect.hasRadius(); |
| } |
| |
| bool PaintLayerPainter::atLeastOneFragmentIntersectsDamageRect( |
| PaintLayerFragments& fragments, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags localPaintFlags, |
| const LayoutPoint& offsetFromRoot) { |
| if (m_paintLayer.enclosingPaginationLayer()) |
| return true; // The fragments created have already been found to intersect |
| // with the damage rect. |
| |
| if (&m_paintLayer == localPaintingInfo.rootLayer && |
| (localPaintFlags & PaintLayerPaintingOverflowContents)) |
| return true; |
| |
| for (PaintLayerFragment& 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_paintLayer.intersectsDamageRect(fragment.layerBounds, |
| fragment.backgroundRect.rect(), |
| newOffsetFromRoot)) |
| return true; |
| } |
| return false; |
| } |
| |
| PaintResult PaintLayerPainter::paintLayerWithTransform( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags) { |
| TransformationMatrix layerTransform = |
| m_paintLayer.renderableTransform(paintingInfo.getGlobalPaintFlags()); |
| // If the transform can't be inverted, then don't paint anything. |
| if (!layerTransform.isInvertible()) |
| return FullyPainted; |
| |
| // FIXME: We should make sure that we don't walk past paintingInfo.rootLayer |
| // here. m_paintLayer may be the "root", and then we should avoid looking at |
| // its parent. |
| PaintLayer* parentLayer = m_paintLayer.parent(); |
| |
| LayoutObject* object = m_paintLayer.layoutObject(); |
| LayoutView* view = object->view(); |
| bool isFixedPosObjectInPagedMedia = |
| object->style()->position() == FixedPosition && |
| object->container() == view && view->pageLogicalHeight(); |
| PaintLayer* paginationLayer = m_paintLayer.enclosingPaginationLayer(); |
| PaintLayerFragments fragments; |
| // TODO(crbug.com/619094): Figure out the correct behaviour for fixed position |
| // objects in paged media with vertical writing modes. |
| if (isFixedPosObjectInPagedMedia && view->isHorizontalWritingMode()) { |
| // "For paged media, boxes with fixed positions are repeated on every page." |
| // https://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#fixed-positioning |
| unsigned pages = |
| ceilf(view->documentRect().height() / view->pageLogicalHeight()); |
| LayoutPoint paginationOffset; |
| |
| // The fixed position object is offset from the top of the page, so remove |
| // any scroll offset. |
| LayoutPoint offsetFromRoot; |
| m_paintLayer.convertToLayerCoords(paintingInfo.rootLayer, offsetFromRoot); |
| paginationOffset -= offsetFromRoot - m_paintLayer.location(); |
| |
| for (unsigned i = 0; i < pages; i++) { |
| PaintLayerFragment fragment; |
| fragment.backgroundRect = paintingInfo.paintDirtyRect; |
| fragment.paginationOffset = paginationOffset; |
| fragments.append(fragment); |
| paginationOffset += LayoutPoint(LayoutUnit(), view->pageLogicalHeight()); |
| } |
| } else if (paginationLayer) { |
| // FIXME: This is a mess. Look closely at this code and the code in Layer |
| // and fix any issues in it & refactor to make it obvious from code |
| // structure what it does and that it's correct. |
| ClipRectsCacheSlot cacheSlot = (paintFlags & PaintLayerUncachedClipRects) |
| ? UncachedClipRects |
| : PaintingClipRects; |
| ShouldRespectOverflowClipType respectOverflowClip = |
| shouldRespectOverflowClip(paintFlags, m_paintLayer.layoutObject()); |
| // Calculate the transformed bounding box in the current coordinate space, |
| // to figure out which fragmentainers (e.g. columns) we need to visit. |
| LayoutRect transformedExtent = PaintLayer::transparencyClipBox( |
| &m_paintLayer, paginationLayer, PaintLayer::PaintingTransparencyClipBox, |
| PaintLayer::RootOfTransparencyClipBox, |
| paintingInfo.subPixelAccumulation, paintingInfo.getGlobalPaintFlags()); |
| // FIXME: we don't check if paginationLayer is within paintingInfo.rootLayer |
| // here. |
| paginationLayer->collectFragments( |
| fragments, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, |
| cacheSlot, IgnoreOverlayScrollbarSize, respectOverflowClip, 0, |
| paintingInfo.subPixelAccumulation, &transformedExtent); |
| } else { |
| // We don't need to collect any fragments in the regular way here. We have |
| // already calculated a clip rectangle for the ancestry if it was needed, |
| // and clipping this layer is something that can be done further down the |
| // path, when the transform has been applied. |
| PaintLayerFragment fragment; |
| fragment.backgroundRect = paintingInfo.paintDirtyRect; |
| fragments.append(fragment); |
| } |
| |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (fragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| ClipRect ancestorBackgroundClipRect; |
| if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| if (parentLayer) { |
| // Calculate the clip rectangle that the ancestors establish. |
| ClipRectsContext clipRectsContext( |
| paintingInfo.rootLayer, |
| (paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects |
| : PaintingClipRects, |
| IgnoreOverlayScrollbarSize); |
| if (shouldRespectOverflowClip(paintFlags, m_paintLayer.layoutObject()) == |
| IgnoreOverflowClip) |
| clipRectsContext.setIgnoreOverflowClip(); |
| ancestorBackgroundClipRect = |
| m_paintLayer.clipper().backgroundClipRect(clipRectsContext); |
| } |
| } |
| |
| PaintResult result = FullyPainted; |
| for (const auto& fragment : fragments) { |
| Optional<LayerClipRecorder> clipRecorder; |
| if (parentLayer && !RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| ClipRect clipRectForFragment(ancestorBackgroundClipRect); |
| // A fixed-position object is repeated on every page, but if it is clipped |
| // by an ancestor layer then the repetitions are clipped out. |
| if (!isFixedPosObjectInPagedMedia) |
| clipRectForFragment.moveBy(fragment.paginationOffset); |
| clipRectForFragment.intersect(fragment.backgroundRect); |
| if (clipRectForFragment.isEmpty()) |
| continue; |
| if (needsToClip(paintingInfo, clipRectForFragment)) { |
| if (m_paintLayer.layoutObject()->isPositioned() && |
| clipRectForFragment.isClippedByClipCss()) |
| UseCounter::count(m_paintLayer.layoutObject()->document(), |
| UseCounter::ClipCssOfPositionedElement); |
| if (m_paintLayer.layoutObject()->isFixedPositioned()) |
| UseCounter::count(m_paintLayer.layoutObject()->document(), |
| UseCounter::ClipCssOfFixedPositionElement); |
| clipRecorder.emplace(context, *parentLayer->layoutObject(), |
| DisplayItem::kClipLayerParent, clipRectForFragment, |
| paintingInfo.rootLayer, fragment.paginationOffset, |
| paintFlags); |
| } |
| } |
| if (paintFragmentByApplyingTransform(context, paintingInfo, paintFlags, |
| fragment.paginationOffset) == |
| MayBeClippedByPaintDirtyRect) |
| result = MayBeClippedByPaintDirtyRect; |
| } |
| return result; |
| } |
| |
| PaintResult PaintLayerPainter::paintFragmentByApplyingTransform( |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags, |
| const LayoutPoint& fragmentTranslation) { |
| // 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_paintLayer.convertToLayerCoords(paintingInfo.rootLayer, delta); |
| delta.moveBy(fragmentTranslation); |
| TransformationMatrix transform( |
| m_paintLayer.renderableTransform(paintingInfo.getGlobalPaintFlags())); |
| IntPoint roundedDelta = roundedIntPoint(delta); |
| transform.translateRight(roundedDelta.x(), roundedDelta.y()); |
| LayoutSize adjustedSubPixelAccumulation = |
| paintingInfo.subPixelAccumulation + (delta - roundedDelta); |
| |
| // TODO(jbroman): Put the real transform origin here, instead of using a |
| // matrix with the origin baked in. |
| FloatPoint3D transformOrigin; |
| Transform3DRecorder transform3DRecorder( |
| context, *m_paintLayer.layoutObject(), |
| DisplayItem::kTransform3DElementTransform, transform, transformOrigin); |
| |
| // Now do a paint with the root layer shifted to be us. |
| PaintLayerPaintingInfo transformedPaintingInfo( |
| &m_paintLayer, LayoutRect(enclosingIntRect(transform.inverse().mapRect( |
| paintingInfo.paintDirtyRect))), |
| paintingInfo.getGlobalPaintFlags(), adjustedSubPixelAccumulation); |
| transformedPaintingInfo.ancestorHasClipPathClipping = |
| paintingInfo.ancestorHasClipPathClipping; |
| |
| // Remove skip root background flag when we're painting with a new root. |
| if (&m_paintLayer != paintingInfo.rootLayer) |
| paintFlags &= ~PaintLayerPaintingSkipRootBackground; |
| |
| return paintLayerContentsCompositingAllPhases( |
| context, transformedPaintingInfo, paintFlags, ForceSingleFragment); |
| } |
| |
| PaintResult PaintLayerPainter::paintChildren( |
| unsigned childrenToVisit, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags) { |
| PaintResult result = FullyPainted; |
| if (!m_paintLayer.hasSelfPaintingLayerDescendant()) |
| return result; |
| |
| #if ENABLE(ASSERT) |
| LayerListMutationDetector mutationChecker(m_paintLayer.stackingNode()); |
| #endif |
| |
| PaintLayerStackingNodeIterator iterator(*m_paintLayer.stackingNode(), |
| childrenToVisit); |
| PaintLayerStackingNode* child = iterator.next(); |
| if (!child) |
| return result; |
| |
| IntSize scrollOffsetAccumulationForChildren = |
| paintingInfo.scrollOffsetAccumulation; |
| if (m_paintLayer.layoutObject()->hasOverflowClip()) |
| scrollOffsetAccumulationForChildren += |
| m_paintLayer.layoutBox()->scrolledContentOffset(); |
| |
| for (; child; child = iterator.next()) { |
| PaintLayerPainter childPainter(*child->layer()); |
| // If this Layer should paint into its own backing or a grouped backing, |
| // that will be done via CompositedLayerMapping::paintContents() and |
| // CompositedLayerMapping::doPaintTask(). |
| if (!childPainter.shouldPaintLayerInSoftwareMode( |
| paintingInfo.getGlobalPaintFlags(), paintFlags)) |
| continue; |
| |
| PaintLayerPaintingInfo childPaintingInfo = paintingInfo; |
| childPaintingInfo.scrollOffsetAccumulation = |
| scrollOffsetAccumulationForChildren; |
| // Rare case: accumulate scroll offset of non-stacking-context ancestors up |
| // to m_paintLayer. |
| for (PaintLayer* parentLayer = child->layer()->parent(); |
| parentLayer != &m_paintLayer; parentLayer = parentLayer->parent()) { |
| if (parentLayer->layoutObject()->hasOverflowClip()) |
| childPaintingInfo.scrollOffsetAccumulation += |
| parentLayer->layoutBox()->scrolledContentOffset(); |
| } |
| |
| if (childPainter.paintLayer(context, childPaintingInfo, paintFlags) == |
| MayBeClippedByPaintDirtyRect) |
| result = MayBeClippedByPaintDirtyRect; |
| } |
| |
| return result; |
| } |
| |
| bool PaintLayerPainter::shouldPaintLayerInSoftwareMode( |
| const GlobalPaintFlags globalPaintFlags, |
| PaintLayerFlags paintFlags) { |
| DisableCompositingQueryAsserts disabler; |
| |
| return m_paintLayer.compositingState() == NotComposited || |
| (globalPaintFlags & GlobalPaintFlattenCompositingLayers); |
| } |
| |
| void PaintLayerPainter::paintOverflowControlsForFragments( |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags paintFlags) { |
| PaintLayerScrollableArea* scrollableArea = m_paintLayer.getScrollableArea(); |
| if (!scrollableArea) |
| return; |
| |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (layerFragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| for (auto& fragment : layerFragments) { |
| // We need to apply the same clips and transforms that |
| // paintFragmentWithPhase would have. |
| LayoutRect cullRect = fragment.backgroundRect.rect(); |
| |
| Optional<LayerClipRecorder> clipRecorder; |
| if (needsToClip(localPaintingInfo, fragment.backgroundRect)) { |
| clipRecorder.emplace(context, *m_paintLayer.layoutObject(), |
| DisplayItem::kClipLayerOverflowControls, |
| fragment.backgroundRect, localPaintingInfo.rootLayer, |
| fragment.paginationOffset, paintFlags); |
| } |
| |
| Optional<ScrollRecorder> scrollRecorder; |
| if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() && |
| !localPaintingInfo.scrollOffsetAccumulation.isZero()) { |
| cullRect.move(localPaintingInfo.scrollOffsetAccumulation); |
| scrollRecorder.emplace(context, *m_paintLayer.layoutObject(), |
| DisplayItem::kScrollOverflowControls, |
| localPaintingInfo.scrollOffsetAccumulation); |
| } |
| |
| // We pass IntPoint() as the paint offset here, because |
| // ScrollableArea::paintOverflowControls just ignores it and uses the |
| // offset found in a previous pass. |
| CullRect snappedCullRect(pixelSnappedIntRect(cullRect)); |
| ScrollableAreaPainter(*scrollableArea) |
| .paintOverflowControls(context, IntPoint(), snappedCullRect, true); |
| } |
| } |
| |
| void PaintLayerPainter::paintFragmentWithPhase( |
| PaintPhase phase, |
| const PaintLayerFragment& fragment, |
| GraphicsContext& context, |
| const ClipRect& clipRect, |
| const PaintLayerPaintingInfo& paintingInfo, |
| PaintLayerFlags paintFlags, |
| ClipState clipState) { |
| DCHECK(m_paintLayer.isSelfPaintingLayer()); |
| |
| Optional<LayerClipRecorder> clipRecorder; |
| if (clipState != HasClipped && paintingInfo.clipToDirtyRect && |
| needsToClip(paintingInfo, clipRect)) { |
| DisplayItem::Type clipType = |
| DisplayItem::paintPhaseToClipLayerFragmentType(phase); |
| LayerClipRecorder::BorderRadiusClippingRule clippingRule; |
| switch (phase) { |
| case PaintPhaseSelfBlockBackgroundOnly: // Background painting will |
| // handle clipping to self. |
| case PaintPhaseSelfOutlineOnly: |
| case PaintPhaseMask: // Mask painting will handle clipping to self. |
| clippingRule = LayerClipRecorder::DoNotIncludeSelfForBorderRadius; |
| break; |
| default: |
| clippingRule = LayerClipRecorder::IncludeSelfForBorderRadius; |
| break; |
| } |
| |
| // TODO(schenney): Nested border-radius clips are not applied to composited |
| // children, probably due to an incorrect clipRoot. |
| // https://bugs.chromium.org/p/chromium/issues/detail?id=672561 |
| clipRecorder.emplace(context, *m_paintLayer.layoutObject(), clipType, |
| clipRect, paintingInfo.rootLayer, |
| fragment.paginationOffset, paintFlags, clippingRule); |
| } |
| |
| LayoutRect newCullRect(clipRect.rect()); |
| Optional<ScrollRecorder> scrollRecorder; |
| LayoutPoint paintOffset = -m_paintLayer.layoutBoxLocation(); |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| const auto* objectPaintProperties = |
| m_paintLayer.layoutObject()->paintProperties(); |
| DCHECK(objectPaintProperties && |
| objectPaintProperties->localBorderBoxProperties()); |
| paintOffset += |
| toSize(objectPaintProperties->localBorderBoxProperties()->paintOffset); |
| newCullRect.move(paintingInfo.scrollOffsetAccumulation); |
| } else { |
| paintOffset += toSize(fragment.layerBounds.location()); |
| if (!paintingInfo.scrollOffsetAccumulation.isZero()) { |
| // As a descendant of the root layer, m_paintLayer's painting is not |
| // controlled by the ScrollRecorders created by BlockPainter of the |
| // ancestor layers up to the root layer, so we need to issue |
| // ScrollRecorder for this layer seperately, with the scroll offset |
| // accumulated from the root layer to the parent of this layer, to get the |
| // same result as ScrollRecorder in BlockPainter. |
| paintOffset += paintingInfo.scrollOffsetAccumulation; |
| |
| newCullRect.move(paintingInfo.scrollOffsetAccumulation); |
| scrollRecorder.emplace(context, *m_paintLayer.layoutObject(), phase, |
| paintingInfo.scrollOffsetAccumulation); |
| } |
| } |
| PaintInfo paintInfo(context, pixelSnappedIntRect(newCullRect), phase, |
| paintingInfo.getGlobalPaintFlags(), paintFlags, |
| paintingInfo.rootLayer->layoutObject()); |
| |
| m_paintLayer.layoutObject()->paint(paintInfo, paintOffset); |
| } |
| |
| void PaintLayerPainter::paintBackgroundForFragments( |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const LayoutRect& transparencyPaintDirtyRect, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags paintFlags) { |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (layerFragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| for (auto& fragment : layerFragments) |
| paintFragmentWithPhase(PaintPhaseSelfBlockBackgroundOnly, fragment, context, |
| fragment.backgroundRect, localPaintingInfo, |
| paintFlags, HasNotClipped); |
| } |
| |
| void PaintLayerPainter::paintForegroundForFragments( |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const LayoutRect& transparencyPaintDirtyRect, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| bool selectionOnly, |
| PaintLayerFlags paintFlags) { |
| DCHECK(!(paintFlags & PaintLayerPaintingRootBackgroundOnly)); |
| |
| // Optimize clipping for the single fragment case. |
| bool shouldClip = localPaintingInfo.clipToDirtyRect && |
| layerFragments.size() == 1 && |
| !layerFragments[0].foregroundRect.isEmpty(); |
| ClipState clipState = HasNotClipped; |
| Optional<LayerClipRecorder> clipRecorder; |
| if (shouldClip && |
| needsToClip(localPaintingInfo, layerFragments[0].foregroundRect)) { |
| clipRecorder.emplace(context, *m_paintLayer.layoutObject(), |
| DisplayItem::kClipLayerForeground, |
| layerFragments[0].foregroundRect, |
| localPaintingInfo.rootLayer, |
| layerFragments[0].paginationOffset, paintFlags); |
| 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. |
| if (selectionOnly) { |
| paintForegroundForFragmentsWithPhase(PaintPhaseSelection, layerFragments, |
| context, localPaintingInfo, paintFlags, |
| clipState); |
| } else { |
| if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() || |
| m_paintLayer.needsPaintPhaseDescendantBlockBackgrounds()) { |
| size_t sizeBefore = |
| context.getPaintController().newDisplayItemList().size(); |
| paintForegroundForFragmentsWithPhase( |
| PaintPhaseDescendantBlockBackgroundsOnly, layerFragments, context, |
| localPaintingInfo, paintFlags, clipState); |
| // Don't set the empty flag if we are not painting the whole background. |
| if (!(paintFlags & PaintLayerPaintingSkipRootBackground)) { |
| bool phaseIsEmpty = |
| context.getPaintController().newDisplayItemList().size() == |
| sizeBefore; |
| DCHECK(phaseIsEmpty || |
| m_paintLayer.needsPaintPhaseDescendantBlockBackgrounds()); |
| m_paintLayer.setPreviousPaintPhaseDescendantBlockBackgroundsEmpty( |
| phaseIsEmpty); |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() || |
| m_paintLayer.needsPaintPhaseFloat()) { |
| size_t sizeBefore = |
| context.getPaintController().newDisplayItemList().size(); |
| paintForegroundForFragmentsWithPhase(PaintPhaseFloat, layerFragments, |
| context, localPaintingInfo, |
| paintFlags, clipState); |
| bool phaseIsEmpty = |
| context.getPaintController().newDisplayItemList().size() == |
| sizeBefore; |
| DCHECK(phaseIsEmpty || m_paintLayer.needsPaintPhaseFloat()); |
| m_paintLayer.setPreviousPaintPhaseFloatEmpty(phaseIsEmpty); |
| } |
| |
| paintForegroundForFragmentsWithPhase(PaintPhaseForeground, layerFragments, |
| context, localPaintingInfo, paintFlags, |
| clipState); |
| |
| if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() || |
| m_paintLayer.needsPaintPhaseDescendantOutlines()) { |
| size_t sizeBefore = |
| context.getPaintController().newDisplayItemList().size(); |
| paintForegroundForFragmentsWithPhase( |
| PaintPhaseDescendantOutlinesOnly, layerFragments, context, |
| localPaintingInfo, paintFlags, clipState); |
| bool phaseIsEmpty = |
| context.getPaintController().newDisplayItemList().size() == |
| sizeBefore; |
| DCHECK(phaseIsEmpty || m_paintLayer.needsPaintPhaseDescendantOutlines()); |
| m_paintLayer.setPreviousPaintPhaseDescendantOutlinesEmpty(phaseIsEmpty); |
| } |
| } |
| } |
| |
| void PaintLayerPainter::paintForegroundForFragmentsWithPhase( |
| PaintPhase phase, |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags paintFlags, |
| ClipState clipState) { |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (layerFragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| for (auto& fragment : layerFragments) { |
| if (!fragment.foregroundRect.isEmpty()) |
| paintFragmentWithPhase(phase, fragment, context, fragment.foregroundRect, |
| localPaintingInfo, paintFlags, clipState); |
| } |
| } |
| |
| void PaintLayerPainter::paintSelfOutlineForFragments( |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags paintFlags) { |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (layerFragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| for (auto& fragment : layerFragments) { |
| if (!fragment.backgroundRect.isEmpty()) |
| paintFragmentWithPhase(PaintPhaseSelfOutlineOnly, fragment, context, |
| fragment.backgroundRect, localPaintingInfo, |
| paintFlags, HasNotClipped); |
| } |
| } |
| |
| void PaintLayerPainter::paintMaskForFragments( |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags paintFlags) { |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (layerFragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| for (auto& fragment : layerFragments) |
| paintFragmentWithPhase(PaintPhaseMask, fragment, context, |
| fragment.backgroundRect, localPaintingInfo, |
| paintFlags, HasNotClipped); |
| } |
| |
| void PaintLayerPainter::paintChildClippingMaskForFragments( |
| const PaintLayerFragments& layerFragments, |
| GraphicsContext& context, |
| const PaintLayerPaintingInfo& localPaintingInfo, |
| PaintLayerFlags paintFlags) { |
| Optional<DisplayItemCacheSkipper> cacheSkipper; |
| if (layerFragments.size() > 1) |
| cacheSkipper.emplace(context); |
| |
| for (auto& fragment : layerFragments) |
| paintFragmentWithPhase(PaintPhaseClippingMask, fragment, context, |
| fragment.foregroundRect, localPaintingInfo, |
| paintFlags, HasNotClipped); |
| } |
| |
| void PaintLayerPainter::paintOverlayScrollbars( |
| GraphicsContext& context, |
| const LayoutRect& damageRect, |
| const GlobalPaintFlags paintFlags) { |
| if (!m_paintLayer.containsDirtyOverlayScrollbars()) |
| return; |
| |
| PaintLayerPaintingInfo paintingInfo(&m_paintLayer, |
| LayoutRect(enclosingIntRect(damageRect)), |
| paintFlags, LayoutSize()); |
| paintLayer(context, paintingInfo, PaintLayerPaintingOverlayScrollbars); |
| |
| m_paintLayer.setContainsDirtyOverlayScrollbars(false); |
| } |
| |
| } // namespace blink |