Skip to content

Commit fc5acfd

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

File tree

1 file changed

+53
-53
lines changed

1 file changed

+53
-53
lines changed

SVG/SVGPathDataAPI/explainer.md

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

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

55
## Authors
66

77
- Virali Purbey (viralipurbey@microsoft.com)
88

99
## Status of this Document
1010

11-
This document is an **in-progress** explainer.
11+
This document is a **short explainer** for an implementation of an existing consensus standard ([SVG Paths §7 DOM Interfaces](https://svgwg.org/specs/paths/#DOMInterfaces)). No new web platform concepts are introduced. This explainer captures developer benefit, key implementation decisions, and Chromium-specific shipping details; it is intentionally concise since the API was designed by the SVG WG, not the authors of this document.
1212

1313
## Participate
1414

@@ -24,22 +24,21 @@ This document is an **in-progress** explainer.
2424
- [User-Facing Problem](#user-facing-problem)
2525
- [Goals](#goals)
2626
- [Non-Goals](#non-goals)
27-
- [User Research](#user-research)
2827
- [Proposed Approach](#proposed-approach)
2928
- [Key Design Decisions](#key-design-decisions)
3029
- [Alternatives Considered](#alternatives-considered)
3130
- [Accessibility, Internationalization, Privacy, and Security Considerations](#accessibility-internationalization-privacy-and-security-considerations)
3231
- [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition)
3332
- [References & Acknowledgements](#references--acknowledgements)
33+
- [Testing](#testing)
34+
- [Implementation Notes](#implementation-notes)
3435
- [Appendix: WebIDL](#appendix-webidl)
3536

3637
---
3738

3839
## Introduction
3940

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.
41-
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.
41+
Chrome has had **no native way** to read or write individual SVG path segments since Chrome 48 (early 2016). This explainer proposes implementing `getPathData()`, `setPathData()`, and `getPathSegmentAtLength()` on `SVGPathElement`, as specified in the [SVG Paths](https://svgwg.org/specs/paths/#DOMInterfaces) W3C Editor's Draft and already shipped in **Firefox 137+** (Apr 2025).
4342

4443
---
4544

@@ -51,14 +50,16 @@ Chromium removed the old `SVGPathSegList` API in **Chrome 48** (Jan 2016) becaus
5150

5251
| Engine | Old API (`SVGPathSegList`) | New API (`getPathData`/`setPathData`) |
5352
|--------|---------------------------|---------------------------------------|
54-
| Chrome | ❌ Removed Jan 2016 | ❌ Not yet |
55-
| Firefox | ❌ Removed 2018 | ✅ Shipped Jan 2025 |
56-
| Safari | ✅ Still supported | ❌ Not yet |
53+
| Chrome | ❌ Removed Jan 2016 | ❌ Not implemented |
54+
| Firefox | ❌ Removed 2018 | ✅ Shipped Apr 2025 |
55+
| Safari | ✅ Still supported | ❌ Not implemented |
5756

5857
**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).
5958

6059
**Current workarounds:** [path-data-polyfill](https://github.com/jarek-foksa/path-data-polyfill) (129+ stars, de facto standard), manual `d` string parsing, and [pathseg polyfill](https://github.com/progers/pathseg). All are slower than native and add unnecessary JS weight.
6160

61+
**Developer demand:** [crbug.com/40441025](https://issues.chromium.org/issues/40441025) has **45 upvotes** and **31 comments** from enterprise developers and library authors over 10 years. Five Sheriffbot closure attempts (2017-2021) were each reopened.
62+
6263
---
6364

6465
## Goals
@@ -78,21 +79,9 @@ Chromium removed the old `SVGPathSegList` API in **Chrome 48** (Jan 2016) becaus
7879

7980
---
8081

81-
## User Research
82-
83-
No formal study was conducted, but 10 years of organic feedback on [crbug.com/40441025](https://issues.chromium.org/issues/40441025) provides helpful signal: **45 upvotes**, **31 comments** (enterprise developers, library authors), **129+ GitHub stars** on the polyfill, and **5 Sheriffbot closure attempts** survived (2017-2021, each reopened by fs@opera.com).
84-
85-
> *"In our B2B solution for glasses design we have round about 1000 users which can not work since the last Google Chrome update."* - Jan 2016
86-
87-
> *"We'll soon celebrate the 10th anniversary of this issue. It's... a long time."* - Jun 2025
88-
89-
---
90-
9182
## Proposed Approach
9283

93-
**Dependencies on non-stable features:** None.
94-
95-
Three methods are added to `SVGPathElement`, using simple `{type, values}` plain objects:
84+
Three methods are added to `SVGPathElement`, using simple `{type, values}` plain objects (no dependencies on non-stable features):
9685

9786
#### `getPathData(settings)` - read segments
9887

@@ -117,8 +106,11 @@ path.setPathData([
117106
{type: "Z", values: []}
118107
]);
119108

120-
// Passing an empty array clears the path (equivalent to setAttribute('d', ''))
109+
// Passing an empty array clears the path: sets d="" (equivalent to setAttribute('d', ''),
110+
// NOT removeAttribute('d') - the attribute remains present but empty). Matches Firefox.
121111
path.setPathData([]);
112+
// getPathData() on an empty/cleared path returns []
113+
emptyPath.getPathData(); // → []
122114
```
123115

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

136128
// NaN returns null
137129
path.getPathSegmentAtLength(NaN); // → null
130+
131+
// Distances exceeding getTotalLength() clamp to the path's total length (returns last segment),
132+
// matching getPointAtLength() clamping behavior per the SVG spec.
133+
path.getPathSegmentAtLength(99999); // → last segment (e.g. {type: "Z", values: []})
138134
```
139135

140136
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.
141137

142138
**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.
143139

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.
140+
> **Note:** Arc-to-cubic conversion (A → C) is an approximation (inherently lossy); quadratic-to-cubic (Q → C) is exact. Precision details will be in the design doc.
145141
146142
### Before and after
147143

@@ -160,43 +156,27 @@ segments[1].values[0] = 50;
160156
path.setPathData(segments);
161157
```
162158

163-
### Example: path morphing
164-
165-
```js
166-
const segA = pathA.getPathData({normalize: true});
167-
const segB = pathB.getPathData({normalize: true});
168-
const interpolate = (t) => segA.map((s, i) => ({
169-
type: s.type,
170-
values: s.values.map((v, j) => v + (segB[i].values[j] - v) * t)
171-
}));
172-
pathTarget.setPathData(interpolate(0.5));
173-
```
174-
175159
The formal WebIDL is in the [Appendix](#appendix-webidl).
176160

177161
---
178162

179163
## Key Design Decisions
180164

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.
165+
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.
182166

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

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.
169+
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.
186170

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

189173
---
190174

191175
## Alternatives Considered
192176

193-
| Alternative | Why rejected |
194-
|---|---|
195-
| **Re-implement `SVGPathSegList`** | SVG WG removed it from SVG 2; live mutation is complex; 20+ factory methods; no modern engine adding new support ([WebKit removal bug](https://bugs.webkit.org/show_bug.cgi?id=260894)) |
196-
| **Use `interface` per spec text** | Would not accept plain objects from polyfill code; Firefox encountered this and updated to accept POJOs; spec author confirmed dictionary was the intent |
197-
| **Use `float` (not `unrestricted`)** | SVG renders degenerate paths as empty rather than erroring; would affect polyfill-based code; Firefox uses unrestricted |
198-
| **Throw on invalid segments** | Firefox and polyfill skip silently; SVG model is "render what you can" |
199-
| **Return animated value** | No use case identified; adds complexity; inconsistent with `getAttribute('d')`; Firefox returns base |
177+
The API shape was designed by the SVG WG, not the authors of this document. The main alternative - re-implementing the old `SVGPathSegList` API - was rejected by the WG because of its complexity (20+ factory methods, live mutation semantics). No modern engine is adding new `SVGPathSegList` support ([WebKit removal bug](https://bugs.webkit.org/show_bug.cgi?id=260894)).
178+
179+
Our implementation-specific choices (dictionary vs interface, `unrestricted float`, lenient validation) are documented in [Key Design Decisions](#key-design-decisions) above.
200180

201181
---
202182

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

210190
---
211191

212192
## Stakeholder Feedback / Opposition
213193

214194
| Stakeholder | Signal | Evidence |
215195
|---|---|---|
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 |
196+
| **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 |
197+
| **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) |
218198
| **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 |
199+
| **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) |
221200

222201
---
223202

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

228207
**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)
229208

230-
**Discussions:** [w3c/editing#483](https://github.com/w3c/editing/issues/483) · [w3c/svgwg#974](https://github.com/w3c/svgwg/issues/974)
209+
**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)
231210

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)
211+
**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))
233212

234213
**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).
235214

236215
---
237216

217+
## Testing
218+
219+
**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.
220+
221+
**Planned additional tests:**
222+
- Edge cases: empty paths, NaN/Infinity values, distance > totalLength clamping, negative distance clamping
223+
- Normalization accuracy: arc-to-cubic precision, quadratic-to-cubic exactness
224+
- POJO acceptance: plain `{type, values}` objects work without constructors
225+
- Two-level validation: TypeError for missing required fields vs silent skip for semantic errors
226+
- Blink layout tests for rendering integration
227+
228+
---
229+
230+
## Implementation Notes
231+
232+
**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).
233+
234+
**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.
235+
236+
---
237+
238238
## Appendix: WebIDL
239239

240240
```webidl
@@ -254,4 +254,4 @@ partial interface SVGPathElement {
254254
};
255255
```
256256

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([{}])`).
257+
**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)