Skip to content

fix: parse innerHTML of an SVG element as SVG (#326) - #346

Open
jonasporto wants to merge 1 commit into
WebReflection:mainfrom
jonasporto:fix-326-svg-inner-html
Open

jonasporto wants to merge 1 commit into
WebReflection:mainfrom
jonasporto:fix-326-svg-inner-html

Conversation

@jonasporto

Copy link
Copy Markdown

Fixes #326

Problem

In an HTML document, setting innerHTML on an SVG element parses the markup as if it were HTML content: the /> of a self-closing tag is ignored, so siblings end up nested, and the new children are HTMLElements in the XHTML namespace.

import { parseHTML } from 'linkedom';
const { document } = parseHTML('<!doctype html><html><body><svg></svg></body></html>');
const svg = document.querySelector('svg');

svg.innerHTML = '<circle r="1"/><circle r="2"/>';

svg.innerHTML;                       // '<circle r="1"><circle r="2"></circle></circle>'
svg.children.length;                 // 1   (browser: 2)
svg.firstChild.namespaceURI;         // 'http://www.w3.org/1999/xhtml'   (browser: SVG)

Parsing the same markup as part of a document (<body><svg><circle/><circle/></svg>) is already right: htmlparser2 enters its foreign context when it sees <svg>, and parseFromString creates SVG elements under it. The fragment passed to innerHTML has no <svg> around it, so neither happens.

Reproduced on 0.18.12 and on main (3fcf145).

Fix

setInnerHtml (esm/mixin/inner-html.js): when the element is an SVG element in an HTML document, the markup is parsed wrapped in <svg>…</svg> and the wrapper's children are used. This reuses the existing SVG path of parseFromString (no change there, so it does not overlap with #330), and htmlparser2's own foreign-context handling closes />.

The parser points ownerSVGElement of what it creates at the temporary wrapper, so those are set to the element itself when it is an <svg>, or to its ownerSVGElement otherwise.

HTML/XML/SVG documents and non-SVG elements take the same path as before. CJS regenerated with npm run cjs.

Tests

Added to test/svg/element.js: siblings stay siblings, the children are SVGElements with the right ownerSVGElement, the serialization, and innerHTML on a nested <g>. The new test fails without the change (Expected: 2 / Got instead: 1).

npm test passes. Coverage for mixin/inner-html.js is 100%. The overall function figure (99.75%, from interface/element.js at 97.01%) is the same on main without this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self closed tag circle not correctly nested

1 participant