Skip to content

Commit bfc74b0

Browse files
author
Virali Purbey
committed
Add SVG Path Data API explainer
Proposes adding getPathData(), setPathData(), and getPathSegmentAtLength() to SVGPathElement, implementing the SVG Paths spec DOM interfaces. Already shipped in Firefox 137+.
1 parent c7e0e1b commit bfc74b0

File tree

2 files changed

+239
-0
lines changed

2 files changed

+239
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ we move them into the [Alumni section](#alumni-) below.
9191
| [Expose Content-Encoding in Resource Timing](ResourceTimingContentEncoding/Content_Encoding.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/Resource%20Timing%20Content%20Encoding">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/Resource%20Timing%20Content%20Encoding?label=issues)</a> | [New Issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?template=resource-timing-content-encoding.md) | Web Perf |
9292
| [Expose resource dependency in Resource Timing](ResourceTimingInitiatorInfo/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/Resource%20Timing%20Initiator%20Info">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/Resource%20Timing%20Initiator%20Info?label=issues)</a> | [New Issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?template=resource-timing-initiator-info.md) | Web Perf |
9393
| [Allow SVG `use` to reference entire files](SVG/allow-use-to-reference-entire-files.md)| <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/SVG%20Use%20reference%20entire%20file">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/SVG%20Use%20reference%20entire%20file?label=issues)</a>| [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?template=allow-use-to-reference-entire-files.md)| SVG |
94+
| [SVG Path Data API](SVG/SVGPathDataAPI/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/SVGPathDataAPI">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/SVGPathDataAPI?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?labels=SVGPathDataAPI&title=%5BSVGPathDataAPI%5D+) | SVG |
9495
| [Gamepad Event-Driven Input API](GamepadEventDrivenInputAPI/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/Gamepad%20Event-Driven%20Input%20API">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/Gamepad%20Event-Driven%20Input%20API?label=issues)</a> | [New issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?template=gamepad-event-driven-input-api.md) | Gamepad |
9596
| [JS Self-Profiling API: Conditional Marker Exposure](ConditionalMarkersExposure/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/Conditional%20Marker%20Exposure">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/Conditional%20Marker%20Exposure?label=issues)</a> | [New Issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?assignees=heathcliff-msft&labels=Conditional%20Marker%20Exposure&title=%5BConditional+Marker+Exposure%5D+%3CTITLE+HERE%3E) | Performance |
9697
| [GetSelectionBoundingClientRect()](GetSelectionBoundingClientRect/explainer.md) | <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/labels/GetSelectionBoundingClientRect">![GitHub issues by-label](https://img.shields.io/github/issues/MicrosoftEdge/MSEdgeExplainers/GetSelectionBoundingClientRect?label=issues)</a> | [New Issue...](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/new?template=getSelectionBoundingClientRect.md) | DOM |

SVG/SVGPathDataAPI/explainer.md

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# SVG Path Data API
2+
3+
**Written:** 2026-03-30, **Updated:** 2026-03-30
4+
5+
## Authors
6+
7+
- Virali Purbey (viralipurbey@microsoft.com)
8+
9+
## Status of this Document
10+
11+
This document is an **in-progress** explainer.
12+
13+
## Participate
14+
15+
- [Issue #1289](https://github.com/MicrosoftEdge/MSEdgeExplainers/issues/1289) (explainer feedback)
16+
- [Chromium bug 40441025](https://issues.chromium.org/issues/40441025) (45 upvotes, filed Oct 2015)
17+
- [SVG Paths §7 - DOM Interfaces](https://svgwg.org/specs/paths/#DOMInterfaces) (specification)
18+
- [Firefox bug 1934525](https://bugzilla.mozilla.org/show_bug.cgi?id=1934525) (implementation) · [bug 1954044](https://bugzilla.mozilla.org/show_bug.cgi?id=1954044) (POJO fix)
19+
- [w3c/editing#483](https://github.com/w3c/editing/issues/483) (POJO compat) · [w3c/svgwg#974](https://github.com/w3c/svgwg/issues/974) (constructability)
20+
21+
## Table of Contents
22+
23+
- [Introduction](#introduction)
24+
- [User-Facing Problem](#user-facing-problem)
25+
- [Goals](#goals)
26+
- [Non-Goals](#non-goals)
27+
- [User Research](#user-research)
28+
- [Proposed Approach](#proposed-approach)
29+
- [Key Design Decisions](#key-design-decisions)
30+
- [Alternatives Considered](#alternatives-considered)
31+
- [Accessibility, Internationalization, Privacy, and Security Considerations](#accessibility-internationalization-privacy-and-security-considerations)
32+
- [Stakeholder Feedback / Opposition](#stakeholder-feedback--opposition)
33+
- [References & Acknowledgements](#references--acknowledgements)
34+
- [Appendix: WebIDL](#appendix-webidl)
35+
36+
---
37+
38+
## Introduction
39+
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.
43+
44+
---
45+
46+
## User-Facing Problem
47+
48+
SVG `<path>` elements define their shape through a `d` attribute string. Today in Chrome, the only way to inspect or modify individual path segments is to parse this raw string manually or include a polyfill. This can result in slower interactions, larger page loads, and degraded UX - especially on low-end devices.
49+
50+
Chromium removed the old `SVGPathSegList` API in **Chrome 48** (2015) because it was overly complex and poorly specified. The SVG WG specified a cleaner replacement, but it has not yet been implemented in Chrome - a gap that has persisted for over 10 years.
51+
52+
| Engine | Old API (`SVGPathSegList`) | New API (`getPathData`/`setPathData`) |
53+
|--------|---------------------------|---------------------------------------|
54+
| Chrome | ❌ Removed 2015 | ❌ Not yet |
55+
| Firefox | ❌ Removed 2018 | ✅ Shipped Jan 2025 |
56+
| Safari | ✅ Still supported | ❌ Not yet |
57+
58+
**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+
60+
**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.
61+
62+
---
63+
64+
## Goals
65+
66+
1. **Restore segment-level path access** natively in Chrome.
67+
2. **Interop with Firefox** - match Firefox 137+'s shipped behavior.
68+
3. **Polyfill compatibility** - code using path-data-polyfill should work unchanged with the native API.
69+
4. **Normalization support** - `getPathData({normalize: true})` returns only absolute M, L, C, Z.
70+
71+
## Non-Goals
72+
73+
- **Restoring `SVGPathSegList`** - the old API is not being brought back.
74+
- **Path editing UI** - programmatic API only.
75+
- **Animated path data** - returns base value only, not current animated value.
76+
- **New path commands** - no Catmull-Rom (`R`) or Bearing (`B`); no browser supports them.
77+
- **`SVGPathSegment` constructor** - the SVG WG resolved that a constructor is not needed at this time; our dictionary approach aligns with this.
78+
79+
---
80+
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+
91+
## Proposed Approach
92+
93+
**Dependencies on non-stable features:** None.
94+
95+
Three methods are added to `SVGPathElement`, using simple `{type, values}` plain objects:
96+
97+
#### `getPathData(settings)` - read segments
98+
99+
```js
100+
const segments = path.getPathData();
101+
// → [{type: "M", values: [10, 80]}, {type: "C", values: [40, 10, 65, 10, 95, 80]}, ...]
102+
103+
// Normalize: all segments converted to absolute M, L, C, Z
104+
const normalized = path.getPathData({normalize: true});
105+
```
106+
107+
#### `setPathData(pathData)` - write segments (accepts POJOs)
108+
109+
```js
110+
path.setPathData([
111+
{type: "M", values: [0, 0]},
112+
{type: "L", values: [100, 0]},
113+
{type: "L", values: [50, 100]},
114+
{type: "Z", values: []}
115+
]);
116+
```
117+
118+
#### `getPathSegmentAtLength(distance)` - segment at distance
119+
120+
```js
121+
path.getPathSegmentAtLength(50);
122+
// → {type: "C", values: [40, 10, 65, 10, 95, 80]}
123+
```
124+
125+
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.
126+
127+
**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.
128+
129+
### Before and after
130+
131+
```js
132+
// BEFORE: parse d-string manually or include a polyfill
133+
const d = path.getAttribute('d');
134+
const segments = myCustomParser(d); // or load ~4KB polyfill
135+
segments[1].values[0] = 50;
136+
path.setAttribute('d', myCustomSerializer(segments));
137+
138+
// AFTER: native, zero dependencies
139+
const segments = path.getPathData();
140+
segments[1].values[0] = 50;
141+
path.setPathData(segments);
142+
```
143+
144+
### Example: path morphing
145+
146+
```js
147+
const segA = pathA.getPathData({normalize: true});
148+
const segB = pathB.getPathData({normalize: true});
149+
const interpolate = (t) => segA.map((s, i) => ({
150+
type: s.type,
151+
values: s.values.map((v, j) => v + (segB[i].values[j] - v) * t)
152+
}));
153+
pathTarget.setPathData(interpolate(0.5));
154+
```
155+
156+
The formal WebIDL is in the [Appendix](#appendix-webidl).
157+
158+
---
159+
160+
## Key Design Decisions
161+
162+
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.
163+
164+
2. **`unrestricted float` for values.** NaN/Infinity are accepted without throwing, matching SVG's graceful error model and Firefox's behavior.
165+
166+
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.
167+
168+
4. **Returns base value, not animated value.** `getPathData()` returns the `d` attribute's base value, consistent with `getAttribute('d')` and Firefox.
169+
170+
---
171+
172+
## Alternatives Considered
173+
174+
| Alternative | Why rejected |
175+
|---|---|
176+
| **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)) |
177+
| **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 |
178+
| **Use `float` (not `unrestricted`)** | SVG renders degenerate paths as empty rather than erroring; would affect polyfill-based code; Firefox uses unrestricted |
179+
| **Throw on invalid segments** | Firefox and polyfill skip silently; SVG model is "render what you can" |
180+
| **Return animated value** | No use case identified; adds complexity; inconsistent with `getAttribute('d')`; Firefox returns base |
181+
182+
---
183+
184+
## Accessibility, Internationalization, Privacy, and Security Considerations
185+
186+
- **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.
187+
- **Internationalization:** No impact. Path data uses single-character Latin commands and numbers only.
188+
- **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.
189+
- **Security:** No new concerns. Operates entirely within the renderer, no IPC, no untrusted data. `setPathData()` goes through the existing hardened `setAttribute("d")` code path. Gated behind a feature flag.
190+
191+
---
192+
193+
## Stakeholder Feedback / Opposition
194+
195+
| Stakeholder | Signal | Evidence |
196+
|---|---|---|
197+
| **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 |
198+
| **Safari/WebKit** | No signal | Still ships old API; [removal bug](https://bugs.webkit.org/show_bug.cgi?id=260894) open |
199+
| **Web developers** | ✅ Strongly positive | 45 upvotes, 31 comments, enterprise breakage reports, 129+ polyfill stars |
200+
| **SVG WG** | ✅ Positive | API in [consensus spec](https://svgwg.org/specs/paths/#DOMInterfaces) |
201+
| **fs@opera.com** | ✅ Positive | Filed original bug; confirmed dictionary approach |
202+
203+
---
204+
205+
## References & Acknowledgements
206+
207+
**Specs:** [SVG Paths](https://svgwg.org/specs/paths/) · [SVG Paths §7 DOM Interfaces](https://svgwg.org/specs/paths/#DOMInterfaces) · [SVG 2](https://svgwg.org/svg2-draft/)
208+
209+
**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)
210+
211+
**Discussions:** [w3c/editing#483](https://github.com/w3c/editing/issues/483) · [w3c/svgwg#974](https://github.com/w3c/svgwg/issues/974)
212+
213+
**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)
214+
215+
**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).
216+
217+
---
218+
219+
## Appendix: WebIDL
220+
221+
```webidl
222+
dictionary SVGPathSegment {
223+
required DOMString type;
224+
required sequence<unrestricted float> values;
225+
};
226+
227+
dictionary SVGPathDataSettings {
228+
boolean normalize = false;
229+
};
230+
231+
partial interface SVGPathElement {
232+
sequence<SVGPathSegment> getPathData(optional SVGPathDataSettings settings = {});
233+
undefined setPathData(sequence<SVGPathSegment> pathData);
234+
SVGPathSegment? getPathSegmentAtLength(unrestricted float distance);
235+
};
236+
```
237+
238+
**Spec text updates (PR pending):** `dictionary` instead of `[NoInterfaceObject] interface` (accepts POJOs natively); `unrestricted float` instead of `float` (matches SVG error model); `required` keywords added (prevents `setPathData([{}])`).

0 commit comments

Comments
 (0)