[LBSE] Defer per-element SVG transform-attribute work without style recalc
// Source/WebCore/dom/Document.cpp
+ if (kind == Style::SVGRendererUpdateType::TransformAttributeOnly) {
+ if (CheckedPtr layerRenderer = dynamicDowncast<RenderLayerModelObject>(element.renderer())) {
+ if (RefPtr frameView = view())
+ frameView->layoutContext().addPendingSVGTransformAttributeUpdate(*layerRenderer);
+ }
+ return;
+ }
LBSE (Layout-Based SVG Engine) is WebKit's rewrite of the SVG rendering backend that integrates SVG into the standard CSS layout pipeline instead of treating it as a special case. SVG elements have real RenderLayer objects, so transform changes must propagate through the same layer compositing machinery as HTML elements. Previously, every attribute mutation on an SVG transform triggered an immediate repaint/relayout cycle.
This commit replaces synchronous per-mutation calls with a batched, per-frame queue on LocalFrameViewLayoutContext. The flush runs in three phases: snapshot pre-mutation repaint rects, mutate transforms, then emit delta repaints. A dirty bit on RenderSVGContainer/RenderSVGRoot enables lazy bounding-box recomputation so getBBox() doesn't go stale without paying layout costs.
Significance
Eliminates 2N synchronous repaint() calls per frame during SVG animations, yielding ~10% MotionMark/Suits improvement. The restructuring introduces queued renderer references, new flush ordering invariants, and a new lazy-invalidation path — all fresh complexity.
Audit directions
- Flush-before-re-entrancy-check ordering. In
updateLayoutIfDimensionsOutOfDate, the queue is flushed before theisInRenderTreeLayout()guard. The three-phase flush can execute while a layout is nominally in progress from a higher frame — re-entrant geometry queries during phase 2 could observe partially-mutated transform state. - Renderer lifetime vs. queue. The queue holds
CheckedPtr<RenderLayerModelObject>references. If a renderer is detached between enqueue and flush (e.g., DOM mutation during rAF),CheckedPtrwill null out, but the flush logic must handle null entries without UB. Worth checking whether the dirty bit onRenderElementis cleared on detach. - Dirty-bit coverage for
getBBox()staleness. Any code path that mutates descendant transforms outside this queue (style recalc, SMIL engine, direct property changes) without setting the dirty bit will silently return stale bounding boxes to JS. Audit all callers of the oldrepaintOrRelayoutAfterSVGTransformChange()not migrated. - Three-phase atomicity. If a JS microtask or style callback fires between phases, the delta repaint in phase 3 could be computed against wrong geometry.