You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Chrome has had **no native way** to read or write individual SVG path segments since 2015. This explainer proposes adding `getPathData()`, `setPathData()`, and `getPathSegmentAtLength()` to `SVGPathElement`, giving developers structured access to path segments as simple `{type, values}` objects.
42
+
Chrome has had **no native way** to read or write individual SVG path segments since Chrome 48 (early 2016). This explainer proposes adding `getPathData()`, `setPathData()`, and `getPathSegmentAtLength()` to `SVGPathElement`, giving developers structured access to path segments as simple `{type, values}` objects.
41
43
42
-
The API is specified in the [SVG Paths](https://svgwg.org/specs/paths/#DOMInterfaces) W3C Editor's Draft and has shipped in **Firefox 137+** (Jan 2025). This implements an existing consensus standard - no new web platform concepts are introduced.
44
+
The API is specified in the [SVG Paths](https://svgwg.org/specs/paths/#DOMInterfaces) W3C Editor's Draft and shipped in **Firefox 137+** (Apr 2025). This explainer proposes implementing an existing consensus standard - no new web platform concepts are introduced.
43
45
44
46
---
45
47
@@ -51,9 +53,9 @@ Chromium removed the old `SVGPathSegList` API in **Chrome 48** (Jan 2016) becaus
51
53
52
54
| Engine | Old API (`SVGPathSegList`) | New API (`getPathData`/`setPathData`) |
| Chrome | ❌ Removed Jan 2016 | ❌ Not implemented|
57
+
| Firefox | ❌ Removed 2018 | ✅ Shipped Apr 2025 |
58
+
| Safari | ✅ Still supported | ❌ Not implemented|
57
59
58
60
**Who is affected:** End users of SVG-heavy web apps (slower load times due to polyfills); data visualization developers (D3.js path morphing); SVG editor developers (Boxy SVG, SVG-Edit); animation developers (path interpolation).
59
61
@@ -90,9 +92,7 @@ No formal study was conducted, but 10 years of organic feedback on [crbug.com/40
90
92
91
93
## Proposed Approach
92
94
93
-
**Dependencies on non-stable features:** None.
94
-
95
-
Three methods are added to `SVGPathElement`, using simple `{type, values}` plain objects:
95
+
Three methods are added to `SVGPathElement`, using simple `{type, values}` plain objects (no dependencies on non-stable features):
96
96
97
97
#### `getPathData(settings)` - read segments
98
98
@@ -117,8 +117,11 @@ path.setPathData([
117
117
{type:"Z", values: []}
118
118
]);
119
119
120
-
// Passing an empty array clears the path (equivalent to setAttribute('d', ''))
120
+
// Passing an empty array clears the path: sets d="" (equivalent to setAttribute('d', ''),
121
+
// NOT removeAttribute('d') - the attribute remains present but empty). Matches Firefox.
121
122
path.setPathData([]);
123
+
// getPathData() on an empty/cleared path returns []
124
+
emptyPath.getPathData(); // → []
122
125
```
123
126
124
127
#### `getPathSegmentAtLength(distance)` - segment at distance
// Distances exceeding getTotalLength() clamp to the path's total length (returns last segment),
143
+
// matching getPointAtLength() clamping behavior per the SVG spec.
144
+
path.getPathSegmentAtLength(99999); // → last segment (e.g. {type: "Z", values: []})
138
145
```
139
146
140
147
All 20 SVG path commands (M, m, L, l, H, h, V, v, C, c, S, s, Q, q, T, t, A, a, Z, z) are supported. See the [spec](https://svgwg.org/specs/paths/#DOMInterfaces) for the full type/values mapping.
141
148
142
149
**Normalization** (`{normalize: true}`) converts all segments to absolute **M, L, C, Z** only - relative to absolute, H/V to L, Q/T to C, S to C, A to C. Consumers need only handle 4 command types.
143
150
144
-
> **Note:** Arc-to-cubic conversion (A → C) is an approximation using midpoint subdivision and is inherently lossy. The precision matches the existing `getTotalLength()`/`getPointAtLength()` code path in Blink. For most use cases the approximation error is sub-pixel.
151
+
> **Note:** Arc-to-cubic conversion (A → C) is an approximation using midpoint subdivision and is inherently lossy (see [W3C SVG Implementation Notes](https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter)). The precision matches the existing `getTotalLength()`/`getPointAtLength()` code path in Blink. For most use cases the approximation error is sub-pixel. Quadratic-to-cubic conversion (Q → C) is mathematically exact - every quadratic Bézier can be exactly represented as a cubic Bézier.
145
152
146
153
### Before and after
147
154
@@ -163,6 +170,9 @@ path.setPathData(segments);
163
170
### Example: path morphing
164
171
165
172
```js
173
+
// Prerequisite: both paths must have the same number and type of segments
174
+
// after normalization. This is a fundamental limitation of simple interpolation,
175
+
// not specific to this API.
166
176
constsegA=pathA.getPathData({normalize:true});
167
177
constsegB=pathB.getPathData({normalize:true});
168
178
constinterpolate= (t) =>segA.map((s, i) => ({
@@ -178,11 +188,11 @@ The formal WebIDL is in the [Appendix](#appendix-webidl).
178
188
179
189
## Key Design Decisions
180
190
181
-
1.**Plain objects, not class instances.** We use a WebIDL `dictionary`, so `setPathData()` accepts plain `{type, values}` POJOs natively. Firefox initially required interface instances (Firefox 137), which caused polyfill compatibility issues, and later [updated](https://bugzilla.mozilla.org/show_bug.cgi?id=1954044) to accept plain objects in Firefox 138. Using a dictionary from the start avoids this.
191
+
1.**Plain objects, not class instances.** We use a WebIDL `dictionary`, so `setPathData()` accepts plain `{type, values}` POJOs natively. The SVG WG confirmed this approach in [w3c/svgwg#1082](https://github.com/w3c/svgwg/issues/1082). Firefox initially required interface instances (Firefox 137), which caused polyfill compatibility issues, and later [updated](https://bugzilla.mozilla.org/show_bug.cgi?id=1954044) to accept plain objects in Firefox 138. Using a dictionary from the start avoids this.
182
192
183
193
2.**`unrestricted float` for values.** NaN/Infinity are accepted without throwing, matching SVG's graceful error model and Firefox's behavior.
184
194
185
-
3.**Invalid segments silently skipped.**Unrecognized types or wrong value counts in `setPathData()`are skipped (not thrown), matching SVG's "render what you can" model, Firefox, and the polyfill.
195
+
3.**Two-level validation in `setPathData()`.**WebIDL enforces structural validity: both `type` and `values` must be present, or a `TypeError` is thrown (e.g., `setPathData([{}])`or `setPathData([{type: "L"}])` throws). Semantic validation is lenient: unrecognized type strings or incorrect `values` array lengths cause the segment to be silently skipped - not thrown - matching SVG's "render what you can" model, Firefox, and the polyfill.
186
196
187
197
4.**Returns base value, not animated value.**`getPathData()` returns the `d` attribute's base value, consistent with `getAttribute('d')` and Firefox.
188
198
@@ -205,19 +215,18 @@ The formal WebIDL is in the [Appendix](#appendix-webidl).
205
215
-**Accessibility:** No impact. Programmatic API only - no new visual content, interaction patterns, or ARIA roles. Indirectly benefits a11y by making it easier to build well-structured SVG.
206
216
-**Internationalization:** No impact. Path data uses single-character Latin commands and numbers only.
207
217
-**Privacy:** No new concerns. Returns the same data available via `getAttribute('d')` - purely a convenience API over existing capabilities. No fingerprinting surface, no network requests.
208
-
-**Security:** No new concerns. Operates entirely within the renderer on already-structured data. No string parsing is needed (segments are pre-typed), reducing attack surface compared to `setAttribute('d')`. No IPC. Gated behind a feature flag.
218
+
-**Security:** No new concerns. Operates entirely within the renderer on already-structured data. `setPathData()` operates on structured `{type, values}` dictionaries - no string parsing is needed (segments are pre-typed), reducing attack surface compared to `setAttribute('d')`. No additional IPC beyond existing DOM access. Gated behind a Blink `RuntimeEnabledFeature` (`SVGPathDataAPI`).
|**Safari/WebKit**| No signal | Still ships old API; [removal bug](https://bugs.webkit.org/show_bug.cgi?id=260894) open (TODO: file WebKit standards position request) |
|**SVG WG**| ✅ Positive | API in [consensus spec](https://svgwg.org/specs/paths/#DOMInterfaces); dictionary approach confirmed in [w3c/svgwg#1082](https://github.com/w3c/svgwg/issues/1082)|
221
230
222
231
---
223
232
@@ -227,14 +236,35 @@ The formal WebIDL is in the [Appendix](#appendix-webidl).
**Acknowledgements:** Fredrik Söderquist (fs@opera.com, original API sketch author, SVG OWNERS), Philip Rogers (pdr@chromium.org, drove SVGPathSegList removal, pathseg polyfill), Robert Longson (Mozilla SVG lead, Firefox implementation), Jarek Foksa (path-data-polyfill author), Cameron McCormack (spec editor).
235
244
236
245
---
237
246
247
+
## Testing
248
+
249
+
**Existing WPTs:** Firefox landed web-platform-tests alongside their implementation in [svg/path/interfaces/](https://wpt.fyi/results/svg/path/interfaces?label=experimental&label=master&aligned), including `SVGPathSegment.svg` which covers `getPathData()`, `setPathData()`, `getPathSegmentAtLength()`, normalization, and basic command coverage.
- POJO acceptance: plain `{type, values}` objects work without constructors
255
+
- Two-level validation: TypeError for missing required fields vs silent skip for semantic errors
256
+
- Blink layout tests for rendering integration
257
+
258
+
---
259
+
260
+
## Implementation Notes
261
+
262
+
**Feature flag:** This API will be gated behind a Blink `RuntimeEnabledFeature` named `SVGPathDataAPI`. It will not have a separate `chrome://flags` entry - it follows the standard Blink shipping process (flag → origin trial → ship).
263
+
264
+
**UseCounters:** The implementation will include UseCounters for each method (`getPathData`, `setPathData`, `getPathSegmentAtLength`) to track adoption and inform the ship decision. No existing UseCounter data is available since the API does not yet exist in Blink.
0 commit comments