fix(ivy): incorrectly validating html foreign objects inside svg (#34178)
Fixes ngtsc incorrectly logging an unknown element diagnostic for HTML elements that are inside an SVG `foreignObject` with the `xhtml` namespace. Fixes #34171. PR Close #34178
This commit is contained in:
parent
539d8f09e0
commit
e6909bda89
|
@ -14,6 +14,7 @@ import {ErrorCode} from '../../diagnostics';
|
||||||
import {TcbSourceResolver, makeTemplateDiagnostic} from './diagnostics';
|
import {TcbSourceResolver, makeTemplateDiagnostic} from './diagnostics';
|
||||||
|
|
||||||
const REGISTRY = new DomElementSchemaRegistry();
|
const REGISTRY = new DomElementSchemaRegistry();
|
||||||
|
const REMOVE_XHTML_REGEX = /^:xhtml:/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks every non-Angular element/property processed in a template and potentially produces
|
* Checks every non-Angular element/property processed in a template and potentially produces
|
||||||
|
@ -69,15 +70,20 @@ export class RegistryDomSchemaChecker implements DomSchemaChecker {
|
||||||
constructor(private resolver: TcbSourceResolver) {}
|
constructor(private resolver: TcbSourceResolver) {}
|
||||||
|
|
||||||
checkElement(id: string, element: TmplAstElement, schemas: SchemaMetadata[]): void {
|
checkElement(id: string, element: TmplAstElement, schemas: SchemaMetadata[]): void {
|
||||||
if (!REGISTRY.hasElement(element.name, schemas)) {
|
// HTML elements inside an SVG `foreignObject` are declared in the `xhtml` namespace.
|
||||||
|
// We need to strip it before handing it over to the registry because all HTML tag names
|
||||||
|
// in the registry are without a namespace.
|
||||||
|
const name = element.name.replace(REMOVE_XHTML_REGEX, '');
|
||||||
|
|
||||||
|
if (!REGISTRY.hasElement(name, schemas)) {
|
||||||
const mapping = this.resolver.getSourceMapping(id);
|
const mapping = this.resolver.getSourceMapping(id);
|
||||||
|
|
||||||
let errorMsg = `'${element.name}' is not a known element:\n`;
|
let errorMsg = `'${name}' is not a known element:\n`;
|
||||||
errorMsg +=
|
errorMsg +=
|
||||||
`1. If '${element.name}' is an Angular component, then verify that it is part of this module.\n`;
|
`1. If '${name}' is an Angular component, then verify that it is part of this module.\n`;
|
||||||
if (element.name.indexOf('-') > -1) {
|
if (name.indexOf('-') > -1) {
|
||||||
errorMsg +=
|
errorMsg +=
|
||||||
`2. If '${element.name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.`;
|
`2. If '${name}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.`;
|
||||||
} else {
|
} else {
|
||||||
errorMsg +=
|
errorMsg +=
|
||||||
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
|
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
|
||||||
|
|
|
@ -1362,6 +1362,55 @@ export declare class AnimationEvent {
|
||||||
const diags = env.driveDiagnostics();
|
const diags = env.driveDiagnostics();
|
||||||
expect(diags).toEqual([]);
|
expect(diags).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow HTML elements inside SVG foreignObject', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'blah',
|
||||||
|
template: \`
|
||||||
|
<svg>
|
||||||
|
<svg:foreignObject>
|
||||||
|
<xhtml:div>Hello</xhtml:div>
|
||||||
|
</svg:foreignObject>
|
||||||
|
</svg>
|
||||||
|
\`,
|
||||||
|
})
|
||||||
|
export class FooCmp {}
|
||||||
|
@NgModule({
|
||||||
|
declarations: [FooCmp],
|
||||||
|
})
|
||||||
|
export class FooModule {}
|
||||||
|
`);
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check for unknown elements inside an SVG foreignObject', () => {
|
||||||
|
env.write('test.ts', `
|
||||||
|
import {Component, NgModule} from '@angular/core';
|
||||||
|
@Component({
|
||||||
|
selector: 'blah',
|
||||||
|
template: \`
|
||||||
|
<svg>
|
||||||
|
<svg:foreignObject>
|
||||||
|
<xhtml:foo>Hello</xhtml:foo>
|
||||||
|
</svg:foreignObject>
|
||||||
|
</svg>
|
||||||
|
\`,
|
||||||
|
})
|
||||||
|
export class FooCmp {}
|
||||||
|
@NgModule({
|
||||||
|
declarations: [FooCmp],
|
||||||
|
})
|
||||||
|
export class FooModule {}
|
||||||
|
`);
|
||||||
|
const diags = env.driveDiagnostics();
|
||||||
|
expect(diags.length).toBe(1);
|
||||||
|
expect(diags[0].messageText).toBe(`'foo' is not a known element:
|
||||||
|
1. If 'foo' is an Angular component, then verify that it is part of this module.
|
||||||
|
2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test both sync and async compilations, see https://github.com/angular/angular/issues/32538
|
// Test both sync and async compilations, see https://github.com/angular/angular/issues/32538
|
||||||
|
|
|
@ -311,5 +311,30 @@ describe('NgModule', () => {
|
||||||
}).not.toThrow();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not throw for HTML elements inside an SVG foreignObject', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<svg>
|
||||||
|
<svg:foreignObject>
|
||||||
|
<xhtml:div>Hello</xhtml:div>
|
||||||
|
</svg:foreignObject>
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({declarations: [MyComp]})
|
||||||
|
class MyModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [MyModule]});
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue