WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
NEW
314618
SVG text: character-data mutation on a `<tspan>` triggers O(N) re-measure of all visible siblings
https://bugs.webkit.org/show_bug.cgi?id=314618
Summary
SVG text: character-data mutation on a `<tspan>` triggers O(N) re-measure of ...
Karl Dubost
Reported
2026-05-11 23:43:50 PDT
Created
attachment 479626
[details]
benchmark Replacing the text node of a single `<tspan>` inside a `<text>` element forces the renderer to re-measure every visible `RenderSVGInlineText` in the subtree, not just the one whose character data actually changed. Cost grows linearly with the number of visible siblings. Blink and Gecko don't have this scaling — they're 3× and 11× faster respectively on the same workload. Steps to reproduce Open the attached standalone benchmark in the browser under test, click "Run benchmark", wait ~30s for the sweep over N visible tspans in {5, 10, 25, 50, 100, 200}. Each iteration is `tspan.replaceChild(newTextNode, oldTextNode)` + a forced layout (`getBoundingClientRect`). ### Measurements (2026-05-12, macOS, same page, same run) | Engine | ms / tspan (slope) | ms @ N=200 | vs Safari | | --- | --- | --- | --- | | Safari (WebKit) | 0.0281 | 5.75 | 1.0× | | Chrome (Blink) | 0.0092 | 1.84 | 3.1× faster | | Firefox (Gecko) | 0.0025 | 0.65 | 11.2× faster | Raw Safari data (median ms/iter across 3 repeats of 80 iterations, 10-iter warmup): ``` visible tspans median min max 5 0.263 0.263 0.263 10 0.400 0.400 0.412 25 0.825 0.813 0.825 50 1.575 1.525 1.575 100 2.925 2.900 2.938 200 5.750 5.687 5.775 linear fit: 0.0281 ms / tspan + 0.129 ms fixed overhead ``` Linear fit quality is excellent in all three engines — the algorithmic shape (O(N) in visible siblings) is the same everywhere; WebKit's constant factor is the outlier. `RenderSVGInlineText::setTextInternal()` calls `RenderSVGText::subtreeTextDidChange()` (`Source/WebCore/rendering/svg/RenderSVGInlineText.cpp:107`), which sets `m_needsPositioningValuesUpdate` and marks the `<text>` renderer for layout. In `RenderSVGText::layout()` (`Source/WebCore/rendering/svg/RenderSVGText.cpp:345-354`) that flag triggers: ```cpp m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(*this); ``` which calls `SVGTextMetricsBuilder::walkTree(textRoot, /*stopAtLeaf=*/nullptr, data)` — a full preorder walk that re-measures every `RenderSVGInlineText` in the subtree. The existing `stopAtLeaf` parameter shows the infrastructure for scoped measurement is already in the API, but isn't plumbed through this path. The file even carries an explicit FIXME acknowledging the issue at `Source/WebCore/rendering/svg/SVGTextMetricsBuilder.cpp:174`:
> FIXME: This function is called even though width information is not changed at all. `RenderSVGText` / `RenderSVGInlineText` should track the potential changes to width etc. and invoke this function only when it is actually changed.
### History The 2012 fix for
bug 65711
(
r104683
–
r105143
, Niko Zimmermann) introduced the per-renderer `SVGTextLayoutAttributes` architecture and fixed add/remove paths via `subtreeChildWasAdded` / `subtreeChildWillBeRemoved` (only rebuilds prev/current/next). The character-data-change path (`subtreeTextDidChange`) was left on the full-subtree rebuild path. Blink forked post-2012 and has since rewritten SVG text layout on top of LayoutNG, which appears to have addressed this. WebKit still runs on the legacy `SVGTextLayoutEngine` / `SVGTextMetricsBuilder` pipeline. ### Suggested direction (not prescriptive) When `subtreeTextDidChange(text)` fires, only the passed `RenderSVGInlineText` and its immediate previous/next siblings should need remeasurement (same scope as `subtreeChildWasAdded`). The `stopAtLeaf` plumbing already exists in `SVGTextMetricsBuilder::walkTree`; the question is whether the x/y/dx/dy/rotate list redistribution across the subtree can be made incremental, or whether the character-data-change path can skip that redistribution entirely when the list shapes haven't changed. Matching Blink's slope (3× improvement) looks like a reasonable first milestone without needing a ground-up rewrite. -
bug 65711
— the 2011 regression this code path originally fixed; propose closing as RESOLVED FIXED since the original <1 FPS regression is long gone and REOPENED was for a long-dead Qt assertion. -
bug 19118
— general "SVG text perf" umbrella.
Attachments
benchmark
(8.99 KB, text/html)
2026-05-11 23:43 PDT
,
Karl Dubost
no flags
Details
View All
Add attachment
proposed patch, testcase, etc.
Radar WebKit Bug Importer
Comment 1
2026-05-18 23:44:11 PDT
<
rdar://problem/177409377
>
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug