[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
pull/7469/head
Yuichiro Yamashita 2 years ago committed by GitHub
parent eb37f4a285
commit 1803290864
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,8 +1,10 @@
import { namespaces } from './../../../utils/namespaces';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Renderer from '../Renderer'; import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import Tag from './shared/Tag'; import Tag from './shared/Tag';
import Wrapper from './shared/Wrapper'; import Wrapper from './shared/Wrapper';
import Element from '../../nodes/Element';
import MustacheTag from '../../nodes/MustacheTag'; import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag'; import RawMustacheTag from '../../nodes/RawMustacheTag';
import { is_head } from './shared/is_head'; import { is_head } from './shared/is_head';
@ -51,9 +53,12 @@ export default class RawMustacheTagWrapper extends Tag {
const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null'; const update_anchor = needs_anchor ? html_anchor : this.next ? this.next.var : 'null';
block.chunks.create.push(b`${html_tag} = new @HtmlTag();`); const parent_element = this.node.find_nearest(/^Element/) as Element;
const is_svg = parent_element && parent_element.namespace === namespaces.svg;
block.chunks.create.push(b`${html_tag} = new @HtmlTag(${is_svg ? 'true' : 'false'});`);
if (this.renderer.options.hydratable) { if (this.renderer.options.hydratable) {
block.chunks.claim.push(b`${html_tag} = @claim_html_tag(${_parent_nodes});`); block.chunks.claim.push(b`${html_tag} = @claim_html_tag(${_parent_nodes}, ${is_svg ? 'true' : 'false'});`);
} }
block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`); block.chunks.hydrate.push(b`${html_tag}.a = ${update_anchor};`);
block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`); block.chunks.mount.push(b`${html_tag}.m(${init}, ${parent_node || '#target'}, ${parent_node ? null : '#anchor'});`);

@ -492,12 +492,13 @@ function find_comment(nodes, text, start) {
return nodes.length; return nodes.length;
} }
export function claim_html_tag(nodes) {
export function claim_html_tag(nodes, is_svg: boolean) {
// find html opening tag // find html opening tag
const start_index = find_comment(nodes, 'HTML_TAG_START', 0); const start_index = find_comment(nodes, 'HTML_TAG_START', 0);
const end_index = find_comment(nodes, 'HTML_TAG_END', start_index); const end_index = find_comment(nodes, 'HTML_TAG_END', start_index);
if (start_index === end_index) { if (start_index === end_index) {
return new HtmlTagHydration(); return new HtmlTagHydration(undefined, is_svg);
} }
init_claim_info(nodes); init_claim_info(nodes);
@ -509,7 +510,7 @@ export function claim_html_tag(nodes) {
n.claim_order = nodes.claim_info.total_claimed; n.claim_order = nodes.claim_info.total_claimed;
nodes.claim_info.total_claimed += 1; nodes.claim_info.total_claimed += 1;
} }
return new HtmlTagHydration(claimed_nodes); return new HtmlTagHydration(claimed_nodes, is_svg);
} }
export function set_data(text, data) { export function set_data(text, data) {
@ -645,16 +646,18 @@ export function query_selector_all(selector: string, parent: HTMLElement = docum
} }
export class HtmlTag { export class HtmlTag {
private is_svg = false;
// parent for creating node // parent for creating node
e: HTMLElement; e: HTMLElement | SVGElement;
// html tag nodes // html tag nodes
n: ChildNode[]; n: ChildNode[];
// target // target
t: HTMLElement; t: HTMLElement | SVGElement;
// anchor // anchor
a: HTMLElement; a: HTMLElement | SVGElement;
constructor() { constructor(is_svg: boolean = false) {
this.is_svg = is_svg;
this.e = this.n = null; this.e = this.n = null;
} }
@ -662,9 +665,14 @@ export class HtmlTag {
this.h(html); this.h(html);
} }
m(html: string, target: HTMLElement, anchor: HTMLElement = null) { m(
html: string,
target: HTMLElement | SVGElement,
anchor: HTMLElement | SVGElement = null
) {
if (!this.e) { if (!this.e) {
this.e = element(target.nodeName as keyof HTMLElementTagNameMap); if (this.is_svg) this.e = svg_element(target.nodeName as keyof SVGElementTagNameMap);
else this.e = element(target.nodeName as keyof HTMLElementTagNameMap);
this.t = target; this.t = target;
this.c(html); this.c(html);
} }
@ -698,8 +706,8 @@ export class HtmlTagHydration extends HtmlTag {
// hydration claimed nodes // hydration claimed nodes
l: ChildNode[] | void; l: ChildNode[] | void;
constructor(claimed_nodes?: ChildNode[]) { constructor(claimed_nodes?: ChildNode[], is_svg: boolean = false) {
super(); super(is_svg);
this.e = this.n = null; this.e = this.n = null;
this.l = claimed_nodes; this.l = claimed_nodes;
} }

@ -52,7 +52,7 @@ function create_each_block(ctx) {
t4 = text(t4_value); t4 = text(t4_value);
t5 = text(" ago:"); t5 = text(" ago:");
t6 = space(); t6 = space();
html_tag = new HtmlTag(); html_tag = new HtmlTag(false);
attr(span, "class", "meta"); attr(span, "class", "meta");
html_tag.a = null; html_tag.a = null;
attr(div, "class", "comment"); attr(div, "class", "comment");
@ -170,4 +170,4 @@ class Component extends SvelteComponent {
} }
} }
export default Component; export default Component;

@ -0,0 +1,35 @@
export default {
html: `
<svg width="100" height="60">
<circle cx="25" cy="30" r="24" fill="#FFD166"></circle>
<circle cx="75" cy="30" r="24" fill="#118AB2"></circle>
</svg>
`,
test({ assert, target, component }) {
let svg = target.querySelector('svg');
let circles = target.querySelectorAll('circle');
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(2, circles.length);
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
component.width = 200;
component.height = 120;
assert.htmlEqual(
target.innerHTML,
`
<svg width="200" height="120">
<circle cx="50" cy="60" r="24" fill="#FFD166"></circle>
<circle cx="150" cy="60" r="24" fill="#118AB2"></circle>
</svg>
`
);
svg = target.querySelector('svg');
circles = target.querySelectorAll('circle');
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(2, circles.length);
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
}
};

@ -0,0 +1,10 @@
<script>
export let width = 100
export let height = 60
$: circle = `<circle cx="${width/4}" cy="${height/2}" r="24" fill="#FFD166"/>`
</script>
<svg width="{width}" height="{height}">
{@html circle}
<circle cx="{width/4*3}" cy="{height/2}" r="24" fill="#118AB2"/>
</svg>

@ -0,0 +1,39 @@
export default {
html: `
<svg width="100" height="60">
<rect>
<circle cx="25" cy="30" r="24" fill="#FFD166"></circle>
<circle cx="75" cy="30" r="24" fill="#118AB2"></circle>
</rect>
</svg>
`,
test({ assert, target, component }) {
let svg = target.querySelector('svg');
let circles = target.querySelectorAll('circle');
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(2, circles.length);
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
component.width = 200;
component.height = 120;
assert.htmlEqual(
target.innerHTML,
`
<svg width="200" height="120">
<rect>
<circle cx="50" cy="60" r="24" fill="#FFD166"></circle>
<circle cx="150" cy="60" r="24" fill="#118AB2"></circle>
</rect>
</svg>
`
);
svg = target.querySelector('svg');
circles = target.querySelectorAll('circle');
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(2, circles.length);
assert.equal(circles[0].namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(circles[1].namespaceURI, 'http://www.w3.org/2000/svg');
}
};

@ -0,0 +1,12 @@
<script>
export let width = 100
export let height = 60
$: circle = `<circle cx="${width/4}" cy="${height/2}" r="24" fill="#FFD166"/>`
</script>
<svg width="{width}" height="{height}">
<rect>
{@html circle}
<circle cx="{width/4*3}" cy="{height/2}" r="24" fill="#118AB2"/>
</rect>
</svg>

@ -0,0 +1,33 @@
export default {
html: `
<svg>
<foreignObject>
<circle cx="25" cy="30" r="24" fill="#FFD166"></circle>
</foreignObject>
</svg>
`,
test({ assert, target, component }) {
let svg = target.querySelector('svg');
let circle = target.querySelector('circle');
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(circle.namespaceURI, 'http://www.w3.org/1999/xhtml');
component.width = 200;
component.height = 120;
assert.htmlEqual(
target.innerHTML,
`
<svg>
<foreignObject>
<circle cx="50" cy="60" r="24" fill="#FFD166"></circle>
</foreignObject>
</svg>
`
);
svg = target.querySelector('svg');
circle = target.querySelector('circle');
assert.equal(svg.namespaceURI, 'http://www.w3.org/2000/svg');
assert.equal(circle.namespaceURI, 'http://www.w3.org/1999/xhtml');
}
};

@ -0,0 +1,11 @@
<script>
export let width = 100
export let height = 60
$: circle = `<circle cx="${width/4}" cy="${height/2}" r="24" fill="#FFD166"/>`
</script>
<svg>
<foreignObject>
{@html circle}
</foreignObject>
</svg>
Loading…
Cancel
Save