Skip to content

Commit 1803290

Browse files
authored
[fix] Specify svg namespace if {@html} is used in svg (#7464)
* add test * create svg element if {@html} tag is inside of svg * always use claim_html_tag
1 parent eb37f4a commit 1803290

File tree

9 files changed

+168
-15
lines changed

9 files changed

+168
-15
lines changed

src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { namespaces } from './../../../utils/namespaces';
12
import { b, x } from 'code-red';
23
import Renderer from '../Renderer';
34
import Block from '../Block';
45
import Tag from './shared/Tag';
56
import Wrapper from './shared/Wrapper';
7+
import Element from '../../nodes/Element';
68
import MustacheTag from '../../nodes/MustacheTag';
79
import RawMustacheTag from '../../nodes/RawMustacheTag';
810
import { is_head } from './shared/is_head';
@@ -51,9 +53,12 @@ export default class RawMustacheTagWrapper extends Tag {
5153

5254
const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
5355

54-
block.chunks.create.push(b`${html_tag} = new @HtmlTag();`);
56+
const parent_element = this.node.find_nearest(/^Element/) as Element;
57+
const is_svg = parent_element && parent_element.namespace === namespaces.svg;
58+
block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`);
59+
5560
if (this.renderer.options.hydratable) {
56-
block.chunks.claim.push(b`${html_tag} = @claim_html_tag(${_parent_nodes});`);
61+
block.chunks.claim.push(b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`);
5762
}
5863
block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`);
5964
block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`);

src/runtime/internal/dom.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -492,12 +492,13 @@ function find_comment(nodes, text, start) {
492492
return nodes.length;
493493
}
494494

