| /* |
| * Copyright (C) 2026 Igalia S.L. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "config.h" |
| #include "RenderLayer.h" |
| |
| #include "CSSFilterRenderer.h" |
| #include "ClipPathPaintScope.h" |
| #include "HitTestRequest.h" |
| #include "HitTestResult.h" |
| #include "ReferencedSVGResources.h" |
| #include "RenderAncestorIterator.h" |
| #include "RenderBoxInlines.h" |
| #include "RenderDescendantIterator.h" |
| #include "RenderElementInlines.h" |
| #include "RenderElementStyleInlines.h" |
| #include "RenderIterator.h" |
| #include "RenderLayerBacking.h" |
| #include "RenderLayerFilters.h" |
| #include "RenderLayerInlines.h" |
| #include "RenderLayerModelObject.h" |
| #include "RenderLayerSVGAdditionsInlines.h" |
| #include "RenderObjectInlines.h" |
| #include "RenderSVGContainer.h" |
| #include "RenderSVGForeignObject.h" |
| #include "RenderSVGHiddenContainer.h" |
| #include "RenderSVGInline.h" |
| #include "RenderSVGModelObject.h" |
| #include "RenderSVGModelObjectInlines.h" |
| #include "RenderSVGResourceClipper.h" |
| #include "RenderSVGResourceContainer.h" |
| #include "RenderSVGRoot.h" |
| #include "RenderSVGText.h" |
| #include "RenderSVGViewportContainer.h" |
| #include "SVGFilterElement.h" |
| #include "SVGRenderSupport.h" |
| #include "SVGSVGElement.h" |
| #include "Settings.h" |
| #include "StyleTransformResolver.h" |
| #include "TransformPaintScope.h" |
| #include "TransformationMatrix.h" |
| #include <wtf/TZoneMallocInlines.h> |
| |
| namespace WebCore { |
| |
| WTF_MAKE_STRUCT_TZONE_ALLOCATED_IMPL(RenderLayer::SVGData); |
| |
| // Applies clip-path and mask to a non-layer SVG renderer for the lifetime of the scope, the |
| // non-layer counterpart of the layer paint flow. A clip-path clips with a path via ClipPathPaintScope |
| // when possible. A mask, and a clip-path that cannot be a path, need a transparency layer capturing |
| // the renderer's foreground, which the destructor composites the mask and the clipper over with |
| // destination-in once the content is done. |
| class SVGNonLayerClippingAndMaskingScope { |
| WTF_MAKE_NONCOPYABLE(SVGNonLayerClippingAndMaskingScope); |
| public: |
| SVGNonLayerClippingAndMaskingScope(GraphicsContext& context, RenderElement& child, std::optional<LayoutSize> offsetFromRoot, const RenderLayer::LayerPaintingInfo& paintingInfo, OptionSet<PaintBehavior> paintBehavior, RenderObject* subtreePaintRoot, RenderLayer& hostLayer, bool isCollectingEventRegion) |
| : m_context(context) |
| , m_paintingInfo(paintingInfo) |
| , m_paintBehavior(paintBehavior) |
| , m_subtreePaintRoot(subtreePaintRoot) |
| , m_hostLayer(hostLayer) |
| { |
| CheckedPtr svgChild = dynamicDowncast<RenderLayerModelObject>(child); |
| if (!svgChild || (!child.hasClipPath() && !child.hasMask()) || !offsetFromRoot || (context.paintingDisabled() && !isCollectingEventRegion) || paintingInfo.paintDirtyRect.isEmpty()) |
| return; |
| |
| m_renderer = svgChild.get(); |
| m_offsetFromRoot = *offsetFromRoot; |
| |
| if (child.hasClipPath()) |
| m_clipScope.emplace(context, paintingInfo.regionContext, *svgChild, *offsetFromRoot, LayoutSize(), LayoutRect(), isCollectingEventRegion, ClipPathPaintScope::CoordinateMode::NonLayerPaint); |
| |
| // The mask, and a clip that can't be a path, composite over the foreground, so they need a |
| // painting context. While only the event region is collected there is nothing to composite. |
| if (context.paintingDisabled()) |
| return; |
| |
| m_paintsMask = child.hasMask(); |
| m_paintsClippingMask = m_clipScope && m_clipScope->needsMaskClipping(); |
| |
| if (m_paintsMask || m_paintsClippingMask) |
| m_maskLayer.emplace(context, 1); |
| } |
| |
| ~SVGNonLayerClippingAndMaskingScope() |
| { |
| if (!m_maskLayer) |
| return; |
| |
| CheckedRef renderer = *m_renderer; |
| |
| if (m_paintsMask) |
| paintPhase(renderer.get(), PaintPhase::Mask, LayoutPoint(m_offsetFromRoot)); |
| |
| if (m_paintsClippingMask) { |
| auto maskOriginTranslation = m_offsetFromRoot; |
| if (renderer->isSVGLayerAwareRenderer()) |
| maskOriginTranslation += toLayoutSize(renderer->currentSVGLayoutLocation()) - toLayoutSize(renderer->nominalSVGLayoutLocation()); |
| |
| GraphicsContextStateSaver maskCTMSaver(m_context, false); |
| if (!maskOriginTranslation.isZero()) { |
| maskCTMSaver.save(); |
| m_context.translate(maskOriginTranslation); |
| } |
| |
| paintPhase(renderer.get(), PaintPhase::ClippingMask, LayoutPoint()); |
| } |
| |
| m_maskLayer.reset(); |
| } |
| |
| private: |
| void paintPhase(RenderLayerModelObject& renderer, PaintPhase phase, const LayoutPoint& paintOffset) |
| { |
| PaintInfo paintInfo(m_context, m_paintingInfo.paintDirtyRect, phase, m_paintBehavior, m_subtreePaintRoot.get(), nullptr, nullptr, &m_paintingInfo.rootLayer->renderer(), m_hostLayer.ptr()); |
| renderer.paint(paintInfo, paintOffset); |
| } |
| |
| GraphicsContext& m_context; |
| const RenderLayer::LayerPaintingInfo& m_paintingInfo; |
| OptionSet<PaintBehavior> m_paintBehavior; |
| CheckedPtr<RenderObject> m_subtreePaintRoot; |
| CheckedRef<RenderLayer> m_hostLayer; |
| CheckedPtr<RenderLayerModelObject> m_renderer; |
| LayoutSize m_offsetFromRoot; |
| std::optional<ClipPathPaintScope> m_clipScope; |
| std::optional<TransparencyLayerScope> m_maskLayer; |
| bool m_paintsMask : 1 { false }; |
| bool m_paintsClippingMask : 1 { false }; |
| }; |
| |
| bool RenderLayer::hasVisibleContentForPaintingForSVG() const |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| // paintResourceLayerForSVG() sets isPaintingResourceLayer on the paint root, a resource |
| // container for mask/clipPath/marker/pattern or the referenced content itself for feImage. Any |
| // layer up the chain carrying the flag means we paint this resource subtree now, so it must |
| // paint despite living in a hidden (<defs>) or resource container, regardless of visibility. |
| for (CheckedPtr ancestor = this; ancestor; ancestor = ancestor->parent()) { |
| if (ancestor->isPaintingResourceLayerForSVG()) |
| return true; |
| } |
| |
| // Outside an active resource paint, resource-container and hidden SVG container (<defs>, |
| // <symbol> ...) content are never painted directly. |
| if (is<RenderSVGResourceContainer>(renderer()) || m_svgData->enclosingHiddenOrResourceContainer) |
| return false; |
| |
| return hasVisibleContent(); |
| } |
| |
| void RenderLayer::paintResourceLayerForSVG(GraphicsContext& context, const AffineTransform& layerContentTransform) |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| bool wasPaintingSVGResourceLayer = m_svgData->isPaintingResourceLayer; |
| m_svgData->isPaintingResourceLayer = true; |
| context.concatCTM(layerContentTransform); |
| |
| auto localPaintDirtyRect = LayoutRect::infiniteRect(); |
| |
| LayerPaintingInfo paintingInfo(this, localPaintDirtyRect, PaintBehavior::Normal, LayoutSize()); |
| |
| OptionSet<PaintLayerFlag> flags { PaintLayerFlag::TemporaryClipRects }; |
| if (!renderer().hasNonVisibleOverflow()) |
| flags.add({ PaintLayerFlag::PaintingOverflowContents, PaintLayerFlag::PaintingOverflowContentsRoot }); |
| |
| paintLayer(context, paintingInfo, flags); |
| |
| m_svgData->isPaintingResourceLayer = wasPaintingSVGResourceLayer; |
| } |
| |
| bool RenderLayer::setupClipPathIfNeededForSVG(OptionSet<PaintLayerFlag>& paintFlags) |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| // Applying clip-path on <clipPath> enforces us to use mask based clipping, so disable path based clipping. |
| // If isPaintingResourceLayerForSVG() is true, this function was invoked via paintResourceLayerForSVG() -- clipping on <clipPath> is already |
| // handled in RenderSVGResourceClipper::applyMaskClipping(), so do not set paintSVGClippingMask to true here. |
| if (!is<RenderSVGResourceClipper>(m_svgData->enclosingHiddenOrResourceContainer.get())) |
| return false; |
| |
| paintFlags.set(PaintLayerFlag::PaintingSVGClippingMask, !m_svgData->isPaintingResourceLayer); |
| return true; |
| } |
| |
| bool RenderLayer::paintForegroundForFragmentsForSVG(const LayerFragments& layerFragments, GraphicsContext& context, const LayerPaintingInfo& localPaintingInfo, OptionSet<PaintBehavior> localPaintBehavior, RenderObject* subtreePaintRootForRenderer) |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| if (!is<RenderSVGModelObject>(renderer()) || is<RenderSVGContainer>(renderer())) |
| return false; |
| |
| // SVG containers need to propagate paint phases. This could be saved if we remember somewhere if a SVG subtree |
| // contains e.g. LegacyRenderSVGForeignObject objects that do need the individual paint phases. For SVG shapes & SVG images |
| // we can avoid the multiple paintForegroundForFragmentsWithPhase() calls. |
| if (localPaintingInfo.paintBehavior.containsAny({ PaintBehavior::SelectionOnly, PaintBehavior::SelectionAndBackgroundsOnly })) |
| return true; |
| |
| paintForegroundForFragmentsWithPhase(PaintPhase::Foreground, layerFragments, context, localPaintingInfo, localPaintBehavior, subtreePaintRootForRenderer); |
| return true; |
| } |
| |
| void RenderLayer::paintNegativeZOrderChildrenForSVG(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, OptionSet<PaintLayerFlag> paintFlags) |
| { |
| ASSERT(m_svgData); |
| |
| // foreignObject uses HTML-style z-order painting. |
| if (renderer().isRenderSVGForeignObject()) { |
| paintList(negativeZOrderLayers(), context, paintingInfo, paintFlags); |
| return; |
| } |
| |
| // SVG: no-op. Negative z-order children are handled in DOM-order painting |
| // (paintChildrenInDOMOrderForSVG handles all children including negative z-index). |
| } |
| |
| static bool viewBoxDisablesPaintingForSVG(RenderLayerModelObject& renderer) |
| { |
| if (CheckedPtr svgRoot = dynamicDowncast<RenderSVGRoot>(renderer)) |
| return protect(svgRoot->svgSVGElement())->viewBoxDisablesPainting(); |
| |
| if (CheckedPtr viewportContainer = dynamicDowncast<RenderSVGViewportContainer>(renderer)) |
| return protect(viewportContainer->svgSVGElement())->viewBoxDisablesPainting(); |
| |
| return false; |
| } |
| |
| void RenderLayer::paintForegroundChildrenForSVG(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, const LayerPaintingInfo& localPaintingInfo, OptionSet<PaintLayerFlag> paintFlags, const LayerFragments& layerFragments, OptionSet<PaintBehavior> paintBehavior, RenderObject* subtreePaintRoot, std::optional<WTF::Range<unsigned>> svgPaintOrderItemRange, LayoutSize svgFilterChildLayerCorrection) |
| { |
| ASSERT(m_svgData); |
| |
| // An empty viewBox disables rendering of the content. Non-layer descendants are flattened into |
| // this layer's DOM-order list (see collectChildrenInDOMOrderForSVG), so the whole list has to be |
| // gated here -- RenderSVGViewportContainer::paint() alone would not catch the flattened ones. |
| if (viewBoxDisablesPaintingForSVG(renderer())) |
| return; |
| |
| // foreignObject uses HTML-style z-order painting. |
| if (renderer().isRenderSVGForeignObject()) { |
| // HTML layers and foreignObject use normal z-order list painting. No filter buffer correction |
| // applies: these child layers paint via paintList and never reach paintChildrenInDOMOrderForSVG. |
| paintList(normalFlowLayers(), context, paintingInfo, paintFlags); |
| paintList(positiveZOrderLayers(), context, localPaintingInfo, paintFlags); |
| return; |
| } |
| |
| paintChildrenInDOMOrderForSVG(context, localPaintingInfo, paintFlags, layerFragments, paintBehavior, subtreePaintRoot, svgPaintOrderItemRange, svgFilterChildLayerCorrection); |
| } |
| |
| RenderLayer::HitLayer RenderLayer::hitTestChildrenForSVG(RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, const HitTestingTransformState* transformState, double* zOffsetForDescendants) |
| { |
| ASSERT(m_svgData); |
| ASSERT(!renderer().isRenderSVGForeignObject()); |
| |
| // RenderSVGRoot: viewport-clip points outside the SVG content box. |
| if (auto* svgRoot = dynamicDowncast<RenderSVGRoot>(renderer())) { |
| if (svgRoot->shouldApplyViewportClip() && !hitTestLocation.intersects(svgRoot->contentBoxRect())) |
| return { }; |
| } |
| |
| return hitTestChildrenInDOMOrderForSVG(rootLayer, request, result, hitTestRect, hitTestLocation, transformState, zOffsetForDescendants); |
| } |
| |
| bool RenderLayer::shouldSkipRepaintAfterLayoutForSVG() const |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| // The SVG containers themselves never trigger repaints, only their contents are allowed to. |
| return is<RenderSVGContainer>(renderer()) && !shouldPaintWithFilters(); |
| } |
| |
| bool RenderLayer::isCompositedSVGPaintOrderChild() const |
| { |
| // A child that paints outside the container's DOM-order walk: it owns a backing store, or hosts a |
| // composited descendant parented under its layer. Either way, trailing non-composited siblings must |
| // paint above it through an overlay segment layer. |
| return compositedWithOwnBackingStore(*this) || hasCompositingDescendant(); |
| } |
| |
| bool RenderLayer::paintsInlineInSVGContainer() const |
| { |
| // A layer without its own backing store is painted inline by the container's DOM-order walk. |
| return !compositedWithOwnBackingStore(*this); |
| } |
| |
| bool RenderLayer::isFlattenedByEnclosingSVGReferenceFilter() const |
| { |
| // A non-compositable (reference) SVG filter renders its whole subtree into a single software buffer, |
| // so a descendant must not composite independently, which would let it escape the filter. Walk the |
| // SVG layer ancestry (below the SVG root, whose reference filter is handled separately) for such a |
| // filter. |
| for (CheckedPtr ancestor = parent(); ancestor; ancestor = ancestor->parent()) { |
| auto& renderer = ancestor->renderer(); |
| if (!renderer.isSVGLayerAwareRenderer() || renderer.isRenderSVGRoot()) |
| return false; |
| if (renderer.style().filter().hasReferenceFilter()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool RenderLayer::shouldSkipHitTestForSVG() const |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| // SVG resource layers and their children are never hit tested. |
| if (is<RenderSVGResourceContainer>(m_svgData->enclosingHiddenOrResourceContainer.get())) |
| return true; |
| |
| // Hidden SVG containers (<defs> / <symbol> ...) are never hit tested directly. |
| if (is<RenderSVGHiddenContainer>(renderer())) |
| return true; |
| |
| return false; |
| } |
| |
| bool RenderLayer::hasFailedFilterForSVG() const |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| |
| // Per the SVG spec, a referenced filter that cannot be applied (missing reference, |
| // empty filter) means the element must not be rendered. |
| if (!m_filters || m_filters->filter() || !renderer().style().filter().isReferenceFilter()) |
| return false; |
| return WTF::switchOn(renderer().style().filter().first(), |
| [&](const Style::FilterReference& reference) { |
| return ReferencedSVGResources::referencedFilterElement(protect(renderer().treeScopeForSVGReferences()), reference) != nullptr; |
| }, |
| [&](const auto&) { return false; } |
| ); |
| } |
| |
| void RenderLayer::updateAncestorDependentStateForSVG() |
| { |
| ASSERT(m_svgData); |
| ASSERT(renderer().document().settings().layerBasedSVGEngineEnabled()); |
| m_svgData->enclosingHiddenOrResourceContainer = ancestorsOfType<RenderSVGHiddenContainer>(renderer()).first(); |
| } |
| |
| void RenderLayer::dirtyChildrenInDOMOrderForSVG() |
| { |
| ASSERT(m_svgData); |
| m_svgData->childrenInDOMOrder.shrink(0); // Use shrink(0) instead of clear() to retain our capacity. |
| m_svgData->childrenInDOMOrderDirty = true; |
| |
| // Segment ranges index into this flat list, so a rebuild invalidates them. Schedule a configuration |
| // update so the segments are recomputed off the fresh list. |
| if (isComposited() && backing()->hasSVGPaintOrderSegments()) |
| setNeedsCompositingConfigurationUpdate(); |
| } |
| |
| void RenderLayer::invalidateEnclosingSVGContainerSegmentation() |
| { |
| if (!isSVGLayer()) |
| return; |
| // This layer's composited-child status in its enclosing SVG container may have changed, so that |
| // container has to recompute its paint-order segments. |
| if (CheckedPtr enclosing = enclosingCompositingLayerForRepaint(ExcludeSelf).layer) |
| enclosing->setNeedsCompositingConfigurationUpdate(); |
| } |
| |
| void RenderLayer::collectChildrenInDOMOrderForSVG() |
| { |
| ASSERT(m_svgData); |
| m_svgData->childrenInDOMOrderDirty = false; |
| m_svgData->childrenInDOMOrder.shrink(0); // Use shrink(0) instead of clear() to retain our capacity. |
| |
| bool anyNonZeroZIndex = false; |
| appendChildrenInDOMOrderForSVG(renderer(), { }, anyNonZeroZIndex); |
| |
| // Sort by z-index; for equal z-index, stable_sort preserves DOM order. |
| // Skip entirely when no child uses z-index — collection order already matches DOM order. |
| if (anyNonZeroZIndex) { |
| std::stable_sort(m_svgData->childrenInDOMOrder.begin(), m_svgData->childrenInDOMOrder.end(), |
| [](const SVGPaintOrderLayerItem& a, const SVGPaintOrderLayerItem& b) { |
| return a.zIndex < b.zIndex; |
| }); |
| } |
| } |
| |
| // Recursively collect children, splitting non-layered containers that have |
| // layered descendants to ensure proper DOM-order interleaving. Returns true |
| // if any layered child was found in this subtree. |
| bool RenderLayer::appendChildrenInDOMOrderForSVG(RenderElement& parent, LayoutSize ancestorOffset, bool& anyNonZeroZIndex) |
| { |
| auto& allChildren = m_svgData->childrenInDOMOrder; |
| bool hasIndependentlyPaintedDescendant = false; |
| for (CheckedRef child : childrenOfType<RenderElement>(parent)) { |
| // Never directly paint children of <defs>, <linearGradient>, etc. |
| if (child->isRenderSVGHiddenContainer()) |
| continue; |
| |
| if (child->hasSelfPaintingLayer()) { |
| CheckedRef layerModelObject = downcast<RenderLayerModelObject>(child.get()); |
| CheckedRef childLayer = *layerModelObject->layer(); |
| int zIndex = childLayer->zIndex(); |
| if (zIndex) |
| anyNonZeroZIndex = true; |
| allChildren.append(SVGPaintOrderLayerItem::makeLayered(child.get(), childLayer.get(), zIndex)); |
| hasIndependentlyPaintedDescendant = true; |
| continue; |
| } |
| |
| // Transformed non-layer children are painted atomically by the consumer: |
| // its transform is applied and children are painted recursively. |
| if (child->isTransformed()) { |
| allChildren.append(SVGPaintOrderLayerItem::makeAtomic(child.get(), ancestorOffset)); |
| hasIndependentlyPaintedDescendant = true; |
| continue; |
| } |
| |
| // Paint a non-layer child that clips its subtree together with that subtree as one unit, so the |
| // clip covers all of it. Splitting it apart would move its descendants into the parent's |
| // z-order list and paint them outside the clip. |
| if (RenderSVGModelObject::clipsSubtree(child.get())) { |
| allChildren.append(SVGPaintOrderLayerItem::makeAtomic(child.get(), ancestorOffset)); |
| hasIndependentlyPaintedDescendant = true; |
| continue; |
| } |
| |
| // Leaf nodes (no children) are always painted atomically. |
| if (!child->firstChild()) { |
| allChildren.append(SVGPaintOrderLayerItem::makeAtomic(child.get(), ancestorOffset)); |
| continue; |
| } |
| |
| // Compute the offset that this child contributes to its descendants. |
| LayoutSize childOffset = ancestorOffset; |
| if (CheckedPtr svgModel = dynamicDowncast<RenderSVGModelObject>(child.get())) |
| childOffset += toLayoutSize(svgModel->currentSVGLayoutLocation()); |
| |
| // We don't yet know whether this non-layered container has any |
| // independently painted descendants (layered or transformed non-layer |
| // children), so recurse speculatively and decide what to keep based on |
| // the result. |
| // |
| // Example: |
| // <g id="A"> |
| // <rect id="r1"/> |
| // <g id="B" style="z-index: 5; opacity: 0.5;"/> <!-- layered --> |
| // <rect id="r2"/> |
| // </g> |
| // |
| // When recursing into A, the inner walk visits r1 (leaf, append), then B |
| // (layered, append, mark independently-painted), then r2 (leaf, append). |
| // The speculative segment is now [r1, B, r2] and |
| // subtreeHasIndependentlyPaintedDescendant=true, so we fall into Case B |
| // and additionally append A with the split flag. After z-sort the final |
| // list is [r1, r2, A(split), B] — B last because z=5. The consumer then |
| // paints r1 and r2 at A's offset, then A(split) which paints only A's |
| // own outline (no child recursion), then B on top. Without the split |
| // flag, A's normal paint would recurse into r1/B/r2 and all three would |
| // be painted twice — once via A's recursion, once via their own list |
| // entries. |
| // |
| // Case A — no independently painted descendants: the subtree paints |
| // atomically as part of the container's own normal paint walk. Discard |
| // the speculative entries (shrink to startIndex) and append a single |
| // entry for the container. Without the shrink, A's normal recursive |
| // paint would visit children that are still present in the list, |
| // causing the same double-paint problem. |
| // |
| // Case B — independently painted descendants exist: keep their entries |
| // (so they sort into the overall z-order) and append a "split" entry |
| // for the container itself. The consumer paints only the container's |
| // own non-content contribution (outlines) and skips child recursion, |
| // since each independently painted descendant is painted from the |
| // sorted list. |
| size_t startIndex = allChildren.size(); |
| bool subtreeHasIndependentlyPaintedDescendant = appendChildrenInDOMOrderForSVG(child.get(), childOffset, anyNonZeroZIndex); |
| if (subtreeHasIndependentlyPaintedDescendant) { |
| ASSERT(!RenderSVGModelObject::clipsSubtree(child.get())); |
| allChildren.append(SVGPaintOrderLayerItem::makeOutlineOnly(child.get(), ancestorOffset)); |
| hasIndependentlyPaintedDescendant = true; |
| } else { |
| allChildren.shrink(startIndex); |
| allChildren.append(SVGPaintOrderLayerItem::makeAtomic(child.get(), ancestorOffset)); |
| } |
| } |
| return hasIndependentlyPaintedDescendant; |
| } |
| |
| const Vector<SVGPaintOrderLayerItem>& RenderLayer::childrenInDOMOrderForSVG() |
| { |
| ASSERT(m_svgData); |
| if (m_svgData->childrenInDOMOrderDirty) |
| collectChildrenInDOMOrderForSVG(); |
| return m_svgData->childrenInDOMOrder; |
| } |
| |
| void RenderLayer::paintNonLayerChildForFragmentsForSVG(RenderElement& childRenderer, const LayoutSize& accumulatedAncestorOffset, |
| PaintPhase phase, const LayerFragments& layerFragments, GraphicsContext& context, const LayerPaintingInfo& paintingInfo, |
| OptionSet<PaintBehavior> paintBehavior, RenderObject* subtreePaintRootForRenderer, const LayoutPoint& containerBaseOffset, bool isSVGRoot, bool sharedClipApplied) |
| { |
| LayoutPoint svgRootScrollAdjustment; |
| if (isSVGRoot) |
| svgRootScrollAdjustment = LayoutPoint(-downcast<RenderSVGRoot>(renderer()).scrollPosition()); |
| |
| // Like collectEventRegionForFragments(), event-region collection runs even for fragments |
| // with no visible content (shouldPaintContent false, empty foreground rect). |
| bool collectingEventRegion = phase == PaintPhase::EventRegion; |
| for (const auto& fragment : layerFragments) { |
| if (!collectingEventRegion && (!fragment.shouldPaintContent || fragment.dirtyForegroundRect().isEmpty())) |
| continue; |
| |
| // Re-clip per shape only when the caller has not already applied a shared clip around the |
| // whole child loop (see paintChildrenInDOMOrderForSVG). |
| std::optional<GraphicsContextStateSaver> stateSaver; |
| std::optional<RegionContextStateSaver> regionContextStateSaver; |
| if (!sharedClipApplied) { |
| stateSaver.emplace(context, false); |
| regionContextStateSaver.emplace(paintingInfo.regionContext); |
| clipToRect(context, *stateSaver, *regionContextStateSaver, paintingInfo, paintBehavior, fragment.dirtyForegroundRect()); |
| } |
| |
| PaintInfo paintInfo(context, fragment.dirtyForegroundRect().rect(), |
| phase, paintBehavior, subtreePaintRootForRenderer, |
| nullptr, nullptr, &paintingInfo.rootLayer->renderer(), this, |
| paintingInfo.requireSecurityOriginAccessForWidgets); |
| if (collectingEventRegion) |
| paintInfo.regionContext = paintingInfo.regionContext; |
| if (phase == PaintPhase::Foreground) |
| paintInfo.overlapTestRequests = paintingInfo.overlapTestRequests; |
| paintInfo.updateSubtreePaintRootForChildren(&renderer()); |
| |
| auto containerPaintOffset = paintOffsetForRenderer(fragment, paintingInfo); |
| auto childPaintOffset = containerBaseOffset.isZero() ? containerPaintOffset : containerPaintOffset + containerBaseOffset; |
| if (isSVGRoot) |
| childPaintOffset.moveBy(svgRootScrollAdjustment); |
| |
| auto finalOffset = childPaintOffset + accumulatedAncestorOffset; |
| childRenderer.paint(paintInfo, finalOffset); |
| } |
| } |
| |
| void RenderLayer::paintResourceCorrectedChildLayerForSVG(GraphicsContext& context, RenderLayer& childLayer, |
| const LayerPaintingInfo& paintingInfo, OptionSet<PaintLayerFlag> paintFlags, LayoutSize correction) const |
| { |
| // A child layer painted inside an SVG resource/filter buffer (mask, clipPath, filter) computes its |
| // position relative to the buffer root and so misses the buffer's nominalSVGLayoutLocation offset. |
| // Translate by 'correction' to compensate. Deeper layers do not re-apply it: the resource path |
| // recomputes it per level and the filter path re-derives it per layer in paintLayerContents. |
| GraphicsContextStateSaver stateSaver(context, false); |
| if (!correction.isZero()) { |
| stateSaver.save(); |
| context.translate(correction.width(), correction.height()); |
| } |
| childLayer.paintLayer(context, paintingInfo, paintFlags); |
| } |
| |
| void RenderLayer::paintChildrenInDOMOrderForSVG(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, OptionSet<PaintLayerFlag> paintFlags, |
| const LayerFragments& layerFragments, OptionSet<PaintBehavior> paintBehavior, RenderObject* subtreePaintRootForRenderer, std::optional<WTF::Range<unsigned>> svgPaintOrderItemRange, LayoutSize svgFilterChildLayerCorrection) |
| { |
| ASSERT(m_svgData); |
| auto& allChildren = childrenInDOMOrderForSVG(); |
| if (allChildren.isEmpty()) |
| return; |
| |
| bool isSVGRoot = is<RenderSVGRoot>(renderer()); |
| bool isCollectingEventRegion = paintFlags.contains(PaintLayerFlag::CollectingEventRegion); |
| LayoutPoint containerBaseOffset; |
| LayoutPoint layerResourceOffset; |
| |
| if (auto* svgModelObject = dynamicDowncast<RenderSVGModelObject>(renderer())) { |
| containerBaseOffset = svgModelObject->currentSVGLayoutLocation(); |
| if (isPaintingResourceLayerForSVG()) |
| layerResourceOffset = svgModelObject->nominalSVGLayoutLocation(); |
| } else if (auto* svgRoot = dynamicDowncast<RenderSVGRoot>(renderer())) |
| containerBaseOffset = svgRoot->location(); |
| |
| // When this layer paints as a single paint-order segment (paintIntoLayer passes the range of the |
| // GraphicsLayer being painted), walk only that segment's half-open range of the flat list. The |
| // rootLayer == this check keeps the range tied to this layer: a child layer paints through its own |
| // paintLayer call, which carries no range, so it cannot leak into the child's flat list. |
| size_t beginIndex = 0; |
| size_t endIndex = allChildren.size(); |
| if (svgPaintOrderItemRange && paintingInfo.rootLayer == this) { |
| ASSERT(svgPaintOrderItemRange->end() <= allChildren.size()); |
| beginIndex = svgPaintOrderItemRange->begin(); |
| endIndex = std::min<size_t>(svgPaintOrderItemRange->end(), allChildren.size()); |
| } |
| |
| // Every non-layer child clips the context to the same fragment dirty-foreground rect, one |
| // CGContextSaveGState + CGContextClipToRect per shape. With a single paintable fragment and no |
| // child owning a layer, apply that clip once and share it across all children. Skip resource |
| // layers (marker/mask/pattern), whose content paints in its own coordinate system where this |
| // fragment rect is the wrong clip. |
| bool hasChildLayers = beginIndex < endIndex && std::ranges::any_of(allChildren.subspan(beginIndex, endIndex - beginIndex), [](auto& child) { |
| return !!child.layer.get(); |
| }); |
| std::optional<GraphicsContextStateSaver> sharedClipSaver; |
| std::optional<RegionContextStateSaver> sharedRegionSaver; |
| if (!hasChildLayers && layerFragments.size() == 1 && layerFragments[0].shouldPaintContent && !isPaintingResourceLayerForSVG()) { |
| auto dirtyForegroundRect = layerFragments[0].dirtyForegroundRect(); |
| if (!dirtyForegroundRect.isEmpty()) { |
| sharedClipSaver.emplace(context, false); |
| sharedRegionSaver.emplace(paintingInfo.regionContext); |
| clipToRect(context, *sharedClipSaver, *sharedRegionSaver, paintingInfo, paintBehavior, dirtyForegroundRect); |
| } |
| } |
| |
| for (size_t index = beginIndex; index < endIndex; ++index) { |
| auto& childToPaint = allChildren[index]; |
| if (CheckedPtr childLayer = childToPaint.layer.get()) { |
| // Children that own a backing store paint through the compositor and an overlay segment layer |
| // paints above them. A composited child that paints into a composited ancestor has no own |
| // GraphicsLayer, so it must still paint inline here (see paintsInlineInSVGContainer()). |
| if (!childLayer->paintsInlineInSVGContainer() && !paintBehavior.contains(PaintBehavior::FlattenCompositingLayers)) |
| continue; |
| |
| // mask/clipPath buffers supply the correction locally as layerResourceOffset, filters |
| // carry it via the svgFilterChildLayerCorrection argument (computed at filter-buffer setup |
| // in paintLayerContents). These are mutually exclusive per child, with the resource path |
| // taking precedence. |
| auto correction = (isPaintingResourceLayerForSVG() && !layerResourceOffset.isZero()) |
| ? toLayoutSize(layerResourceOffset) : svgFilterChildLayerCorrection; |
| |
| paintResourceCorrectedChildLayerForSVG(context, *childLayer, paintingInfo, paintFlags, correction); |
| continue; |
| } |
| |
| CheckedPtr childRendererPtr = childToPaint.renderer.get(); |
| if (!childRendererPtr) |
| continue; |
| CheckedRef childRenderer = *childRendererPtr; |
| |
| // Transformed non-layer children: apply the transform and recursively paint the subtree. |
| if (childRenderer->isTransformed()) { |
| // When this container is itself a transformed SVG layer (e.g. a layered <use>), its CTM is |
| // positioned at its own nominalSVGLayoutLocation (objectBoundingBox top-left). The transform |
| // recursion paints the child additively in that CTM, so the child would inherit a spurious |
| // shift. Cancel it by folding -nominalCorrection into the child's transform scope (see |
| // paintRendererByApplyingTransformForSVG). No-op for SVG root / viewport (nominal == 0) and |
| // when not painting as a layer-scope root. Since every transformed container is layered, the |
| // only transformed renderers reaching this recursion are leaves and the anonymous outermost |
| // viewport container, so no layer descendant ever needs the inverse correction. |
| LayoutSize nominalCorrection; |
| if (this == paintingInfo.rootLayer && !isPaintingResourceLayerForSVG() && renderer().isSVGLayerAwareRenderer() && !renderer().isRenderSVGRoot()) |
| nominalCorrection = toLayoutSize(renderer().nominalSVGLayoutLocation()); |
| |
| for (const auto& fragment : layerFragments) { |
| if (!fragment.shouldPaintContent || fragment.dirtyForegroundRect().isEmpty()) |
| continue; |
| |
| // Re-clip per shape only when no shared clip was applied before the loop (multiple fragments, or child layers present). |
| std::optional<GraphicsContextStateSaver> stateSaver; |
| std::optional<RegionContextStateSaver> regionContextStateSaver; |
| if (!sharedClipSaver) { |
| stateSaver.emplace(context, false); |
| regionContextStateSaver.emplace(paintingInfo.regionContext); |
| clipToRect(context, *stateSaver, *regionContextStateSaver, paintingInfo, paintBehavior, fragment.dirtyForegroundRect()); |
| } |
| |
| // nominalCorrection is applied inside the scope, so TransformPaintScope concatenates it |
| // onto the CTM and inverse-maps paintDirtyRect through it together. The leaf |
| // visual-overflow cull (RenderSVGShape::paint) then compares the shifted subtree against |
| // a matching damage rect, so overhanging leaves survive incremental repaints |
| // (svg/W3C-SVG-1.1/animate-elem-80-t.svg). |
| paintRendererByApplyingTransformForSVG(context, childRenderer, |
| childToPaint.accumulatedAncestorOffset, paintingInfo, paintFlags, |
| paintBehavior, subtreePaintRootForRenderer, nominalCorrection); |
| } |
| continue; |
| } |
| |
| std::optional<SVGNonLayerClippingAndMaskingScope> clippingAndMaskingScope; |
| if (childRenderer->hasClipPath() || childRenderer->hasMask()) { |
| std::optional<LayoutSize> offsetFromRoot; |
| for (const auto& fragment : layerFragments) { |
| if (!fragment.shouldPaintContent || fragment.dirtyForegroundRect().isEmpty()) |
| continue; |
| auto childPaintOffset = paintOffsetForRenderer(fragment, paintingInfo) + containerBaseOffset; |
| if (isSVGRoot) |
| childPaintOffset.moveBy(LayoutPoint(-downcast<RenderSVGRoot>(renderer()).scrollPosition())); |
| offsetFromRoot = toLayoutSize(childPaintOffset + childToPaint.accumulatedAncestorOffset); |
| break; |
| } |
| clippingAndMaskingScope.emplace(context, childRenderer.get(), offsetFromRoot, paintingInfo, paintBehavior, subtreePaintRootForRenderer, *this, isCollectingEventRegion); |
| } |
| |
| // phasesToPaint only covers normal painting (Foreground/Outline), so drive the |
| // EventRegion phase separately for non-layer children when collecting the event region. |
| if (paintFlags.contains(PaintLayerFlag::CollectingEventRegion)) { |
| paintNonLayerChildForFragmentsForSVG(childRenderer.get(), childToPaint.accumulatedAncestorOffset, |
| PaintPhase::EventRegion, layerFragments, context, paintingInfo, paintBehavior, subtreePaintRootForRenderer, containerBaseOffset, isSVGRoot, sharedClipSaver.has_value()); |
| continue; |
| } |
| |
| for (auto phase : childToPaint.phasesToPaint) { |
| if ((phase == PaintPhase::Outline || phase == PaintPhase::SelfOutline) && !childRenderer->hasOutline()) |
| continue; |
| paintNonLayerChildForFragmentsForSVG(childRenderer.get(), childToPaint.accumulatedAncestorOffset, |
| phase, layerFragments, context, paintingInfo, paintBehavior, subtreePaintRootForRenderer, containerBaseOffset, isSVGRoot, sharedClipSaver.has_value()); |
| } |
| } |
| } |
| |
| std::optional<RenderLayer::SVGRendererTransform> RenderLayer::computeRendererTransformForSVG( |
| CheckedRef<RenderElement> rendererRef, const LayoutSize& positionOffset) const |
| { |
| CheckedRef layerModelObject = downcast<RenderLayerModelObject>(rendererRef.get()); |
| TransformationMatrix transform; |
| CheckedRef style = layerModelObject->style(); |
| |
| // Fast path: a non-layer SVG renderer already caches 'transform' in m_localTransform. |
| // The only difference from the paint transform is the reference-box + nominal shift, which |
| // just moves the transform-origin, so the paint transform equals: |
| // translate(nominal) * m_localTransform * translate(-nominal). |
| bool isNonLayerSVG = !rendererRef->hasSelfPaintingLayer() && rendererRef->isSVGLayerAwareRenderer(); |
| CheckedPtr useTransformRenderer = (isNonLayerSVG && !is<RenderSVGViewportContainer>(rendererRef.get())) |
| ? dynamicDowncast<RenderSVGModelObject>(rendererRef.get()) : nullptr; |
| |
| if (useTransformRenderer) { |
| auto nominal = useTransformRenderer->nominalSVGLayoutLocation(); |
| transform.translate(nominal.x().toFloat(), nominal.y().toFloat()); |
| transform.multiply(TransformationMatrix(useTransformRenderer->localTransform())); |
| transform.translate(-nominal.x().toFloat(), -nominal.y().toFloat()); |
| } else { |
| auto referenceBoxRect = layerModelObject->transformReferenceBoxRect(style); |
| |
| // For non-layer renderers, undo the alignReferenceBox shift applied in transformReferenceBoxRect(). |
| if (isNonLayerSVG) |
| referenceBoxRect.moveBy(layerModelObject->nominalSVGLayoutLocation()); |
| |
| layerModelObject->applyTransform(transform, style, referenceBoxRect, Style::TransformResolver::allTransformOperations); |
| } |
| |
| // For the outermost viewport container (anonymous child of RenderSVGRoot), apply the |
| // content-box origin offset (border+padding). |
| if (auto* viewportContainer = dynamicDowncast<RenderSVGViewportContainer>(rendererRef.get()); viewportContainer && viewportContainer->isAnonymous()) { |
| if (CheckedPtr svgRoot = dynamicDowncast<RenderSVGRoot>(viewportContainer->parent())) { |
| auto contentBoxLocation = svgRoot->contentBoxLocation(); |
| if (!contentBoxLocation.isZero()) |
| transform.translateRight(contentBoxLocation.x(), contentBoxLocation.y()); |
| } |
| } |
| |
| if (!transform.isInvertible()) |
| return std::nullopt; |
| |
| bool isOutermostViewportContainer = is<RenderSVGViewportContainer>(rendererRef.get()) && rendererRef->isAnonymous(); |
| LayoutSize containerOffset; |
| if (rendererRef->hasSelfPaintingLayer() && !isOutermostViewportContainer) { |
| containerOffset = positionOffset; |
| if (auto* box = dynamicDowncast<RenderBox>(rendererRef.get())) |
| containerOffset += toLayoutSize(box->location()); |
| else if (auto* svgModel = dynamicDowncast<RenderSVGModelObject>(rendererRef.get())) |
| containerOffset += toLayoutSize(svgModel->currentSVGLayoutLocation()); |
| } |
| |
| transform.translateRight(containerOffset.width().toFloat(), containerOffset.height().toFloat()); |
| return SVGRendererTransform { transform, containerOffset }; |
| } |
| |
| void RenderLayer::paintRendererByApplyingTransformForSVG(GraphicsContext& context, CheckedRef<RenderElement> rendererToPaint, |
| const LayoutSize& positionOffset, const LayerPaintingInfo& paintingInfo, OptionSet<PaintLayerFlag> paintFlags, |
| OptionSet<PaintBehavior> paintBehavior, RenderObject* subtreePaintRoot, const LayoutSize& nominalPreTranslation) |
| { |
| ASSERT(rendererToPaint->isTransformed()); |
| auto rendererTransform = computeRendererTransformForSVG(rendererToPaint, positionOffset); |
| if (!rendererTransform) |
| return; |
| |
| Ref document = rendererToPaint->document(); |
| float deviceScaleFactor = document->deviceScaleFactor(); |
| |
| ASSERT(paintingInfo.subpixelOffset.isZero()); |
| LayoutSize adjustedSubpixelOffset; |
| |
| // Fold the container's nominal pre-translation into the scope transform rather than a separate |
| // context.translate, so TransformPaintScope concatenates it onto the CTM and inverse-maps |
| // paintDirtyRect through it together, keeping content and the leaf visual-overflow cull in one |
| // coordinate space. translateRight post-multiplies, placing the offset in this container's own |
| // space, where nominal lives. |
| auto scopeTransform = rendererTransform->transform; |
| if (!nominalPreTranslation.isZero()) |
| scopeTransform.translateRight(-nominalPreTranslation.width().toFloat(), -nominalPreTranslation.height().toFloat()); |
| |
| TransformPaintScope scope(context, paintingInfo, scopeTransform, deviceScaleFactor, adjustedSubpixelOffset); |
| |
| auto adjustedPaintFlags = paintFlags; |
| adjustedPaintFlags.remove(PaintLayerFlag::PaintingOverflowContents); |
| |
| if (rendererToPaint->hasSelfPaintingLayer()) { |
| CheckedRef layerModelObject = downcast<RenderLayerModelObject>(rendererToPaint.get()); |
| CheckedPtr childLayer = layerModelObject->layer(); |
| |
| LayerPaintingInfo childPaintingInfo(childLayer.get(), scope.transformedPaintingInfo().paintDirtyRect, paintBehavior, LayoutSize()); |
| childLayer->paintLayer(context, childPaintingInfo, adjustedPaintFlags | PaintLayerFlag::AppliedTransform); |
| } else { |
| // Non-layer renderer painted directly within the transform scope. |
| auto& transformedPaintingInfo = scope.transformedPaintingInfo(); |
| auto paintInScope = [&](PaintPhase phase, const LayoutPoint& offset) { |
| PaintInfo paintInfo(context, transformedPaintingInfo.paintDirtyRect, phase, paintBehavior, subtreePaintRoot, |
| nullptr, nullptr, &transformedPaintingInfo.rootLayer->renderer(), this, |
| transformedPaintingInfo.requireSecurityOriginAccessForWidgets); |
| rendererToPaint->paint(paintInfo, offset); |
| }; |
| |
| // A renderer's own paint() positions content at paintOffset + currentSVGLayoutLocation() - at a |
| // transform-scope root we want it at the visual top-left, so pass (nominal - current). This is |
| // uniform for shapes, images, text and a container's own outline. |
| LayoutPoint selfPaintOffset; |
| if (rendererToPaint->isSVGLayerAwareRenderer()) { |
| CheckedRef layerModelObject = downcast<RenderLayerModelObject>(rendererToPaint.get()); |
| selfPaintOffset = layerModelObject->nominalSVGLayoutLocation(); |
| selfPaintOffset.moveBy(-layerModelObject->currentSVGLayoutLocation()); |
| } |
| |
| bool isCollectingEventRegion = adjustedPaintFlags.contains(PaintLayerFlag::CollectingEventRegion); |
| { |
| SVGNonLayerClippingAndMaskingScope clippingAndMaskingScope(context, rendererToPaint.get(), toLayoutSize(selfPaintOffset), transformedPaintingInfo, paintBehavior, subtreePaintRoot, *this, isCollectingEventRegion); |
| if (rendererToPaint->isRenderSVGContainer()) { |
| // Children recurse from the container's nominal origin (selfPaintOffset plus current) and |
| // add their own currentSVGLayoutLocation. The anonymous outermost viewport starts at (0, 0). |
| LayoutPoint recursionBase; |
| if (auto* viewportContainer = dynamicDowncast<RenderSVGViewportContainer>(rendererToPaint.get()); viewportContainer && viewportContainer->isAnonymous()) { |
| // Outermost viewport container: coordinate system starts at (0, 0). |
| } else if (auto* svgModel = dynamicDowncast<RenderSVGModelObject>(rendererToPaint.get())) |
| recursionBase = svgModel->nominalSVGLayoutLocation(); |
| paintSubtreeWithinTransformScopeForSVG(context, rendererToPaint.get(), recursionBase, transformedPaintingInfo, adjustedPaintFlags, paintBehavior, subtreePaintRoot); |
| if (rendererToPaint->hasOutline()) |
| paintInScope(PaintPhase::SelfOutline, selfPaintOffset); |
| } else { |
| paintInScope(PaintPhase::Foreground, selfPaintOffset); |
| if (rendererToPaint->hasOutline()) |
| paintInScope(PaintPhase::Outline, selfPaintOffset); |
| } |
| } |
| } |
| } |
| |
| void RenderLayer::paintSubtreeWithinTransformScopeForSVG(GraphicsContext& context, RenderElement& container, |
| const LayoutPoint& paintOffset, const LayerPaintingInfo& paintingInfo, OptionSet<PaintLayerFlag> paintFlags, |
| OptionSet<PaintBehavior> paintBehavior, RenderObject* subtreePaintRoot) |
| { |
| // Apply viewport clipping for nested <svg> elements without layers. |
| GraphicsContextStateSaver clipSaver(context, false); |
| if (CheckedPtr viewportContainer = dynamicDowncast<RenderSVGViewportContainer>(container)) { |
| if (!viewportContainer->hasSelfPaintingLayer() && SVGRenderSupport::isOverflowHidden(*viewportContainer)) { |
| clipSaver.save(); |
| auto clipOffset = viewportContainer->isTransformed() ? LayoutPoint() : paintOffset; |
| context.clip(FloatRect(static_cast<const RenderSVGModelObject&>(*viewportContainer).overflowClipRect(clipOffset))); |
| } |
| } |
| |
| Vector<SVGPaintOrderLayerItem> sortedChildren; |
| for (CheckedRef child : childrenOfType<RenderElement>(container)) { |
| if (child->isRenderSVGHiddenContainer()) |
| continue; |
| if (child->style().display() == Style::DisplayType::None) |
| continue; |
| |
| if (child->hasSelfPaintingLayer()) { |
| CheckedRef layerModelObject = downcast<RenderLayerModelObject>(child.get()); |
| if (CheckedPtr childLayer = layerModelObject->layer()) { |
| sortedChildren.append(SVGPaintOrderLayerItem::makeLayered(child.get(), *childLayer, childLayer->zIndex())); |
| continue; |
| } |
| } |
| sortedChildren.append(SVGPaintOrderLayerItem::makeAtomic(child.get(), { })); |
| } |
| |
| std::stable_sort(sortedChildren.begin(), sortedChildren.end(), |
| [](const SVGPaintOrderLayerItem& a, const SVGPaintOrderLayerItem& b) { |
| return a.zIndex < b.zIndex; |
| }); |
| |
| for (auto& entry : sortedChildren) { |
| CheckedRef child = *entry.renderer; |
| |
| if (child->isTransformed()) { |
| paintRendererByApplyingTransformForSVG(context, child, toLayoutSize(paintOffset), paintingInfo, paintFlags, |
| paintBehavior, subtreePaintRoot); |
| continue; |
| } |
| |
| if (child->hasSelfPaintingLayer()) { |
| CheckedRef layerModelObject = downcast<RenderLayerModelObject>(child.get()); |
| CheckedPtr childLayer = layerModelObject->layer(); |
| |
| auto adjustedFlags = paintFlags; |
| adjustedFlags.remove(PaintLayerFlag::PaintingOverflowContents); |
| |
| RenderLayer::LayerPaintingInfo childPaintingInfo(paintingInfo); |
| |
| // Inside a resource buffer the transform-scope paint offset is this child's resource-layer |
| // correction (see paintResourceCorrectedChildLayerForSVG). |
| auto correction = isPaintingResourceLayerForSVG() ? toLayoutSize(paintOffset) : LayoutSize(); |
| paintResourceCorrectedChildLayerForSVG(context, *childLayer, childPaintingInfo, adjustedFlags, correction); |
| continue; |
| } |
| |
| // Non-layer, non-transformed child. |
| auto adjustedPaintOffset = paintOffset; |
| if (CheckedPtr childSvgModel = dynamicDowncast<RenderSVGModelObject>(child.get())) |
| adjustedPaintOffset.moveBy(childSvgModel->currentSVGLayoutLocation()); |
| |
| bool isCollectingEventRegion = paintFlags.contains(PaintLayerFlag::CollectingEventRegion); |
| { |
| SVGNonLayerClippingAndMaskingScope clippingAndMaskingScope(context, child.get(), toLayoutSize(paintOffset), paintingInfo, paintBehavior, subtreePaintRoot, *this, isCollectingEventRegion); |
| if (child->isRenderSVGContainer()) { |
| paintSubtreeWithinTransformScopeForSVG(context, child.get(), adjustedPaintOffset, paintingInfo, paintFlags, paintBehavior, subtreePaintRoot); |
| |
| PaintInfo outlinePaintInfo(context, paintingInfo.paintDirtyRect, PaintPhase::SelfOutline, paintBehavior, subtreePaintRoot, |
| nullptr, nullptr, &paintingInfo.rootLayer->renderer(), this, |
| paintingInfo.requireSecurityOriginAccessForWidgets); |
| child->paint(outlinePaintInfo, paintOffset); |
| } else { |
| LayoutRect dirtyRect = paintingInfo.paintDirtyRect; |
| PaintInfo paintInfo(context, dirtyRect, PaintPhase::Foreground, paintBehavior, subtreePaintRoot, |
| nullptr, nullptr, &paintingInfo.rootLayer->renderer(), this, |
| paintingInfo.requireSecurityOriginAccessForWidgets); |
| child->paint(paintInfo, paintOffset); |
| |
| PaintInfo outlinePaintInfo(paintInfo); |
| outlinePaintInfo.phase = PaintPhase::Outline; |
| child->paint(outlinePaintInfo, paintOffset); |
| } |
| } |
| } |
| } |
| |
| RenderLayer::HitLayer RenderLayer::hitTestChildrenInDOMOrderForSVG(RenderLayer* rootLayer, const HitTestRequest& request, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, const HitTestingTransformState* transformState, double* zOffsetForDescendants) |
| { |
| ASSERT(m_svgData); |
| auto& allChildren = childrenInDOMOrderForSVG(); |
| if (allChildren.isEmpty()) |
| return { }; |
| |
| CheckedPtr svgModelObject = dynamicDowncast<RenderSVGModelObject>(renderer()); |
| LayoutSize svgOffset = svgModelObject ? toLayoutSize(svgModelObject->nominalSVGLayoutLocation()) : LayoutSize(); |
| |
| for (int i = allChildren.size() - 1; i >= 0; --i) { |
| auto& childToPaint = allChildren[i]; |
| |
| if (CheckedPtr childLayer = childToPaint.layer.get()) { |
| auto hitLayer = childLayer->hitTestLayer(rootLayer, this, request, result, hitTestRect, hitTestLocation, false, transformState, zOffsetForDescendants); |
| if (hitLayer.layer) |
| return hitLayer; |
| continue; |
| } |
| |
| if (!childToPaint.phasesToPaint.contains(PaintPhase::Foreground)) |
| continue; |
| |
| CheckedPtr childRendererPtr = childToPaint.renderer.get(); |
| if (!childRendererPtr) |
| continue; |
| CheckedRef childRenderer = *childRendererPtr; |
| |
| if (childRenderer->isTransformed()) { |
| auto hitLayer = hitTestRendererByInversingTransformForSVG(childRenderer.get(), childToPaint.accumulatedAncestorOffset, request, result, hitTestRect, hitTestLocation); |
| if (hitLayer.layer) |
| return hitLayer; |
| continue; |
| } |
| |
| LayoutPoint accumulatedOffset(svgOffset); |
| accumulatedOffset += childToPaint.accumulatedAncestorOffset; |
| |
| if (childRenderer->nodeAtPoint(request, result, hitTestLocation, accumulatedOffset, HitTestAction::Foreground)) |
| return { this, 0 }; |
| } |
| |
| return { }; |
| } |
| |
| RenderLayer::HitLayer RenderLayer::hitTestRendererByInversingTransformForSVG(RenderElement& rendererToTest, |
| const LayoutSize& positionOffset, const HitTestRequest& request, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation) |
| { |
| auto transformResult = computeRendererTransformForSVG(rendererToTest, positionOffset); |
| if (!transformResult) |
| return { }; |
| |
| auto inverse = transformResult->transform.inverse(); |
| if (!inverse) |
| return { }; |
| |
| auto localPoint = inverse->mapPoint(hitTestLocation.point()); |
| auto localRect = enclosingLayoutRect(inverse->mapRect(FloatRect(hitTestRect))); |
| |
| HitTestLocation localLocation { LayoutPoint { localPoint } }; |
| |
| if (rendererToTest.isRenderSVGContainer()) { |
| LayoutPoint accumulatedOffset; |
| if (auto* viewportContainer = dynamicDowncast<RenderSVGViewportContainer>(rendererToTest); !viewportContainer || !viewportContainer->isAnonymous()) { |
| if (auto* svgModel = dynamicDowncast<RenderSVGModelObject>(rendererToTest)) |
| accumulatedOffset = svgModel->nominalSVGLayoutLocation(); |
| } |
| return hitTestSubtreeWithinTransformScopeForSVG(rendererToTest, accumulatedOffset, request, result, localRect, localLocation); |
| } |
| |
| LayoutPoint accumulatedOffset; |
| if (CheckedPtr parentRenderer = rendererToTest.parent()) { |
| if (CheckedPtr parentSvgModel = dynamicDowncast<RenderSVGModelObject>(*parentRenderer)) |
| accumulatedOffset = parentSvgModel->nominalSVGLayoutLocation(); |
| } |
| if (rendererToTest.nodeAtPoint(request, result, localLocation, accumulatedOffset, HitTestAction::Foreground)) |
| return { this, 0 }; |
| return { }; |
| } |
| |
| RenderLayer::HitLayer RenderLayer::hitTestSubtreeWithinTransformScopeForSVG(RenderElement& container, |
| const LayoutPoint& accumulatedOffset, const HitTestRequest& request, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation) |
| { |
| for (CheckedPtr child = container.lastChild(); child; child = child->previousSibling()) { |
| CheckedPtr childElement = dynamicDowncast<RenderElement>(child.get()); |
| if (!childElement) |
| continue; |
| |
| if (childElement->isRenderSVGHiddenContainer() || childElement->style().display() == Style::DisplayType::None) |
| continue; |
| |
| if (childElement->isTransformed()) { |
| if (auto hitLayer = hitTestRendererByInversingTransformForSVG(*childElement, toLayoutSize(accumulatedOffset), request, result, hitTestRect, hitTestLocation); hitLayer.layer) |
| return hitLayer; |
| continue; |
| } |
| |
| if (childElement->hasSelfPaintingLayer()) { |
| if (childElement->nodeAtPoint(request, result, hitTestLocation, accumulatedOffset, HitTestAction::Foreground)) |
| return { this, 0 }; |
| continue; |
| } |
| |
| auto adjustedOffset = accumulatedOffset; |
| if (CheckedPtr childSvgModel = dynamicDowncast<RenderSVGModelObject>(*childElement)) |
| adjustedOffset.moveBy(childSvgModel->currentSVGLayoutLocation()); |
| |
| if (childElement->isRenderSVGContainer()) { |
| if (CheckedPtr viewportContainer = dynamicDowncast<RenderSVGViewportContainer>(*childElement)) { |
| if (SVGRenderSupport::isOverflowHidden(*viewportContainer) && !viewportContainer->viewport().contains(hitTestLocation.point())) |
| continue; |
| } |
| if (auto hitLayer = hitTestSubtreeWithinTransformScopeForSVG(*childElement, adjustedOffset, request, result, hitTestRect, hitTestLocation); hitLayer.layer) |
| return hitLayer; |
| } else { |
| if (childElement->nodeAtPoint(request, result, hitTestLocation, accumulatedOffset, HitTestAction::Foreground)) |
| return { this, 0 }; |
| } |
| } |
| |
| return { }; |
| } |
| |
| } // namespace WebCore |