From f2360aab9d6fef57a98b5924d0acff633346068e Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 26 Jun 2019 09:21:57 +0200 Subject: [PATCH] fix(ivy): incorrect namespace for root node created through ViewContainerRef (#31232) Currently in Ivy whenever we encounter a new namespace, we set it in the global state so that all subsequent nodes are created under the same namespace. Next time a template is run the namespace will be reset back to HTML. This breaks down if the last node that was rendered was under the SVG or MathML namespace and we create a component through `ViewContainerRef.create`, because the next template function hasn't run yet and it hasn't had the chance to update the namespace. The result is that the root node of the new component will retain the wrong namespace and may not end up rendering at all (e.g. if we're trying to show a `div` inside the SVG namespace). This issue has the potential to affect a lot of apps, because all components inserted through the router also go through `ViewContainerRef.create`. PR Close #31232 --- packages/core/src/render3/component_ref.ts | 5 ++- packages/core/src/render3/state.ts | 10 ++++- .../acceptance/view_container_ref_spec.ts | 37 +++++++++++++++++++ .../cyclic_import/bundle.golden_symbols.json | 3 ++ .../hello_world/bundle.golden_symbols.json | 3 ++ .../bundling/todo/bundle.golden_symbols.json | 3 ++ 6 files changed, 59 insertions(+), 2 deletions(-) diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index a3d8ad2131..f6fe2d964d 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -30,7 +30,7 @@ import {ComponentDef} from './interfaces/definition'; import {TContainerNode, TElementContainerNode, TElementNode} from './interfaces/node'; import {RNode, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {LView, LViewFlags, RootContext, TVIEW} from './interfaces/view'; -import {enterView, leaveView} from './state'; +import {enterView, leaveView, namespaceHTMLInternal} from './state'; import {defaultScheduler} from './util/misc_utils'; import {getTNode} from './util/view_utils'; import {createElementRef} from './view_engine_compatibility'; @@ -140,6 +140,9 @@ export class ComponentFactory extends viewEngine_ComponentFactory { rootViewInjector.get(RendererFactory2, domRendererFactory3) as RendererFactory3; const sanitizer = rootViewInjector.get(Sanitizer, null); + // Ensure that the namespace for the root node is correct, + // otherwise the browser might not render out the element properly. + namespaceHTMLInternal(); const hostRNode = isInternalRootView ? elementCreate(this.selector, rendererFactory.createRenderer(null, this.componentDef)) : locateHostElement(rendererFactory, rootSelectorOrNode); diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 4d89ee54ac..57e8b54b3d 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -537,12 +537,20 @@ export function ɵɵnamespaceMathML() { } /** - * Sets the namespace used to create elements no `null`, which forces element creation to use + * Sets the namespace used to create elements to `null`, which forces element creation to use * `createElement` rather than `createElementNS`. * * @codeGenApi */ export function ɵɵnamespaceHTML() { + namespaceHTMLInternal(); +} + +/** + * Sets the namespace used to create elements to `null`, which forces element creation to use + * `createElement` rather than `createElementNS`. + */ +export function namespaceHTMLInternal() { _currentNamespace = null; } diff --git a/packages/core/test/acceptance/view_container_ref_spec.ts b/packages/core/test/acceptance/view_container_ref_spec.ts index 0db09a7467..b9de42aa3d 100644 --- a/packages/core/test/acceptance/view_container_ref_spec.ts +++ b/packages/core/test/acceptance/view_container_ref_spec.ts @@ -1049,6 +1049,43 @@ describe('ViewContainerRef', () => { expect(() => fixture.componentRef.destroy()).not.toThrow(); }); + it('should create the root node in the correct namespace when previous node is SVG', () => { + @Component({ + template: ` +
Some random content
+ + + ` + }) + class TestComp { + constructor( + public viewContainerRef: ViewContainerRef, + public componentFactoryResolver: ComponentFactoryResolver) {} + } + + @Component({selector: 'dynamic-comp', template: ''}) + class DynamicComponent { + } + + @NgModule({declarations: [DynamicComponent], entryComponents: [DynamicComponent]}) + class DeclaresDynamicComponent { + } + + TestBed.configureTestingModule( + {imports: [DeclaresDynamicComponent], declarations: [TestComp]}); + const fixture = TestBed.createComponent(TestComp); + + // Note: it's important that we **don't** call `fixture.detectChanges` between here and + // the component being created, because running change detection will reset Ivy's + // namespace state which will make the test pass. + + const {viewContainerRef, componentFactoryResolver} = fixture.componentInstance; + const componentRef = viewContainerRef.createComponent( + componentFactoryResolver.resolveComponentFactory(DynamicComponent)); + const element = componentRef.location.nativeElement; + expect((element.namespaceURI || '').toLowerCase()).not.toContain('svg'); + }); + }); describe('insertion points and declaration points', () => { diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index ad90414efd..ce7aaffd47 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -581,6 +581,9 @@ { "name": "matchTemplateAttribute" }, + { + "name": "namespaceHTMLInternal" + }, { "name": "nativeAppendChild" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index ba58c7eaae..f694748d27 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -386,6 +386,9 @@ { "name": "locateHostElement" }, + { + "name": "namespaceHTMLInternal" + }, { "name": "nativeAppendChild" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 0d20b4a710..2eb9133154 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -1244,6 +1244,9 @@ { "name": "matchTemplateAttribute" }, + { + "name": "namespaceHTMLInternal" + }, { "name": "nativeAppendChild" },