495-
export function claim_html_tag(nodes) {
495+
496+
export function claim_html_tag(nodes, is_svg: boolean) {
496497
// find html opening tag
497498
const start_index = find_comment(nodes, 'HTML_TAG_START', 0);
498499
const end_index = find_comment(nodes, 'HTML_TAG_END', start_index);
499500
if (start_index === end_index) {
500-
return new HtmlTagHydration();
501+
return new HtmlTagHydration(undefined, is_svg);
501502
}
502503

503504
init_claim_info(nodes);
@@ -509,7 +510,7 @@ export function claim_html_tag(nodes) {
509510
n.claim_order = nodes.claim_info.total_claimed;
510511
nodes.claim_info.total_claimed += 1;
511512
}
512-
return new HtmlTagHydration(claimed_nodes);
513+
return new HtmlTagHydration(claimed_nodes, is_svg);
513514
}
514515

515516
export function set_data(text, data) {
@@ -645,26 +646,33 @@ export function query_selector_all(selector: string, parent: HTMLElement = docum
645646
}
646647

647648
export class HtmlTag {
649+
private is_svg = false;
648650
// parent for creating node
649-
e: HTMLElement;
651+
e: HTMLElement | SVGElement;
650652
// html tag nodes
651653
n: ChildNode[];
652654
// target
653-
t: HTMLElement;
655+
t: HTMLElement | SVGElement;
654656
// anchor
655-
a: HTMLElement;
657+
a: HTMLElement | SVGElement;
656658

657-
constructor() {
659+
constructor(is_svg: boolean = false) {
660+
this.is_svg = is_svg;
658661
this.e = this.n = null;
659662
}
660663

661664
c(html: string) {
662665
this.h(html);
663666
}
664667

665-
m(html: string, target: HTMLElement, anchor: HTMLElement = null) {
668+
m(
669+
html: string,
670+
target: HTMLElement | SVGElement,
671+
anchor: HTMLElement | SVGElement = null
672+
) {
666673
if (!this.e) {
667-
this.e = element(target.nodeName as keyof HTMLElementTagNameMap);
674+
if (this.is_svg) this.e = svg_element(target.nodeName as keyof SVGElementTagNameMap);
675+
else this.e = element(target.nodeName as keyof HTMLElementTagNameMap);
668676
this.t = target;
669677
this.c(html);
670678
}
@@ -698,8 +706,8 @@ export class HtmlTagHydration extends HtmlTag {
698706
// hydration claimed nodes
699707
l: ChildNode[] | void;
700708

701-
constructor(claimed_nodes?: ChildNode[]) {
702-
super();
709+
constructor(claimed_nodes?: ChildNode[], is_svg: boolean = false) {
710+
super(is_svg);
703711
this.e = this.n = null;
704712
this.l = claimed_nodes;
705713
}

test/js/samples/each-block-changed-check/expected.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function create_each_block(ctx) {
5252
t4 = text(t4_value);
5353
t5 = text(" ago:");
5454
t6 = space();
55-
html_tag = new HtmlTag();
55+
html_tag = new HtmlTag(false);
5656
attr(span, "class", "meta");
5757
html_tag.a = null;
5858
attr(div, "class", "comment");
@@ -170,4 +170,4 @@ class Component extends SvelteComponent {
170170
}
171171
}
172172

173-
export default Component;
173+
export default Component;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export default {
2+
html: `
3+
<svg width="100" height="60">
4+
<circle cx="25" cy="30" r="24" fill="#FFD166"></circle>
5+
<circle cx="75" cy="30" r="24" fill="#118AB2"></circle>
6+
</svg>
7+
`,
8+
test({ assert, target, component }) {
9+
10+
let svg = target.querySelector('svg');
11+
let circles = target.querySelectorAll('circle');
12+
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
13+
assert.equal(2, circles.length);
14+
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
15+
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
16+
17+
component.width = 200;
18+
component.height = 120;
19+
assert.htmlEqual(
20+
target.innerHTML,
21+
`
22+
<svg width="200" height="120">
23+
<circle cx="50" cy="60" r="24" fill="#FFD166"></circle>
24+
<circle cx="150" cy="60" r="24" fill="#118AB2"></circle>
25+
</svg>
26+
`
27+
);
28+
svg = target.querySelector('svg');
29+
circles = target.querySelectorAll('circle');
30+
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
31+
assert.equal(2, circles.length);
32+
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
33+
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
34+
}
35+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
export let width = 100
3+
export let height = 60
4+
$: circle = `<circle cx="${width/4}" cy="${height/2}" r="24" fill="#FFD166"/>`
5+
</script>
6+
7+
<svg width="{width}" height="{height}">
8+
{@html circle}
9+
<circle cx="{width/4*3}" cy="{height/2}" r="24" fill="#118AB2"/>
10+
</svg>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export default {
2+
html: `
3+
<svg width="100" height="60">
4+
<rect>
5+
<circle cx="25" cy="30" r="24" fill="#FFD166"></circle>
6+
<circle cx="75" cy="30" r="24" fill="#118AB2"></circle>
7+
</rect>
8+
</svg>
9+
`,
10+
test({ assert, target, component }) {
11+
12+
let svg = target.querySelector('svg');
13+
let circles = target.querySelectorAll('circle');
14+
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
15+
assert.equal(2, circles.length);
16+
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
17+
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
18+
19+
component.width = 200;
20+
component.height = 120;
21+
assert.htmlEqual(
22+
target.innerHTML,
23+
`
24+
<svg width="200" height="120">
25+
<rect>
26+
<circle cx="50" cy="60" r="24" fill="#FFD166"></circle>
27+
<circle cx="150" cy="60" r="24" fill="#118AB2"></circle>
28+
</rect>
29+
</svg>
30+
`
31+
);
32+
svg = target.querySelector('svg');
33+
circles = target.querySelectorAll('circle');
34+
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
35+
assert.equal(2, circles.length);
36+
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
37+
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
38+
}
39+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script>
2+
export let width = 100
3+
export let height = 60
4+
$: circle = `<circle cx="${width/4}" cy="${height/2}" r="24" fill="#FFD166"/>`
5+
</script>
6+
7+
<svg width="{width}" height="{height}">
8+
<rect>
9+
{@html circle}
10+
<circle cx="{width/4*3}" cy="{height/2}" r="24" fill="#118AB2"/>
11+
</rect>
12+
</svg>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export default {
2+
html: `
3+
<svg>
4+
<foreignObject>
5+
<circle cx="25" cy="30" r="24" fill="#FFD166"></circle>
6+
</foreignObject>
7+
</svg>
8+
`,
9+
test({ assert, target, component }) {
10+
11+
let svg = target.querySelector('svg');
12+
let circle = target.querySelector('circle');
13+
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
14+
assert.equal(circle.namespaceURI, 'http://www.w3.org/1999/xhtml');
15+
16+
component.width = 200;
17+
component.height = 120;
18+
assert.htmlEqual(
19+
target.innerHTML,
20+
`
21+
<svg>
22+
<foreignObject>
23+
<circle cx="50" cy="60" r="24" fill="#FFD166"></circle>
24+
</foreignObject>
25+
</svg>
26+
`
27+
);
28+
svg = target.querySelector('svg');
29+
circle = target.querySelector('circle');
30+
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
31+
assert.equal(circle.namespaceURI, 'http://www.w3.org/1999/xhtml');
32+
}
33+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
export let width = 100
3+
export let height = 60
4+
$: circle = `<circle cx="${width/4}" cy="${height/2}" r="24" fill="#FFD166"/>`
5+
</script>
6+
7+
<svg>
8+
<foreignObject>
9+
{@html circle}
10+
</foreignObject>
11+
</svg>

0 commit comments

Comments
 (0)