Skip to content

Commit 588800b

Browse files
author
Virali Purbey
committed
addressed comments
1 parent 85d6098 commit 588800b

File tree

1 file changed

+51
-21
lines changed

1 file changed

+51
-21
lines changed

SVG/SVGPathDataAPI/explainer.md

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# SVG Path Data API
22

3-
**Written:** 2026-03-30, **Updated:** 2026-03-30
3+
**Written:** 2026-03-30, **Updated:** 2026-04-01
44

55
## Authors
66

@@ -31,15 +31,17 @@ This document is an **in-progress** explainer.
3131
- [Accessibility, Internationalization, Privacy, and Security Considerations](#accessibility-internationalization-privacy-and-security-considerations)
3232
- [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition)
3333
- [References & Acknowledgements](#references--acknowledgements)
34+
- [Testing](#testing)
35+
- [Implementation Notes](#implementation-notes)
3436
- [Appendix: WebIDL](#appendix-webidl)
3537

3638
---
3739

3840
## Introduction
3941

40-
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.
4143

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.
4345

4446
---
4547

@@ -51,9 +53,9 @@ Chromium removed the old `SVGPathSegList` API in **Chrome 48** (Jan 2016) becaus
5153

5254
| Engine | Old API (`SVGPathSegList`) | New API (`getPathData`/`setPathData`) |
5355
|--------|---------------------------|---------------------------------------|
54-
| Chrome | ❌ Removed Jan 2016 | ❌ Not yet |
55-
| Firefox | ❌ Removed 2018 | ✅ Shipped Jan 2025 |
56-
| Safari | ✅ Still supported | ❌ Not yet |
56+
| Chrome | ❌ Removed Jan 2016 | ❌ Not implemented |
57+
| Firefox | ❌ Removed 2018 | ✅ Shipped Apr 2025 |
58+
| Safari | ✅ Still supported | ❌ Not implemented |
5759

5860
**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).
5961

@@ -90,9 +92,7 @@ No formal study was conducted, but 10 years of organic feedback on [crbug.com/40
9092

9193
## Proposed Approach
9294

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):
9696

9797
#### `getPathData(settings)` - read segments
9898

@@ -117,8 +117,11 @@ path.setPathData([
117117
{type: "Z", values: []}
118118
]);
119119

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.
121122
path.setPathData([]);
123+
// getPathData() on an empty/cleared path returns []
124+
emptyPath.getPathData(); // → []
122125
```
123126

124127
#### `getPathSegmentAtLength(distance)` - segment at distance
@@ -135,13 +138,17 @@ path.getPathSegmentAtLength(-10); // → {type: "M", values: [10, 80]}
135138

136139
// NaN returns null
137140
path.getPathSegmentAtLength(NaN); // → null
141+
142+
// 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: []})
138145
```
139146

140147
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.
141148

142149
**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.
143150

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.
145152
146153
### Before and after
147154

@@ -163,6 +170,9 @@ path.setPathData(segments);
163170
### Example: path morphing
164171

165172
```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.
166176
const segA = pathA.getPathData({normalize: true});
167177
const segB = pathB.getPathData({normalize: true});
168178
const interpolate = (t) => segA.map((s, i) => ({
@@ -178,11 +188,11 @@ The formal WebIDL is in the [Appendix](#appendix-webidl).
178188

179189
## Key Design Decisions
180190

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.
182192

183193
2. **`unrestricted float` for values.** NaN/Infinity are accepted without throwing, matching SVG's graceful error model and Firefox's behavior.
184194

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.
186196

187197
4. **Returns base value, not animated value.** `getPathData()` returns the `d` attribute's base value, consistent with `getAttribute('d')` and Firefox.
188198

@@ -205,19 +215,18 @@ The formal WebIDL is in the [Appendix](#appendix-webidl).
205215
- **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.
206216
- **Internationalization:** No impact. Path data uses single-character Latin commands and numbers only.
207217
- **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`).
209219

210220
---
211221

212222
## Stakeholder Feedback / Opposition
213223

214224
| Stakeholder | Signal | Evidence |
215225
|---|---|---|
216-
| **Firefox** | ✅ Positive | Shipped [Firefox 137](https://bugzilla.mozilla.org/show_bug.cgi?id=1934525) (Jan 2025); [POJO fix](https://bugzilla.mozilla.org/show_bug.cgi?id=1954044) in 138 |
217-
| **Safari/WebKit** | No signal | Still ships old API; [removal bug](https://bugs.webkit.org/show_bug.cgi?id=260894) open |
226+
| **Firefox** | ✅ Positive | Shipped [Firefox 137](https://bugzilla.mozilla.org/show_bug.cgi?id=1934525) (Apr 2025); [POJO fix](https://bugzilla.mozilla.org/show_bug.cgi?id=1954044) in 138 |
227+
| **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) |
218228
| **Web developers** | ✅ Strongly positive | 45 upvotes, 31 comments, enterprise breakage reports, 129+ polyfill stars |
219-
| **SVG WG** | ✅ Positive | API in [consensus spec](https://svgwg.org/specs/paths/#DOMInterfaces) |
220-
| **fs@opera.com** | ✅ Positive | Filed original bug; confirmed dictionary approach |
229+
| **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) |
221230

222231
---
223232

@@ -227,14 +236,35 @@ The formal WebIDL is in the [Appendix](#appendix-webidl).
227236

228237
**Bugs:** [Chromium 40441025](https://issues.chromium.org/issues/40441025) · [Firefox 1934525](https://bugzilla.mozilla.org/show_bug.cgi?id=1934525) · [Firefox 1954044](https://bugzilla.mozilla.org/show_bug.cgi?id=1954044) · [WebKit 260894](https://bugs.webkit.org/show_bug.cgi?id=260894)
229238

230-
**Discussions:** [w3c/editing#483](https://github.com/w3c/editing/issues/483) · [w3c/svgwg#974](https://github.com/w3c/svgwg/issues/974)
239+
**Discussions:** [w3c/editing#483](https://github.com/w3c/editing/issues/483) · [w3c/svgwg#974](https://github.com/w3c/svgwg/issues/974) · [w3c/svgwg#1082](https://github.com/w3c/svgwg/issues/1082) (dictionary resolution)
231240

232-
**Prior art:** [path-data-polyfill](https://github.com/jarek-foksa/path-data-polyfill) (129+ stars) · [pathseg polyfill](https://github.com/progers/pathseg) · [Interop hotlist](https://issues.chromium.org/hotlists/5575920)
241+
**Prior art:** [path-data-polyfill](https://github.com/jarek-foksa/path-data-polyfill) (129+ stars) · [pathseg polyfill](https://github.com/progers/pathseg) · [Interop hotlist](https://issues.chromium.org/hotlists/5575920) (Chromium cross-browser interop tracking; includes [crbug.com/40441025](https://issues.chromium.org/issues/40441025))
233242

234243
**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).
235244

236245
---
237246

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.
250+
251+
**Planned additional tests:**
252+
- Edge cases: empty paths, NaN/Infinity values, distance > totalLength clamping, negative distance clamping
253+
- Normalization accuracy: arc-to-cubic precision, quadratic-to-cubic exactness
254+
- 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.
265+
266+
---
267+
238268
## Appendix: WebIDL
239269

240270
```webidl
@@ -254,4 +284,4 @@ partial interface SVGPathElement {
254284
};
255285
```
256286

257-
**Spec text updates (spec PR to be filed):** `dictionary` instead of `[NoInterfaceObject] interface` (accepts POJOs natively); `unrestricted float` instead of `float` (matches SVG error model); `required` keywords added (prevents `setPathData([{}])`).
287+
**Spec text updates (spec PR to be filed):** `dictionary` instead of `[NoInterfaceObject] interface` (accepts POJOs natively; WG resolution: [w3c/svgwg#1082](https://github.com/w3c/svgwg/issues/1082)); `unrestricted float` instead of `float` (matches SVG error model); `required` keywords added (prevents `setPathData([{}])`).

0 commit comments

Comments
 (0)