[svgwg] `currentScale`'s setter doesn't say whether to limit the value, and each engine does something different (#1158)

karlcow has just created a new issue for https://github.com/w3c/svgwg:

== `currentScale`'s setter doesn't say whether to limit the value, and each engine does something different ==

[`currentScale`](https://w3c.github.io/svgwg/svg2-draft/struct.html#__svg__SVGSVGElement__currentScale) lets a page zoom an SVG document by assigning a number to it. The spec's setter steps are:

> On setting `currentScale`, the following steps are run:
> 1. If the current 'svg' element is not the [outermost svg element](https://w3c.github.io/svgwg/svg2-draft/struct.html#TermOutermostSVGElement), then return.
> 2. Let `scale` be the value being assigned to `currentScale`.
> 3. Let `[a b c d e f]` be the 2x3 matrix that represents the document's magnification and panning transform.
> 4. Set the document's magnification and panning transform to `[scale 0 0 scale e f]`.

Nothing here limits `scale` to a particular range. In practice, one engine limits it and two don't, and the "outermost svg element" check also isn't applied the same way everywhere.

### Code example

```html
<svg><rect width="10" height="10"/></svg>
<script>
  const svg = document.querySelector('svg');
  svg.currentScale = 100;
  console.log(svg.currentScale);
</script>
```

### Results — `<svg>` as a child of `<body>` in an HTML page

| | WebKit (Safari) 26.0 | Gecko (Firefox) 154 | Blink (Chrome) 151 |
|---|---|---|---|
| `svg.currentScale = 100` | stays `1` (the write is silently ignored) | becomes `16` | becomes `100` |
| `svg.currentScale = 0.001` | stays `1` (ignored) | becomes `0.0625` | becomes `0.001` |
| `svg.currentScale = -1000` | stays `1` (ignored) | becomes `0.0625` | becomes `-1000` |
| `svg.currentScale = NaN` | throws `TypeError` | throws `TypeError` | throws `TypeError` |

### Results — the same `<svg>` opened directly as its own document (a standalone `.svg` file)

| | WebKit (Safari) 26.0 | Gecko (Firefox) 154 | Blink (Chrome) 151 |
|---|---|---|---|
| `svg.currentScale = 100` | becomes `100` | becomes `16` | becomes `100` |
| `svg.currentScale = 0.001` | becomes `0.001` | becomes `0.0625` | becomes `0.001` |
| `svg.currentScale = -1000` | becomes `-1000` | becomes `0.0625` | becomes `-1000` |

Two separate things are going on:

**1. Only Gecko limits the value at all.** Gecko clamps every assignment into `[0.0625, 16.0]`, both for very large and very small (or negative) numbers:

```cpp
// https://github.com/mozilla-firefox/firefox/blob/main/dom/svg/SVGSVGElement.cpp#L130-L146
#define CURRENT_SCALE_MAX 16.0f
#define CURRENT_SCALE_MIN 0.0625f

void SVGSVGElement::SetCurrentScale(float aCurrentScale) {
  // Prevent bizarre behaviour and maxing out of CPU and memory by clamping
  aCurrentScale =
      std::clamp(aCurrentScale, CURRENT_SCALE_MIN, CURRENT_SCALE_MAX);
  ...
}
```

WebKit and Blink store whatever finite number you give them, no matter how large, small, or negative. All three engines agree on rejecting non-finite values (`NaN`/`Infinity`).  It comes from the `float` type in the IDL, not from anything specific to `currentScale`.

**2. WebKit's "outermost" check is narrower than the spec's.** The spec's definition (https://w3c.github.io/svgwg/svg2-draft/struct.html#TermOutermostSVGElement) says an `<svg>` is outermost if its parent isn't in the SVG namespace (which includes an `<svg>` that's a direct child of HTML's `<body>`). 

> The ‘[svg](https://w3c.github.io/svgwg/svg2-draft/single-page.html#struct-SVGElement)’ element that starts an [SVG document fragment](https://w3c.github.io/svgwg/svg2-draft/single-page.html#struct-TermSVGDocumentFragment), that is, one whose parent element is not in the SVG namespace

By that definition, the `<svg>` in the code example above is outermost, and the setter should run.

WebKit's actual check is stricter (The `<svg>` has to be the root element of the entire document, not just outermost within its own fragment):

```cpp
// https://github.com/WebKit/WebKit/blob/main/Source/WebCore/svg/SVGSVGElement.cpp
RefPtr<LocalFrame> SVGSVGElement::frameForCurrentScale() const
{
    if (!isConnected() || !isOutermostSVGSVGElement() || document().documentElement() != this)
        return nullptr;
    ...
}
```

That extra `document().documentElement() != this` check is why the HTML-hosted example silently does nothing in WebKit, while the exact same `<svg>` element works when it's the whole document.

### Questions

1. Should `currentScale` have a defined range (matching Gecko's `[0.0625, 16.0]`, or some other range), or should any finite number be accepted (matching WebKit at document-root and Blink everywhere)? Right now the spec's silence reads as "any finite number," which two of three engines already do, but a page can't rely on that without it being written down.
2. Should the "outermost svg element" check used here be exactly the §1.3 definition (any `<svg>` whose parent isn't SVG-namespaced), or is a narrower "must be the whole document's root" check intended for `currentScale` specifically? Right now the same input (`<svg>` as an HTML `<body>` child) is accepted by two engines and silently ignored by the third.


Please view or discuss this issue at https://github.com/w3c/svgwg/issues/1158 using your GitHub account


-- 
Sent via github-notify-ml as configured in https://github.com/w3c/github-notify-ml-config

Received on Wednesday, 29 July 2026 10:16:13 UTC