fix(ivy): support schemas at runtime (#28637)
Accounts for schemas in when validating properties in Ivy. This PR resolves FW-819. A couple of notes: * I had to rework the test slightly, in order to have it fail when we expect it to. The one in master is passing since Ivy's validation runs during the update phase, rather than creation. * I had to deviate from the design in FW-819 and not add an `enableSchema` instruction, because the schema is part of the `NgModule` scope, however the scope is only assigned to a component once all of the module's declarations have been resolved and some of them can be async. Instead, I opted to have the `schemas` on the component definition. PR Close #28637
This commit is contained in:
parent
7cbc36fdac
commit
80a5934af6
|
@ -134,6 +134,8 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
|
||||||
exports: exports.map(exp => this._toR3Reference(exp, valueContext, typeContext)),
|
exports: exports.map(exp => this._toR3Reference(exp, valueContext, typeContext)),
|
||||||
imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)),
|
imports: imports.map(imp => this._toR3Reference(imp, valueContext, typeContext)),
|
||||||
emitInline: false,
|
emitInline: false,
|
||||||
|
// TODO: to be implemented as a part of FW-1004.
|
||||||
|
schemas: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const providers: Expression = ngModule.has('providers') ?
|
const providers: Expression = ngModule.has('providers') ?
|
||||||
|
|
|
@ -97,6 +97,7 @@ export interface R3NgModuleMetadataFacade {
|
||||||
imports: Function[];
|
imports: Function[];
|
||||||
exports: Function[];
|
exports: Function[];
|
||||||
emitInline: boolean;
|
emitInline: boolean;
|
||||||
|
schemas: {name: string}[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface R3InjectorMetadataFacade {
|
export interface R3InjectorMetadataFacade {
|
||||||
|
@ -155,4 +156,4 @@ export interface ParseSourceSpan {
|
||||||
start: any;
|
start: any;
|
||||||
end: any;
|
end: any;
|
||||||
details: any;
|
details: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,7 @@ export class CompilerFacadeImpl implements CompilerFacade {
|
||||||
imports: facade.imports.map(wrapReference),
|
imports: facade.imports.map(wrapReference),
|
||||||
exports: facade.exports.map(wrapReference),
|
exports: facade.exports.map(wrapReference),
|
||||||
emitInline: true,
|
emitInline: true,
|
||||||
|
schemas: facade.schemas ? facade.schemas.map(wrapReference) : null,
|
||||||
};
|
};
|
||||||
const res = compileNgModule(meta);
|
const res = compileNgModule(meta);
|
||||||
return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
|
return this.jitExpression(res.expression, angularCoreEnv, sourceMapUrl, []);
|
||||||
|
|
|
@ -57,13 +57,18 @@ export interface R3NgModuleMetadata {
|
||||||
* does not allow components to be tree-shaken, but is useful for JIT mode.
|
* does not allow components to be tree-shaken, but is useful for JIT mode.
|
||||||
*/
|
*/
|
||||||
emitInline: boolean;
|
emitInline: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of schemas that declare elements to be allowed in the NgModule.
|
||||||
|
*/
|
||||||
|
schemas: R3Reference[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
* Construct an `R3NgModuleDef` for the given `R3NgModuleMetadata`.
|
||||||
*/
|
*/
|
||||||
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
||||||
const {type: moduleType, bootstrap, declarations, imports, exports} = meta;
|
const {type: moduleType, bootstrap, declarations, imports, exports, schemas} = meta;
|
||||||
const definitionMap = {
|
const definitionMap = {
|
||||||
type: moduleType
|
type: moduleType
|
||||||
} as{
|
} as{
|
||||||
|
@ -71,7 +76,8 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
||||||
bootstrap: o.LiteralArrayExpr,
|
bootstrap: o.LiteralArrayExpr,
|
||||||
declarations: o.LiteralArrayExpr,
|
declarations: o.LiteralArrayExpr,
|
||||||
imports: o.LiteralArrayExpr,
|
imports: o.LiteralArrayExpr,
|
||||||
exports: o.LiteralArrayExpr
|
exports: o.LiteralArrayExpr,
|
||||||
|
schemas: o.LiteralArrayExpr
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only generate the keys in the metadata if the arrays have values.
|
// Only generate the keys in the metadata if the arrays have values.
|
||||||
|
@ -91,6 +97,10 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef {
|
||||||
definitionMap.exports = o.literalArr(exports.map(ref => ref.value));
|
definitionMap.exports = o.literalArr(exports.map(ref => ref.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (schemas && schemas.length) {
|
||||||
|
definitionMap.schemas = o.literalArr(schemas.map(ref => ref.value));
|
||||||
|
}
|
||||||
|
|
||||||
const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression(definitionMap)]);
|
const expression = o.importExpr(R3.defineNgModule).callFn([mapToMapExpression(definitionMap)]);
|
||||||
const type = new o.ExpressionType(o.importExpr(R3.NgModuleDefWithMeta, [
|
const type = new o.ExpressionType(o.importExpr(R3.NgModuleDefWithMeta, [
|
||||||
new o.ExpressionType(moduleType), tupleTypeOf(declarations), tupleTypeOf(imports),
|
new o.ExpressionType(moduleType), tupleTypeOf(declarations), tupleTypeOf(imports),
|
||||||
|
|
|
@ -97,6 +97,7 @@ export interface R3NgModuleMetadataFacade {
|
||||||
imports: Function[];
|
imports: Function[];
|
||||||
exports: Function[];
|
exports: Function[];
|
||||||
emitInline: boolean;
|
emitInline: boolean;
|
||||||
|
schemas: {name: string}[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface R3InjectorMetadataFacade {
|
export interface R3InjectorMetadataFacade {
|
||||||
|
@ -155,4 +156,4 @@ export interface ParseSourceSpan {
|
||||||
start: any;
|
start: any;
|
||||||
end: any;
|
end: any;
|
||||||
details: any;
|
details: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,14 @@
|
||||||
import {Attribute} from './di';
|
import {Attribute} from './di';
|
||||||
import {ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
|
import {ContentChild, ContentChildren, Query, ViewChild, ViewChildren} from './metadata/di';
|
||||||
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
|
import {Component, Directive, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
|
||||||
import {DoBootstrap, ModuleWithProviders, NgModule, SchemaMetadata} from './metadata/ng_module';
|
import {DoBootstrap, ModuleWithProviders, NgModule} from './metadata/ng_module';
|
||||||
|
import {SchemaMetadata} from './metadata/schema';
|
||||||
import {ViewEncapsulation} from './metadata/view';
|
import {ViewEncapsulation} from './metadata/view';
|
||||||
|
|
||||||
export {Attribute} from './di';
|
export {Attribute} from './di';
|
||||||
export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './interface/lifecycle_hooks';
|
export {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, OnInit} from './interface/lifecycle_hooks';
|
||||||
export {ANALYZE_FOR_ENTRY_COMPONENTS, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';
|
export {ANALYZE_FOR_ENTRY_COMPONENTS, ContentChild, ContentChildDecorator, ContentChildren, ContentChildrenDecorator, Query, ViewChild, ViewChildDecorator, ViewChildren, ViewChildrenDecorator} from './metadata/di';
|
||||||
export {Component, ComponentDecorator, Directive, DirectiveDecorator, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
|
export {Component, ComponentDecorator, Directive, DirectiveDecorator, HostBinding, HostListener, Input, Output, Pipe} from './metadata/directives';
|
||||||
export {CUSTOM_ELEMENTS_SCHEMA, DoBootstrap, ModuleWithProviders, NO_ERRORS_SCHEMA, NgModule, SchemaMetadata} from './metadata/ng_module';
|
export {DoBootstrap, ModuleWithProviders, NgModule} from './metadata/ng_module';
|
||||||
|
export {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from './metadata/schema';
|
||||||
export {ViewEncapsulation} from './metadata/view';
|
export {ViewEncapsulation} from './metadata/view';
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {InjectorType, defineInjector} from '../di/interface/defs';
|
||||||
import {Provider} from '../di/interface/provider';
|
import {Provider} from '../di/interface/provider';
|
||||||
import {convertInjectableProviderToFactory} from '../di/util';
|
import {convertInjectableProviderToFactory} from '../di/util';
|
||||||
import {Type} from '../interface/type';
|
import {Type} from '../interface/type';
|
||||||
|
import {SchemaMetadata} from '../metadata/schema';
|
||||||
import {NgModuleType} from '../render3';
|
import {NgModuleType} from '../render3';
|
||||||
import {compileNgModule as render3CompileNgModule} from '../render3/jit/module';
|
import {compileNgModule as render3CompileNgModule} from '../render3/jit/module';
|
||||||
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
import {TypeDecorator, makeDecorator} from '../util/decorators';
|
||||||
|
@ -28,6 +29,7 @@ import {TypeDecorator, makeDecorator} from '../util/decorators';
|
||||||
export interface NgModuleTransitiveScopes {
|
export interface NgModuleTransitiveScopes {
|
||||||
compilation: {directives: Set<any>; pipes: Set<any>;};
|
compilation: {directives: Set<any>; pipes: Set<any>;};
|
||||||
exported: {directives: Set<any>; pipes: Set<any>;};
|
exported: {directives: Set<any>; pipes: Set<any>;};
|
||||||
|
schemas: SchemaMetadata[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NgModuleDefWithMeta<T, Declarations, Imports, Exports> = NgModuleDef<T>;
|
export type NgModuleDefWithMeta<T, Declarations, Imports, Exports> = NgModuleDef<T>;
|
||||||
|
@ -67,6 +69,9 @@ export interface NgModuleDef<T> {
|
||||||
* This should never be read directly, but accessed via `transitiveScopesFor`.
|
* This should never be read directly, but accessed via `transitiveScopesFor`.
|
||||||
*/
|
*/
|
||||||
transitiveCompileScopes: NgModuleTransitiveScopes|null;
|
transitiveCompileScopes: NgModuleTransitiveScopes|null;
|
||||||
|
|
||||||
|
/** The set of schemas that declare elements to be allowed in the NgModule. */
|
||||||
|
schemas: SchemaMetadata[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,38 +88,6 @@ export interface ModuleWithProviders<
|
||||||
providers?: Provider[];
|
providers?: Provider[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A schema definition associated with an NgModule.
|
|
||||||
*
|
|
||||||
* @see `@NgModule`, `CUSTOM_ELEMENTS_SCHEMA`, `NO_ERRORS_SCHEMA`
|
|
||||||
*
|
|
||||||
* @param name The name of a defined schema.
|
|
||||||
*
|
|
||||||
* @publicApi
|
|
||||||
*/
|
|
||||||
export interface SchemaMetadata { name: string; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a schema that allows an NgModule to contain the following:
|
|
||||||
* - Non-Angular elements named with dash case (`-`).
|
|
||||||
* - Element properties named with dash case (`-`).
|
|
||||||
* Dash case is the naming convention for custom elements.
|
|
||||||
*
|
|
||||||
* @publicApi
|
|
||||||
*/
|
|
||||||
export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = {
|
|
||||||
name: 'custom-elements'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a schema that allows any property on any element.
|
|
||||||
*
|
|
||||||
* @publicApi
|
|
||||||
*/
|
|
||||||
export const NO_ERRORS_SCHEMA: SchemaMetadata = {
|
|
||||||
name: 'no-errors-schema'
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of the NgModule decorator / constructor function.
|
* Type of the NgModule decorator / constructor function.
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A schema definition associated with an NgModule.
|
||||||
|
*
|
||||||
|
* @see `@NgModule`, `CUSTOM_ELEMENTS_SCHEMA`, `NO_ERRORS_SCHEMA`
|
||||||
|
*
|
||||||
|
* @param name The name of a defined schema.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export interface SchemaMetadata { name: string; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a schema that allows an NgModule to contain the following:
|
||||||
|
* - Non-Angular elements named with dash case (`-`).
|
||||||
|
* - Element properties named with dash case (`-`).
|
||||||
|
* Dash case is the naming convention for custom elements.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata = {
|
||||||
|
name: 'custom-elements'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a schema that allows any property on any element.
|
||||||
|
*
|
||||||
|
* @publicApi
|
||||||
|
*/
|
||||||
|
export const NO_ERRORS_SCHEMA: SchemaMetadata = {
|
||||||
|
name: 'no-errors-schema'
|
||||||
|
};
|
|
@ -122,7 +122,7 @@ export function renderComponent<T>(
|
||||||
|
|
||||||
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
|
const renderer = rendererFactory.createRenderer(hostRNode, componentDef);
|
||||||
const rootView: LView = createLView(
|
const rootView: LView = createLView(
|
||||||
null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null,
|
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null, null,
|
||||||
rendererFactory, renderer, undefined, opts.injector || null);
|
rendererFactory, renderer, undefined, opts.injector || null);
|
||||||
|
|
||||||
const oldView = enterView(rootView, null);
|
const oldView = enterView(rootView, null);
|
||||||
|
@ -165,9 +165,9 @@ export function createRootComponentView(
|
||||||
const tView = rootView[TVIEW];
|
const tView = rootView[TVIEW];
|
||||||
const tNode: TElementNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null);
|
const tNode: TElementNode = createNodeAtIndex(0, TNodeType.Element, rNode, null, null);
|
||||||
const componentView = createLView(
|
const componentView = createLView(
|
||||||
rootView,
|
rootView, getOrCreateTView(
|
||||||
getOrCreateTView(
|
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs,
|
||||||
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery),
|
def.viewQuery, def.schemas),
|
||||||
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
|
null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
|
||||||
rendererFactory, renderer, sanitizer);
|
rendererFactory, renderer, sanitizer);
|
||||||
|
|
||||||
|
|
|
@ -162,8 +162,8 @@ export class ComponentFactory<T> extends viewEngine_ComponentFactory<T> {
|
||||||
|
|
||||||
// Create the root view. Uses empty TView and ContentTemplate.
|
// Create the root view. Uses empty TView and ContentTemplate.
|
||||||
const rootLView = createLView(
|
const rootLView = createLView(
|
||||||
null, createTView(-1, null, 1, 0, null, null, null), rootContext, rootFlags, null, null,
|
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, rootFlags, null,
|
||||||
rendererFactory, renderer, sanitizer, rootViewInjector);
|
null, rendererFactory, renderer, sanitizer, rootViewInjector);
|
||||||
|
|
||||||
// rootView is the parent when bootstrapping
|
// rootView is the parent when bootstrapping
|
||||||
const oldLView = enterView(rootLView, null);
|
const oldLView = enterView(rootLView, null);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import '../util/ng_dev_mode';
|
||||||
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
import {ChangeDetectionStrategy} from '../change_detection/constants';
|
||||||
import {Mutable, Type} from '../interface/type';
|
import {Mutable, Type} from '../interface/type';
|
||||||
import {NgModuleDef} from '../metadata/ng_module';
|
import {NgModuleDef} from '../metadata/ng_module';
|
||||||
|
import {SchemaMetadata} from '../metadata/schema';
|
||||||
import {ViewEncapsulation} from '../metadata/view';
|
import {ViewEncapsulation} from '../metadata/view';
|
||||||
import {noSideEffects} from '../util/closure';
|
import {noSideEffects} from '../util/closure';
|
||||||
import {stringify} from '../util/stringify';
|
import {stringify} from '../util/stringify';
|
||||||
|
@ -234,6 +235,11 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
* `PipeDefs`s. The function is necessary to be able to support forward declarations.
|
* `PipeDefs`s. The function is necessary to be able to support forward declarations.
|
||||||
*/
|
*/
|
||||||
pipes?: PipeTypesOrFactory | null;
|
pipes?: PipeTypesOrFactory | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of schemas that declare elements to be allowed in the component's template.
|
||||||
|
*/
|
||||||
|
schemas?: SchemaMetadata[] | null;
|
||||||
}): never {
|
}): never {
|
||||||
const type = componentDefinition.type;
|
const type = componentDefinition.type;
|
||||||
const typePrototype = type.prototype;
|
const typePrototype = type.prototype;
|
||||||
|
@ -274,6 +280,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||||
styles: componentDefinition.styles || EMPTY_ARRAY,
|
styles: componentDefinition.styles || EMPTY_ARRAY,
|
||||||
_: null as never,
|
_: null as never,
|
||||||
setInput: null,
|
setInput: null,
|
||||||
|
schemas: componentDefinition.schemas || null,
|
||||||
};
|
};
|
||||||
def._ = noSideEffects(() => {
|
def._ = noSideEffects(() => {
|
||||||
const directiveTypes = componentDefinition.directives !;
|
const directiveTypes = componentDefinition.directives !;
|
||||||
|
@ -326,6 +333,7 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
|
||||||
imports: def.imports || EMPTY_ARRAY,
|
imports: def.imports || EMPTY_ARRAY,
|
||||||
exports: def.exports || EMPTY_ARRAY,
|
exports: def.exports || EMPTY_ARRAY,
|
||||||
transitiveCompileScopes: null,
|
transitiveCompileScopes: null,
|
||||||
|
schemas: def.schemas || null,
|
||||||
};
|
};
|
||||||
return res as never;
|
return res as never;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {InjectFlags, InjectionToken, Injector} from '../di';
|
||||||
import {resolveForwardRef} from '../di/forward_ref';
|
import {resolveForwardRef} from '../di/forward_ref';
|
||||||
import {ErrorHandler} from '../error_handler';
|
import {ErrorHandler} from '../error_handler';
|
||||||
import {Type} from '../interface/type';
|
import {Type} from '../interface/type';
|
||||||
|
import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../metadata/schema';
|
||||||
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../sanitization/sanitization';
|
import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../sanitization/sanitization';
|
||||||
import {Sanitizer} from '../sanitization/security';
|
import {Sanitizer} from '../sanitization/security';
|
||||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||||
|
@ -320,12 +321,12 @@ export function renderTemplate<T>(
|
||||||
|
|
||||||
// We need to create a root view so it's possible to look up the host element through its index
|
// We need to create a root view so it's possible to look up the host element through its index
|
||||||
const hostLView = createLView(
|
const hostLView = createLView(
|
||||||
null, createTView(-1, null, 1, 0, null, null, null), {},
|
null, createTView(-1, null, 1, 0, null, null, null, null), {},
|
||||||
LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null, providedRendererFactory, renderer);
|
LViewFlags.CheckAlways | LViewFlags.IsRoot, null, null, providedRendererFactory, renderer);
|
||||||
enterView(hostLView, null); // SUSPECT! why do we need to enter the View?
|
enterView(hostLView, null); // SUSPECT! why do we need to enter the View?
|
||||||
|
|
||||||
const componentTView =
|
const componentTView =
|
||||||
getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null);
|
getOrCreateTView(templateFn, consts, vars, directives || null, pipes || null, null, null);
|
||||||
const hostTNode = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
|
const hostTNode = createNodeAtIndex(0, TNodeType.Element, hostNode, null, null);
|
||||||
componentView = createLView(
|
componentView = createLView(
|
||||||
hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode,
|
hostLView, componentTView, context, LViewFlags.CheckAlways, hostNode, hostTNode,
|
||||||
|
@ -728,12 +729,14 @@ function saveResolvedLocalsInData(
|
||||||
* @param vars The number of bindings and pure function bindings in this view
|
* @param vars The number of bindings and pure function bindings in this view
|
||||||
* @param directives Directive defs that should be saved on TView
|
* @param directives Directive defs that should be saved on TView
|
||||||
* @param pipes Pipe defs that should be saved on TView
|
* @param pipes Pipe defs that should be saved on TView
|
||||||
|
* @param viewQuery View query that should be saved on TView
|
||||||
|
* @param schemas Schemas that should be saved on TView
|
||||||
* @returns TView
|
* @returns TView
|
||||||
*/
|
*/
|
||||||
export function getOrCreateTView(
|
export function getOrCreateTView(
|
||||||
templateFn: ComponentTemplate<any>, consts: number, vars: number,
|
templateFn: ComponentTemplate<any>, consts: number, vars: number,
|
||||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
||||||
viewQuery: ViewQueriesFunction<any>| null): TView {
|
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null): TView {
|
||||||
// TODO(misko): reading `ngPrivateData` here is problematic for two reasons
|
// TODO(misko): reading `ngPrivateData` here is problematic for two reasons
|
||||||
// 1. It is a megamorphic call on each invocation.
|
// 1. It is a megamorphic call on each invocation.
|
||||||
// 2. For nested embedded views (ngFor inside ngFor) the template instance is per
|
// 2. For nested embedded views (ngFor inside ngFor) the template instance is per
|
||||||
|
@ -742,8 +745,8 @@ export function getOrCreateTView(
|
||||||
// and not on embedded templates.
|
// and not on embedded templates.
|
||||||
|
|
||||||
return templateFn.ngPrivateData ||
|
return templateFn.ngPrivateData ||
|
||||||
(templateFn.ngPrivateData =
|
(templateFn.ngPrivateData = createTView(
|
||||||
createTView(-1, templateFn, consts, vars, directives, pipes, viewQuery) as never);
|
-1, templateFn, consts, vars, directives, pipes, viewQuery, schemas) as never);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -754,11 +757,13 @@ export function getOrCreateTView(
|
||||||
* @param consts The number of nodes, local refs, and pipes in this template
|
* @param consts The number of nodes, local refs, and pipes in this template
|
||||||
* @param directives Registry of directives for this view
|
* @param directives Registry of directives for this view
|
||||||
* @param pipes Registry of pipes for this view
|
* @param pipes Registry of pipes for this view
|
||||||
|
* @param viewQuery View queries for this view
|
||||||
|
* @param schemas Schemas for this view
|
||||||
*/
|
*/
|
||||||
export function createTView(
|
export function createTView(
|
||||||
viewIndex: number, templateFn: ComponentTemplate<any>| null, consts: number, vars: number,
|
viewIndex: number, templateFn: ComponentTemplate<any>| null, consts: number, vars: number,
|
||||||
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
directives: DirectiveDefListOrFactory | null, pipes: PipeDefListOrFactory | null,
|
||||||
viewQuery: ViewQueriesFunction<any>| null): TView {
|
viewQuery: ViewQueriesFunction<any>| null, schemas: SchemaMetadata[] | null): TView {
|
||||||
ngDevMode && ngDevMode.tView++;
|
ngDevMode && ngDevMode.tView++;
|
||||||
const bindingStartIndex = HEADER_OFFSET + consts;
|
const bindingStartIndex = HEADER_OFFSET + consts;
|
||||||
// This length does not yet contain host bindings from child directives because at this point,
|
// This length does not yet contain host bindings from child directives because at this point,
|
||||||
|
@ -792,6 +797,7 @@ export function createTView(
|
||||||
directiveRegistry: typeof directives === 'function' ? directives() : directives,
|
directiveRegistry: typeof directives === 'function' ? directives() : directives,
|
||||||
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
|
pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
|
||||||
firstChild: null,
|
firstChild: null,
|
||||||
|
schemas: schemas,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1218,7 +1224,7 @@ function elementPropertyInternal<T>(
|
||||||
} else if (tNode.type === TNodeType.Element) {
|
} else if (tNode.type === TNodeType.Element) {
|
||||||
if (ngDevMode) {
|
if (ngDevMode) {
|
||||||
validateAgainstEventProperties(propName);
|
validateAgainstEventProperties(propName);
|
||||||
validateAgainstUnknownProperties(element, propName, tNode);
|
validateAgainstUnknownProperties(lView, element, propName, tNode);
|
||||||
ngDevMode.rendererSetProperty++;
|
ngDevMode.rendererSetProperty++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1238,7 +1244,12 @@ function elementPropertyInternal<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAgainstUnknownProperties(
|
function validateAgainstUnknownProperties(
|
||||||
element: RElement | RComment, propName: string, tNode: TNode) {
|
hostView: LView, element: RElement | RComment, propName: string, tNode: TNode) {
|
||||||
|
// If the tag matches any of the schemas we shouldn't throw.
|
||||||
|
if (matchingSchemas(hostView, tNode.tagName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If prop is not a known property of the HTML element...
|
// If prop is not a known property of the HTML element...
|
||||||
if (!(propName in element) &&
|
if (!(propName in element) &&
|
||||||
// and we are in a browser context... (web worker nodes should be skipped)
|
// and we are in a browser context... (web worker nodes should be skipped)
|
||||||
|
@ -1251,6 +1262,22 @@ function validateAgainstUnknownProperties(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchingSchemas(hostView: LView, tagName: string | null): boolean {
|
||||||
|
const schemas = hostView[TVIEW].schemas;
|
||||||
|
|
||||||
|
if (schemas !== null) {
|
||||||
|
for (let i = 0; i < schemas.length; i++) {
|
||||||
|
const schema = schemas[i];
|
||||||
|
if (schema === NO_ERRORS_SCHEMA ||
|
||||||
|
schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores debugging data for this property binding on first template pass.
|
* Stores debugging data for this property binding on first template pass.
|
||||||
* This enables features like DebugElement.properties.
|
* This enables features like DebugElement.properties.
|
||||||
|
@ -2040,7 +2067,8 @@ function addComponentLogic<T>(
|
||||||
const native = getNativeByTNode(previousOrParentTNode, lView);
|
const native = getNativeByTNode(previousOrParentTNode, lView);
|
||||||
|
|
||||||
const tView = getOrCreateTView(
|
const tView = getOrCreateTView(
|
||||||
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery);
|
def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery,
|
||||||
|
def.schemas);
|
||||||
|
|
||||||
// Only component views should be added to the view tree directly. Embedded views are
|
// Only component views should be added to the view tree directly. Embedded views are
|
||||||
// accessed through their containers because they may be removed / re-added later.
|
// accessed through their containers because they may be removed / re-added later.
|
||||||
|
@ -2197,7 +2225,7 @@ export function template(
|
||||||
const tContainerNode = containerInternal(index, tagName || null, attrs || null);
|
const tContainerNode = containerInternal(index, tagName || null, attrs || null);
|
||||||
if (tView.firstTemplatePass) {
|
if (tView.firstTemplatePass) {
|
||||||
tContainerNode.tViews = createTView(
|
tContainerNode.tViews = createTView(
|
||||||
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null);
|
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor);
|
createDirectivesAndLocals(tView, lView, localRefs, localRefExtractor);
|
||||||
|
@ -2435,7 +2463,7 @@ function getOrCreateEmbeddedTView(
|
||||||
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
|
ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array');
|
||||||
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
|
if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) {
|
||||||
containerTViews[viewIndex] = createTView(
|
containerTViews[viewIndex] = createTView(
|
||||||
viewIndex, null, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null);
|
viewIndex, null, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
|
||||||
}
|
}
|
||||||
return containerTViews[viewIndex];
|
return containerTViews[viewIndex];
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ViewEncapsulation} from '../../core';
|
import {SchemaMetadata, ViewEncapsulation} from '../../core';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
import {CssSelectorList} from './projection';
|
import {CssSelectorList} from './projection';
|
||||||
|
|
||||||
|
@ -264,7 +264,6 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||||
readonly onPush: boolean;
|
readonly onPush: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
* Registry of directives and components that may be found in this view.
|
* Registry of directives and components that may be found in this view.
|
||||||
*
|
*
|
||||||
* The property is either an array of `DirectiveDef`s or a function which returns the array of
|
* The property is either an array of `DirectiveDef`s or a function which returns the array of
|
||||||
|
@ -280,6 +279,11 @@ export interface ComponentDef<T> extends DirectiveDef<T> {
|
||||||
*/
|
*/
|
||||||
pipeDefs: PipeDefListOrFactory|null;
|
pipeDefs: PipeDefListOrFactory|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The set of schemas that declare elements to be allowed in the component's template.
|
||||||
|
*/
|
||||||
|
schemas: SchemaMetadata[]|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to store the result of `noSideEffects` function so that it is not removed by closure
|
* Used to store the result of `noSideEffects` function so that it is not removed by closure
|
||||||
* compiler. The property should never be read.
|
* compiler. The property should never be read.
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {InjectionToken} from '../../di/injection_token';
|
||||||
import {Injector} from '../../di/injector';
|
import {Injector} from '../../di/injector';
|
||||||
import {Type} from '../../interface/type';
|
import {Type} from '../../interface/type';
|
||||||
import {QueryList} from '../../linker';
|
import {QueryList} from '../../linker';
|
||||||
|
import {SchemaMetadata} from '../../metadata';
|
||||||
import {Sanitizer} from '../../sanitization/security';
|
import {Sanitizer} from '../../sanitization/security';
|
||||||
|
|
||||||
import {LContainer} from './container';
|
import {LContainer} from './container';
|
||||||
|
@ -535,6 +536,11 @@ export interface TView {
|
||||||
* A list of indices for child directives that have content queries.
|
* A list of indices for child directives that have content queries.
|
||||||
*/
|
*/
|
||||||
contentQueries: number[]|null;
|
contentQueries: number[]|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of schemas that declare elements to be allowed inside the view.
|
||||||
|
*/
|
||||||
|
schemas: SchemaMetadata[]|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10}
|
export const enum RootContextFlags {Empty = 0b00, DetectChanges = 0b01, FlushPlayers = 0b10}
|
||||||
|
|
|
@ -115,6 +115,7 @@ export function compileNgModuleDefs(moduleType: NgModuleType, ngModule: NgModule
|
||||||
exports: flatten(ngModule.exports || EMPTY_ARRAY, resolveForwardRef)
|
exports: flatten(ngModule.exports || EMPTY_ARRAY, resolveForwardRef)
|
||||||
.map(expandModuleWithProviders),
|
.map(expandModuleWithProviders),
|
||||||
emitInline: true,
|
emitInline: true,
|
||||||
|
schemas: ngModule.schemas ? flatten(ngModule.schemas) : null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ngModuleDef;
|
return ngModuleDef;
|
||||||
|
@ -353,6 +354,7 @@ export function patchComponentDefWithScope<C>(
|
||||||
.filter(def => !!def);
|
.filter(def => !!def);
|
||||||
componentDef.pipeDefs = () =>
|
componentDef.pipeDefs = () =>
|
||||||
Array.from(transitiveScopes.compilation.pipes).map(pipe => getPipeDef(pipe) !);
|
Array.from(transitiveScopes.compilation.pipes).map(pipe => getPipeDef(pipe) !);
|
||||||
|
componentDef.schemas = transitiveScopes.schemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -375,6 +377,7 @@ export function transitiveScopesFor<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes: NgModuleTransitiveScopes = {
|
const scopes: NgModuleTransitiveScopes = {
|
||||||
|
schemas: def.schemas || null,
|
||||||
compilation: {
|
compilation: {
|
||||||
directives: new Set<any>(),
|
directives: new Set<any>(),
|
||||||
pipes: new Set<any>(),
|
pipes: new Set<any>(),
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory, ɵivyEnabled as ivyEnabled} from '@angular/core';
|
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, InjectionToken, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, Self, Type, forwardRef, getModuleFactory, ɵivyEnabled as ivyEnabled} from '@angular/core';
|
||||||
import {Console} from '@angular/core/src/console';
|
import {Console} from '@angular/core/src/console';
|
||||||
import {InjectableDef, defineInjectable} from '@angular/core/src/di/interface/defs';
|
import {InjectableDef, defineInjectable} from '@angular/core/src/di/interface/defs';
|
||||||
import {getNgModuleDef} from '@angular/core/src/render3/definition';
|
import {getNgModuleDef} from '@angular/core/src/render3/definition';
|
||||||
|
@ -266,22 +266,29 @@ function declareTests(config?: {useJit: boolean}) {
|
||||||
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'someUnknownProp'/);
|
expect(() => fixture.detectChanges()).toThrowError(/Can't bind to 'someUnknownProp'/);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-819: ngtsc compiler should support schemas')
|
it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
|
||||||
.it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
|
() => {
|
||||||
() => {
|
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
||||||
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
|
class ComponentUsingInvalidProperty {
|
||||||
class ComponentUsingInvalidProperty {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
declarations: [ComponentUsingInvalidProperty]
|
declarations: [ComponentUsingInvalidProperty],
|
||||||
})
|
|
||||||
class SomeModule {
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(() => createModule(SomeModule)).not.toThrow();
|
// Note that we need to add the component to `entryComponents`, because of the
|
||||||
});
|
// `createComp` call below. In Ivy the property validation happens during the
|
||||||
|
// update phase so we need to create the component, in order for it to run.
|
||||||
|
entryComponents: [ComponentUsingInvalidProperty]
|
||||||
|
})
|
||||||
|
class SomeModule {
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
const fixture = createComp(ComponentUsingInvalidProperty, SomeModule);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('id', () => {
|
describe('id', () => {
|
||||||
|
|
|
@ -2320,8 +2320,8 @@ describe('di', () => {
|
||||||
describe('getOrCreateNodeInjector', () => {
|
describe('getOrCreateNodeInjector', () => {
|
||||||
it('should handle initial undefined state', () => {
|
it('should handle initial undefined state', () => {
|
||||||
const contentView = createLView(
|
const contentView = createLView(
|
||||||
null, createTView(-1, null, 1, 0, null, null, null), null, LViewFlags.CheckAlways, null,
|
null, createTView(-1, null, 1, 0, null, null, null, null), null, LViewFlags.CheckAlways,
|
||||||
null, {} as any, {} as any);
|
null, null, {} as any, {} as any);
|
||||||
const oldView = enterView(contentView, null);
|
const oldView = enterView(contentView, null);
|
||||||
try {
|
try {
|
||||||
const parentTNode = createNodeAtIndex(0, TNodeType.Element, null, null, null);
|
const parentTNode = createNodeAtIndex(0, TNodeType.Element, null, null, null);
|
||||||
|
|
|
@ -34,8 +34,8 @@ describe('style and class based bindings', () => {
|
||||||
const rootContext =
|
const rootContext =
|
||||||
createRootContext(requestAnimationFrame.bind(window), playerHandler || null);
|
createRootContext(requestAnimationFrame.bind(window), playerHandler || null);
|
||||||
const lView = createLView(
|
const lView = createLView(
|
||||||
null, createTView(-1, null, 1, 0, null, null, null), rootContext, LViewFlags.IsRoot, null,
|
null, createTView(-1, null, 1, 0, null, null, null, null), rootContext, LViewFlags.IsRoot,
|
||||||
null, domRendererFactory3, domRendererFactory3.createRenderer(element, null));
|
null, null, domRendererFactory3, domRendererFactory3.createRenderer(element, null));
|
||||||
return lView;
|
return lView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue