Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webview.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/Components/Web.JS/src/Rendering/BrowserRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ export class BrowserRenderer {
private insertElement(batch: RenderBatch, componentId: number, parent: LogicalElement, childIndex: number, frames: ArrayValues<RenderTreeFrame>, frame: RenderTreeFrame, frameIndex: number) {
const frameReader = batch.frameReader;
const tagName = frameReader.elementName(frame)!;
const newDomElementRaw = tagName === 'svg' || isSvgElement(parent) ?

const newDomElementRaw = (tagName === 'svg' || isSvgElement(parent)) ?
document.createElementNS('http://www.w3.org/2000/svg', tagName) :
document.createElement(tagName);
const newElement = toLogicalElement(newDomElementRaw);
Expand Down
12 changes: 11 additions & 1 deletion src/Components/Web.JS/src/Rendering/LogicalElements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,18 @@ export function getLogicalChild(parent: LogicalElement, childIndex: number): Log
return getLogicalChildrenArray(parent)[childIndex];
}

// SVG elements support `foreignObject` children that can hold arbitrary HTML.
// For these scenarios, the parent SVG and `foreignObject` elements should
// be rendered under the SVG namespace, while the HTML content should be rendered
// under the XHTML namespace. If the correct namespaces are not provided, most
// browsers will fail to render the foreign object content. Here, we ensure that if
// we encounter a `foreignObject` in the SVG, then all its children will be placed
// under the XHTML namespace.
export function isSvgElement(element: LogicalElement) {
return getClosestDomElement(element).namespaceURI === 'http://www.w3.org/2000/svg';
// Note: This check is intentionally case-sensitive since we expect this element
// to appear as a child of an SVG element and SVGs are case-sensitive.
var closestElement = getClosestDomElement(element);
return closestElement.namespaceURI === 'http://www.w3.org/2000/svg' && closestElement.tagName !== 'foreignObject';
}

export function getLogicalChildrenArray(element: LogicalElement) {
Expand Down
15 changes: 9 additions & 6 deletions src/Components/test/E2ETest/Tests/SvgTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void CanRenderSvgChildComponentWithCorrectNamespace()
Assert.NotNull(svgCircleElement);
}

[Fact(Skip="Skipped because functionality is not supported. See https://github.com/dotnet/aspnetcore/issues/18271.")]
[Fact]
public void CanRenderVariablesInForeignObject()
{
var appElement = Browser.MountTestComponent<SvgComponent>();
Expand All @@ -70,14 +70,14 @@ public void CanRenderVariablesInForeignObject()
Assert.NotNull(svgElement);

Func<IEnumerable<IWebElement>> strongElement =
() => appElement.FindElements(By.TagName("strong"));
() => svgElement.FindElements(By.TagName("strong"));

Browser.Collection<IWebElement>(strongElement,
e => Assert.Equal("thestringfoo", e.Text),
e => Assert.Equal("thestringbar", e.Text));
}

[Fact(Skip="Skipped because functionality is not supported. See https://github.com/dotnet/aspnetcore/issues/18271.")]
[Fact]
public void CanRenderSvgWithLink()
{
var appElement = Browser.MountTestComponent<SvgComponent>();
Expand All @@ -90,10 +90,11 @@ public void CanRenderSvgWithLink()
svgLinkElement.Click();

var currentScenario = Browser.FindElement(By.Id("test-selector-select"));
Assert.Equal("SVG", currentScenario.Text);
// Should have navigated away from current page
Browser.True(() => Browser.Url.EndsWith("/subdir/counter", StringComparison.Ordinal));
}

[Fact(Skip="Skipped because functionality is not supported. See https://github.com/dotnet/aspnetcore/issues/18271.")]
[Fact]
public void CanRenderSvgWithTwoWayBinding()
{
var appElement = Browser.MountTestComponent<SvgComponent>();
Expand All @@ -107,11 +108,13 @@ public void CanRenderSvgWithTwoWayBinding()
var svgInputElement = svgElement.FindElement(By.TagName("input"));
Assert.NotNull(svgInputElement);

svgInputElement.SendKeys(Keys.Backspace);
svgInputElement.SendKeys(Keys.Backspace);
svgInputElement.SendKeys("15");
Assert.Equal("15", valueElement.Text);
}

[Fact(Skip="Skipped because functionality is not supported. See https://github.com/dotnet/aspnetcore/issues/18271.")]
[Fact]
public void CanRenderSvgRenderFragment()
{
var appElement = Browser.MountTestComponent<SvgComponent>();
Expand Down
52 changes: 38 additions & 14 deletions src/Components/test/testassets/BasicTestApp/SvgComponent.razor
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,62 @@
</svg>

<h3>SVG with Foreign Object</h3>
<svg width="100" height="100" id="svg-with-foreign-object">
<foreignObject x="0" y="0" width="100" height="100">
<strong>thestringfoo</strong>
<strong>@bar</strong>
<p>
Browsers expect that there is a single parent element under the `foreignObject` or elements
will fail to render properly. Here, we ensure that that the text elements are wrapped in parent
paragraph element.
</p>
<svg width="200" height="200" id="svg-with-foreign-object" xmlns="http://www.w3.org/2000/svg">
<foreignObject x="0" y="0" width="200" height="200">
<p>
<strong>thestringfoo</strong>
<strong>@bar</strong>
</p>
</foreignObject>
</svg>

<h3>SVG with link</h3>
<p>
We can render SVGs within a foreign object as long as the SVG content is once
again wrapped in an SVG element. Here, we provided a complete SVG as a child of
NavLink to render the circle icon.
</p>
<svg id="svg-with-link">
<NavLink href="counter" id="navlink-in-svg">
<circle cx="100" cy="100" r="10"></circle>
</NavLink>
<foreignObject x="0" y="0" width="200" height="200">
<NavLink href="counter" id="navlink-in-svg">
<svg>
<circle cx="100" cy="100" r="10" xmlns="http://www.w3.org/2000/svg"></circle>
</svg>
</NavLink>
</foreignObject>
</svg>

<h3>SVG with input element and two-way binding</h3>
<svg id="svg-with-two-way-binding">
<foreignobject style="overflow:visible">
<input @bind="value" @bind:event="oninput" type="number"/> SVG Two way bind
</foreignobject>
<p>
The `foreignObject` element must provide `width` and `height` attributes
in order for child elements to be rendered correctly in browsers.
</p>
<svg id="svg-with-two-way-binding" xmlns="http://www.w3.org/2000/svg">
<foreignObject width="200" height="200">
<div>
<input @bind="value" @bind:event="oninput" type="number"/> Two-way binding
</div>
</foreignObject>
</svg>
<p id="svg-with-two-way-binding-value">@value</p>


<h3>SVG with render fragment</h3>
<p>
Some elements might pass an `xmlns` attribute set to either `http://www.w3.org/1999/xhtml`
or `http://www.w3.org/2000/svg`. This is generally immaterial and the `element.namespaceUri`
that is set from `createElementNS` impacts the namespace used for the element.
</p>
<svg id="svg-with-render-fragment">
<foreignObject width="100" height="100">
<div xmlns="http://www.w3.org/1999/xhtml">
<div>
@rf
</div>
</div>
</foreignObject>
</svg>

Expand All @@ -61,5 +86,4 @@
int value = 10;

RenderFragment rf = @<text><i>Hello</i></text>;

}