feat(ivy): support injectable sanitization service (#23809)
PR Close #23809
This commit is contained in:
@ -11,6 +11,7 @@
|
|||||||
import {Type} from '../core';
|
import {Type} from '../core';
|
||||||
import {Injector} from '../di/injector';
|
import {Injector} from '../di/injector';
|
||||||
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_factory';
|
||||||
|
import {Sanitizer} from '../sanitization/security';
|
||||||
|
|
||||||
import {assertComponentType, assertNotNull} from './assert';
|
import {assertComponentType, assertNotNull} from './assert';
|
||||||
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
import {queueInitHooks, queueLifecycleHooks} from './hooks';
|
||||||
@ -29,6 +30,9 @@ export interface CreateComponentOptions {
|
|||||||
/** Which renderer factory to use. */
|
/** Which renderer factory to use. */
|
||||||
rendererFactory?: RendererFactory3;
|
rendererFactory?: RendererFactory3;
|
||||||
|
|
||||||
|
/** A custom sanitizer instance */
|
||||||
|
sanitizer?: Sanitizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host element on which the component will be bootstrapped. If not specified,
|
* Host element on which the component will be bootstrapped. If not specified,
|
||||||
* the component definition's `tag` is used to query the existing DOM for the
|
* the component definition's `tag` is used to query the existing DOM for the
|
||||||
@ -120,6 +124,7 @@ export function renderComponent<T>(
|
|||||||
opts: CreateComponentOptions = {}): T {
|
opts: CreateComponentOptions = {}): T {
|
||||||
ngDevMode && assertComponentType(componentType);
|
ngDevMode && assertComponentType(componentType);
|
||||||
const rendererFactory = opts.rendererFactory || domRendererFactory3;
|
const rendererFactory = opts.rendererFactory || domRendererFactory3;
|
||||||
|
const sanitizer = opts.sanitizer || null;
|
||||||
const componentDef = (componentType as ComponentType<T>).ngComponentDef as ComponentDef<T>;
|
const componentDef = (componentType as ComponentType<T>).ngComponentDef as ComponentDef<T>;
|
||||||
if (componentDef.type != componentType) componentDef.type = componentType;
|
if (componentDef.type != componentType) componentDef.type = componentType;
|
||||||
let component: T;
|
let component: T;
|
||||||
@ -144,7 +149,7 @@ export function renderComponent<T>(
|
|||||||
if (rendererFactory.begin) rendererFactory.begin();
|
if (rendererFactory.begin) rendererFactory.begin();
|
||||||
|
|
||||||
// Create element node at index 0 in data array
|
// Create element node at index 0 in data array
|
||||||
elementNode = hostElement(componentTag, hostNode, componentDef);
|
elementNode = hostElement(componentTag, hostNode, componentDef, sanitizer);
|
||||||
|
|
||||||
// Create directive instance with factory() and store at index 0 in directives array
|
// Create directive instance with factory() and store at index 0 in directives array
|
||||||
component = rootContext.component =
|
component = rootContext.component =
|
||||||
|
@ -25,6 +25,7 @@ import {isDifferent, stringify} from './util';
|
|||||||
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
import {executeHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||||
import {ViewRef} from './view_ref';
|
import {ViewRef} from './view_ref';
|
||||||
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
import {throwCyclicDependencyError, throwErrorIfNoChangesMode, throwMultipleComponentError} from './errors';
|
||||||
|
import {Sanitizer} from '../sanitization/security';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive (D) sets a property on all component instances using this constant as a key and the
|
* Directive (D) sets a property on all component instances using this constant as a key and the
|
||||||
@ -42,7 +43,7 @@ const _CLEAN_PROMISE = Promise.resolve(null);
|
|||||||
/**
|
/**
|
||||||
* Function used to sanitize the value before writing it into the renderer.
|
* Function used to sanitize the value before writing it into the renderer.
|
||||||
*/
|
*/
|
||||||
export type Sanitizer = (value: any) => string;
|
export type SanitizerFn = (value: any) => string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive and element indices for top-level directive.
|
* Directive and element indices for top-level directive.
|
||||||
@ -84,6 +85,10 @@ export function getRenderer(): Renderer3 {
|
|||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCurrentSanitizer(): Sanitizer|null {
|
||||||
|
return currentView && currentView.sanitizer;
|
||||||
|
}
|
||||||
|
|
||||||
/** Used to set the parent property when nodes are created. */
|
/** Used to set the parent property when nodes are created. */
|
||||||
let previousOrParentNode: LNode;
|
let previousOrParentNode: LNode;
|
||||||
|
|
||||||
@ -298,7 +303,7 @@ export function executeInitAndContentHooks(): void {
|
|||||||
|
|
||||||
export function createLView<T>(
|
export function createLView<T>(
|
||||||
viewId: number, renderer: Renderer3, tView: TView, template: ComponentTemplate<T>| null,
|
viewId: number, renderer: Renderer3, tView: TView, template: ComponentTemplate<T>| null,
|
||||||
context: T | null, flags: LViewFlags): LView {
|
context: T | null, flags: LViewFlags, sanitizer?: Sanitizer | null): LView {
|
||||||
const newView = {
|
const newView = {
|
||||||
parent: currentView,
|
parent: currentView,
|
||||||
id: viewId, // -1 for component views
|
id: viewId, // -1 for component views
|
||||||
@ -320,6 +325,7 @@ export function createLView<T>(
|
|||||||
lifecycleStage: LifecycleStage.Init,
|
lifecycleStage: LifecycleStage.Init,
|
||||||
queries: null,
|
queries: null,
|
||||||
injector: currentView && currentView.injector,
|
injector: currentView && currentView.injector,
|
||||||
|
sanitizer: sanitizer || null
|
||||||
};
|
};
|
||||||
|
|
||||||
return newView;
|
return newView;
|
||||||
@ -450,8 +456,8 @@ function resetApplicationState() {
|
|||||||
export function renderTemplate<T>(
|
export function renderTemplate<T>(
|
||||||
hostNode: RElement, template: ComponentTemplate<T>, context: T,
|
hostNode: RElement, template: ComponentTemplate<T>, context: T,
|
||||||
providedRendererFactory: RendererFactory3, host: LElementNode | null,
|
providedRendererFactory: RendererFactory3, host: LElementNode | null,
|
||||||
directives?: DirectiveDefListOrFactory | null,
|
directives?: DirectiveDefListOrFactory | null, pipes?: PipeDefListOrFactory | null,
|
||||||
pipes?: PipeDefListOrFactory | null): LElementNode {
|
sanitizer?: Sanitizer | null): LElementNode {
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
resetApplicationState();
|
resetApplicationState();
|
||||||
rendererFactory = providedRendererFactory;
|
rendererFactory = providedRendererFactory;
|
||||||
@ -460,7 +466,7 @@ export function renderTemplate<T>(
|
|||||||
null, LNodeType.Element, hostNode,
|
null, LNodeType.Element, hostNode,
|
||||||
createLView(
|
createLView(
|
||||||
-1, providedRendererFactory.createRenderer(null, null), tView, null, {},
|
-1, providedRendererFactory.createRenderer(null, null), tView, null, {},
|
||||||
LViewFlags.CheckAlways));
|
LViewFlags.CheckAlways, sanitizer));
|
||||||
}
|
}
|
||||||
const hostView = host.data !;
|
const hostView = host.data !;
|
||||||
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
|
ngDevMode && assertNotNull(hostView, 'Host node should have an LView defined in host.data.');
|
||||||
@ -491,7 +497,8 @@ export function renderEmbeddedTemplate<T>(
|
|||||||
previousOrParentNode = null !;
|
previousOrParentNode = null !;
|
||||||
|
|
||||||
if (viewNode == null) {
|
if (viewNode == null) {
|
||||||
const lView = createLView(-1, renderer, tView, template, context, LViewFlags.CheckAlways);
|
const lView = createLView(
|
||||||
|
-1, renderer, tView, template, context, LViewFlags.CheckAlways, getCurrentSanitizer());
|
||||||
|
|
||||||
viewNode = createLNode(null, LNodeType.View, null, lView);
|
viewNode = createLNode(null, LNodeType.View, null, lView);
|
||||||
rf = RenderFlags.Create;
|
rf = RenderFlags.Create;
|
||||||
@ -859,13 +866,14 @@ export function locateHostElement(
|
|||||||
* @returns LElementNode created
|
* @returns LElementNode created
|
||||||
*/
|
*/
|
||||||
export function hostElement(
|
export function hostElement(
|
||||||
tag: string, rNode: RElement | null, def: ComponentDef<any>): LElementNode {
|
tag: string, rNode: RElement | null, def: ComponentDef<any>,
|
||||||
|
sanitizer?: Sanitizer | null): LElementNode {
|
||||||
resetApplicationState();
|
resetApplicationState();
|
||||||
const node = createLNode(
|
const node = createLNode(
|
||||||
0, LNodeType.Element, rNode,
|
0, LNodeType.Element, rNode,
|
||||||
createLView(
|
createLView(
|
||||||
-1, renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, null,
|
-1, renderer, getOrCreateTView(def.template, def.directiveDefs, def.pipeDefs), null, null,
|
||||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));
|
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, sanitizer));
|
||||||
|
|
||||||
if (firstTemplatePass) {
|
if (firstTemplatePass) {
|
||||||
node.tNode = createTNode(tag as string, null, null);
|
node.tNode = createTNode(tag as string, null, null);
|
||||||
@ -958,7 +966,7 @@ export function elementEnd() {
|
|||||||
* @param sanitizer An optional function used to sanitize the value.
|
* @param sanitizer An optional function used to sanitize the value.
|
||||||
*/
|
*/
|
||||||
export function elementAttribute(
|
export function elementAttribute(
|
||||||
index: number, name: string, value: any, sanitizer?: Sanitizer): void {
|
index: number, name: string, value: any, sanitizer?: SanitizerFn): void {
|
||||||
if (value !== NO_CHANGE) {
|
if (value !== NO_CHANGE) {
|
||||||
const element: LElementNode = data[index];
|
const element: LElementNode = data[index];
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@ -989,7 +997,7 @@ export function elementAttribute(
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export function elementProperty<T>(
|
export function elementProperty<T>(
|
||||||
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: Sanitizer): void {
|
index: number, propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void {
|
||||||
if (value === NO_CHANGE) return;
|
if (value === NO_CHANGE) return;
|
||||||
const node = data[index] as LElementNode;
|
const node = data[index] as LElementNode;
|
||||||
const tNode = node.tNode !;
|
const tNode = node.tNode !;
|
||||||
@ -1152,10 +1160,10 @@ export function elementClass<T>(index: number, value: T | NO_CHANGE): void {
|
|||||||
export function elementStyleNamed<T>(
|
export function elementStyleNamed<T>(
|
||||||
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string): void;
|
index: number, styleName: string, value: T | NO_CHANGE, suffix?: string): void;
|
||||||
export function elementStyleNamed<T>(
|
export function elementStyleNamed<T>(
|
||||||
index: number, styleName: string, value: T | NO_CHANGE, sanitizer?: Sanitizer): void;
|
index: number, styleName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn): void;
|
||||||
export function elementStyleNamed<T>(
|
export function elementStyleNamed<T>(
|
||||||
index: number, styleName: string, value: T | NO_CHANGE,
|
index: number, styleName: string, value: T | NO_CHANGE,
|
||||||
suffixOrSanitizer?: string | Sanitizer): void {
|
suffixOrSanitizer?: string | SanitizerFn): void {
|
||||||
if (value !== NO_CHANGE) {
|
if (value !== NO_CHANGE) {
|
||||||
const lElement: LElementNode = data[index];
|
const lElement: LElementNode = data[index];
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@ -1305,7 +1313,8 @@ function addComponentLogic<T>(index: number, instance: T, def: ComponentDef<T>):
|
|||||||
currentView, createLView(
|
currentView, createLView(
|
||||||
-1, rendererFactory.createRenderer(
|
-1, rendererFactory.createRenderer(
|
||||||
previousOrParentNode.native as RElement, def.rendererType),
|
previousOrParentNode.native as RElement, def.rendererType),
|
||||||
tView, null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways));
|
tView, null, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways,
|
||||||
|
getCurrentSanitizer()));
|
||||||
|
|
||||||
(previousOrParentNode.data as any) = hostView;
|
(previousOrParentNode.data as any) = hostView;
|
||||||
(hostView.node as any) = previousOrParentNode;
|
(hostView.node as any) = previousOrParentNode;
|
||||||
@ -1596,7 +1605,7 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags {
|
|||||||
// When we create a new LView, we always reset the state of the instructions.
|
// When we create a new LView, we always reset the state of the instructions.
|
||||||
const newView = createLView(
|
const newView = createLView(
|
||||||
viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null,
|
viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null,
|
||||||
LViewFlags.CheckAlways);
|
LViewFlags.CheckAlways, getCurrentSanitizer());
|
||||||
if (lContainer.queries) {
|
if (lContainer.queries) {
|
||||||
newView.queries = lContainer.queries.enterView(lContainer.nextIndex);
|
newView.queries = lContainer.queries.enterView(lContainer.nextIndex);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Injector} from '../../di/injector';
|
import {Injector} from '../../di/injector';
|
||||||
|
import {Sanitizer} from '../../sanitization/security';
|
||||||
|
|
||||||
import {LContainer} from './container';
|
import {LContainer} from './container';
|
||||||
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDef, PipeDefList} from './definition';
|
||||||
import {LElementNode, LViewNode, TNode} from './node';
|
import {LElementNode, LViewNode, TNode} from './node';
|
||||||
@ -195,6 +197,11 @@ export interface LView {
|
|||||||
* An optional Module Injector to be used as fall back after Element Injectors are consulted.
|
* An optional Module Injector to be used as fall back after Element Injectors are consulted.
|
||||||
*/
|
*/
|
||||||
injector: Injector|null;
|
injector: Injector|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional custom sanitizer
|
||||||
|
*/
|
||||||
|
sanitizer: Sanitizer|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Flags associated with an LView (saved in LView.flags) */
|
/** Flags associated with an LView (saved in LView.flags) */
|
||||||
|
@ -6,9 +6,11 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {getCurrentSanitizer} from '../render3/instructions';
|
||||||
import {stringify} from '../render3/util';
|
import {stringify} from '../render3/util';
|
||||||
|
|
||||||
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
||||||
|
import {SecurityContext} from './security';
|
||||||
import {_sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
|
import {_sanitizeStyle as _sanitizeStyle} from './style_sanitizer';
|
||||||
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
||||||
|
|
||||||
@ -79,6 +81,10 @@ export interface TrustedResourceUrlString extends TrustedString {
|
|||||||
* and urls have been removed.
|
* and urls have been removed.
|
||||||
*/
|
*/
|
||||||
export function sanitizeHtml(unsafeHtml: any): string {
|
export function sanitizeHtml(unsafeHtml: any): string {
|
||||||
|
const s = getCurrentSanitizer();
|
||||||
|
if (s) {
|
||||||
|
return s.sanitize(SecurityContext.HTML, unsafeHtml) || '';
|
||||||
|
}
|
||||||
if (unsafeHtml instanceof String && (unsafeHtml as TrustedHtmlString)[BRAND] === 'Html') {
|
if (unsafeHtml instanceof String && (unsafeHtml as TrustedHtmlString)[BRAND] === 'Html') {
|
||||||
return unsafeHtml.toString();
|
return unsafeHtml.toString();
|
||||||
}
|
}
|
||||||
@ -99,6 +105,10 @@ export function sanitizeHtml(unsafeHtml: any): string {
|
|||||||
* dangerous javascript and urls have been removed.
|
* dangerous javascript and urls have been removed.
|
||||||
*/
|
*/
|
||||||
export function sanitizeStyle(unsafeStyle: any): string {
|
export function sanitizeStyle(unsafeStyle: any): string {
|
||||||
|
const s = getCurrentSanitizer();
|
||||||
|
if (s) {
|
||||||
|
return s.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
|
||||||
|
}
|
||||||
if (unsafeStyle instanceof String && (unsafeStyle as TrustedStyleString)[BRAND] === 'Style') {
|
if (unsafeStyle instanceof String && (unsafeStyle as TrustedStyleString)[BRAND] === 'Style') {
|
||||||
return unsafeStyle.toString();
|
return unsafeStyle.toString();
|
||||||
}
|
}
|
||||||
@ -120,6 +130,10 @@ export function sanitizeStyle(unsafeStyle: any): string {
|
|||||||
* all of the dangerous javascript has been removed.
|
* all of the dangerous javascript has been removed.
|
||||||
*/
|
*/
|
||||||
export function sanitizeUrl(unsafeUrl: any): string {
|
export function sanitizeUrl(unsafeUrl: any): string {
|
||||||
|
const s = getCurrentSanitizer();
|
||||||
|
if (s) {
|
||||||
|
return s.sanitize(SecurityContext.URL, unsafeUrl) || '';
|
||||||
|
}
|
||||||
if (unsafeUrl instanceof String && (unsafeUrl as TrustedUrlString)[BRAND] === 'Url') {
|
if (unsafeUrl instanceof String && (unsafeUrl as TrustedUrlString)[BRAND] === 'Url') {
|
||||||
return unsafeUrl.toString();
|
return unsafeUrl.toString();
|
||||||
}
|
}
|
||||||
@ -136,6 +150,10 @@ export function sanitizeUrl(unsafeUrl: any): string {
|
|||||||
* only trusted `url`s have been allowed to pass.
|
* only trusted `url`s have been allowed to pass.
|
||||||
*/
|
*/
|
||||||
export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
|
export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
|
||||||
|
const s = getCurrentSanitizer();
|
||||||
|
if (s) {
|
||||||
|
return s.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '';
|
||||||
|
}
|
||||||
if (unsafeResourceUrl instanceof String &&
|
if (unsafeResourceUrl instanceof String &&
|
||||||
(unsafeResourceUrl as TrustedResourceUrlString)[BRAND] === 'ResourceUrl') {
|
(unsafeResourceUrl as TrustedResourceUrlString)[BRAND] === 'ResourceUrl') {
|
||||||
return unsafeResourceUrl.toString();
|
return unsafeResourceUrl.toString();
|
||||||
@ -153,6 +171,10 @@ export function sanitizeResourceUrl(unsafeResourceUrl: any): string {
|
|||||||
* because only trusted `scripts`s have been allowed to pass.
|
* because only trusted `scripts`s have been allowed to pass.
|
||||||
*/
|
*/
|
||||||
export function sanitizeScript(unsafeScript: any): string {
|
export function sanitizeScript(unsafeScript: any): string {
|
||||||
|
const s = getCurrentSanitizer();
|
||||||
|
if (s) {
|
||||||
|
return s.sanitize(SecurityContext.SCRIPT, unsafeScript) || '';
|
||||||
|
}
|
||||||
if (unsafeScript instanceof String && (unsafeScript as TrustedScriptString)[BRAND] === 'Script') {
|
if (unsafeScript instanceof String && (unsafeScript as TrustedScriptString)[BRAND] === 'Script') {
|
||||||
return unsafeScript.toString();
|
return unsafeScript.toString();
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,9 @@
|
|||||||
{
|
{
|
||||||
"name": "firstTemplatePass"
|
"name": "firstTemplatePass"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getCurrentSanitizer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getDirectiveInstance"
|
"name": "getDirectiveInstance"
|
||||||
},
|
},
|
||||||
|
@ -383,6 +383,9 @@
|
|||||||
{
|
{
|
||||||
"name": "generatePropertyAliases"
|
"name": "generatePropertyAliases"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getCurrentSanitizer"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "getDirectiveInstance"
|
"name": "getDirectiveInstance"
|
||||||
},
|
},
|
||||||
|
@ -13,17 +13,28 @@ import {defineComponent} from '../../src/render3/definition';
|
|||||||
import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
|
import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {LElementNode, LNode} from '../../src/render3/interfaces/node';
|
import {LElementNode, LNode} from '../../src/render3/interfaces/node';
|
||||||
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||||
import {bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
|
import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||||
|
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
||||||
|
|
||||||
import {NgForOf} from './common_with_def';
|
import {NgForOf} from './common_with_def';
|
||||||
import {ComponentFixture, TemplateFixture} from './render_util';
|
import {ComponentFixture, TemplateFixture} from './render_util';
|
||||||
|
|
||||||
describe('instructions', () => {
|
describe('instructions', () => {
|
||||||
|
function createAnchor() {
|
||||||
|
elementStart(0, 'a');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
function createDiv() {
|
function createDiv() {
|
||||||
elementStart(0, 'div');
|
elementStart(0, 'div');
|
||||||
elementEnd();
|
elementEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createScript() {
|
||||||
|
elementStart(0, 'script');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
|
||||||
describe('elementAttribute', () => {
|
describe('elementAttribute', () => {
|
||||||
it('should use sanitizer function', () => {
|
it('should use sanitizer function', () => {
|
||||||
const t = new TemplateFixture(createDiv);
|
const t = new TemplateFixture(createDiv);
|
||||||
@ -177,4 +188,210 @@ describe('instructions', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sanitization injection compatibility', () => {
|
||||||
|
it('should work for url sanitization', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => `${value}-sanitized`);
|
||||||
|
const t = new TemplateFixture(createAnchor, undefined, null, null, s);
|
||||||
|
const inputValue = 'http://foo';
|
||||||
|
const outputValue = 'http://foo-sanitized';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'href', inputValue, sanitizeUrl));
|
||||||
|
expect(t.html).toEqual(`<a href="${outputValue}"></a>`);
|
||||||
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass url sanitization if marked by the service', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createAnchor, undefined, null, null, s);
|
||||||
|
const inputValue = s.bypassSecurityTrustUrl('http://foo');
|
||||||
|
const outputValue = 'http://foo';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'href', inputValue, sanitizeUrl));
|
||||||
|
expect(t.html).toEqual(`<a href="${outputValue}"></a>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass ivy-level url sanitization if a custom sanitizer is used', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createAnchor, undefined, null, null, s);
|
||||||
|
const inputValue = bypassSanitizationTrustUrl('http://foo');
|
||||||
|
const outputValue = 'http://foo-ivy';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'href', inputValue, sanitizeUrl));
|
||||||
|
expect(t.html).toEqual(`<a href="${outputValue}"></a>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for style sanitization', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => `color:blue`);
|
||||||
|
const t = new TemplateFixture(createDiv, undefined, null, null, s);
|
||||||
|
const inputValue = 'color:red';
|
||||||
|
const outputValue = 'color:blue';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'style', inputValue, sanitizeStyle));
|
||||||
|
expect(stripStyleWsCharacters(t.html)).toEqual(`<div style="${outputValue}"></div>`);
|
||||||
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass style sanitization if marked by the service', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createDiv, undefined, null, null, s);
|
||||||
|
const inputValue = s.bypassSecurityTrustStyle('color:maroon');
|
||||||
|
const outputValue = 'color:maroon';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'style', inputValue, sanitizeStyle));
|
||||||
|
expect(stripStyleWsCharacters(t.html)).toEqual(`<div style="${outputValue}"></div>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass ivy-level style sanitization if a custom sanitizer is used', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createDiv, undefined, null, null, s);
|
||||||
|
const inputValue = bypassSanitizationTrustStyle('font-family:foo');
|
||||||
|
const outputValue = 'font-family:foo-ivy';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'style', inputValue, sanitizeStyle));
|
||||||
|
expect(stripStyleWsCharacters(t.html)).toEqual(`<div style="${outputValue}"></div>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for resourceUrl sanitization', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => `${value}-sanitized`);
|
||||||
|
const t = new TemplateFixture(createScript, undefined, null, null, s);
|
||||||
|
const inputValue = 'http://resource';
|
||||||
|
const outputValue = 'http://resource-sanitized';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'src', inputValue, sanitizeResourceUrl));
|
||||||
|
expect(t.html).toEqual(`<script src="${outputValue}"></script>`);
|
||||||
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass resourceUrl sanitization if marked by the service', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createScript, undefined, null, null, s);
|
||||||
|
const inputValue = s.bypassSecurityTrustResourceUrl('file://all-my-secrets.pdf');
|
||||||
|
const outputValue = 'file://all-my-secrets.pdf';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'src', inputValue, sanitizeResourceUrl));
|
||||||
|
expect(t.html).toEqual(`<script src="${outputValue}"></script>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass ivy-level resourceUrl sanitization if a custom sanitizer is used', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createScript, undefined, null, null, s);
|
||||||
|
const inputValue = bypassSanitizationTrustResourceUrl('file://all-my-secrets.pdf');
|
||||||
|
const outputValue = 'file://all-my-secrets.pdf-ivy';
|
||||||
|
|
||||||
|
t.update(() => elementAttribute(0, 'src', inputValue, sanitizeResourceUrl));
|
||||||
|
expect(t.html).toEqual(`<script src="${outputValue}"></script>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for script sanitization', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => `${value} //sanitized`);
|
||||||
|
const t = new TemplateFixture(createScript, undefined, null, null, s);
|
||||||
|
const inputValue = 'fn();';
|
||||||
|
const outputValue = 'fn(); //sanitized';
|
||||||
|
|
||||||
|
t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeScript));
|
||||||
|
expect(t.html).toEqual(`<script>${outputValue}</script>`);
|
||||||
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass script sanitization if marked by the service', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createScript, undefined, null, null, s);
|
||||||
|
const inputValue = s.bypassSecurityTrustScript('alert("bar")');
|
||||||
|
const outputValue = 'alert("bar")';
|
||||||
|
|
||||||
|
t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeScript));
|
||||||
|
expect(t.html).toEqual(`<script>${outputValue}</script>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass ivy-level script sanitization if a custom sanitizer is used', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createScript, undefined, null, null, s);
|
||||||
|
const inputValue = bypassSanitizationTrustScript('alert("bar")');
|
||||||
|
const outputValue = 'alert("bar")-ivy';
|
||||||
|
|
||||||
|
t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeScript));
|
||||||
|
expect(t.html).toEqual(`<script>${outputValue}</script>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work for html sanitization', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => `${value} <!--sanitized-->`);
|
||||||
|
const t = new TemplateFixture(createDiv, undefined, null, null, s);
|
||||||
|
const inputValue = '<header></header>';
|
||||||
|
const outputValue = '<header></header> <!--sanitized-->';
|
||||||
|
|
||||||
|
t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeHtml));
|
||||||
|
expect(t.html).toEqual(`<div>${outputValue}</div>`);
|
||||||
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass html sanitization if marked by the service', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createDiv, undefined, null, null, s);
|
||||||
|
const inputValue = s.bypassSecurityTrustHtml('<div onclick="alert(123)"></div>');
|
||||||
|
const outputValue = '<div onclick="alert(123)"></div>';
|
||||||
|
|
||||||
|
t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeHtml));
|
||||||
|
expect(t.html).toEqual(`<div>${outputValue}</div>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bypass ivy-level script sanitization if a custom sanitizer is used', () => {
|
||||||
|
const s = new LocalMockSanitizer(value => '');
|
||||||
|
const t = new TemplateFixture(createDiv, undefined, null, null, s);
|
||||||
|
const inputValue = bypassSanitizationTrustHtml('<div onclick="alert(123)"></div>');
|
||||||
|
const outputValue = '<div onclick="alert(123)"></div>-ivy';
|
||||||
|
|
||||||
|
t.update(() => elementProperty(0, 'innerHTML', inputValue, sanitizeHtml));
|
||||||
|
expect(t.html).toEqual(`<div>${outputValue}</div>`);
|
||||||
|
expect(s.lastSanitizedValue).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class LocalSanitizedValue {
|
||||||
|
constructor(public value: any) {}
|
||||||
|
|
||||||
|
toString() { return this.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalMockSanitizer implements Sanitizer {
|
||||||
|
public lastSanitizedValue: string|null;
|
||||||
|
|
||||||
|
constructor(private _interceptor: (value: string|null|any) => string) {}
|
||||||
|
|
||||||
|
sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null|any): string|null {
|
||||||
|
if (value instanceof String) {
|
||||||
|
return value.toString() + '-ivy';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof LocalSanitizedValue) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lastSanitizedValue = this._interceptor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bypassSecurityTrustHtml(value: string) { return new LocalSanitizedValue(value); }
|
||||||
|
|
||||||
|
bypassSecurityTrustStyle(value: string) { return new LocalSanitizedValue(value); }
|
||||||
|
|
||||||
|
bypassSecurityTrustScript(value: string) { return new LocalSanitizedValue(value); }
|
||||||
|
|
||||||
|
bypassSecurityTrustUrl(value: string) { return new LocalSanitizedValue(value); }
|
||||||
|
|
||||||
|
bypassSecurityTrustResourceUrl(value: string) { return new LocalSanitizedValue(value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripStyleWsCharacters(value: string): string {
|
||||||
|
// color: blue; => color:blue
|
||||||
|
return value.replace(/;/g, '').replace(/:\s+/g, ':');
|
||||||
|
}
|
||||||
|
@ -11,6 +11,8 @@ import {RenderFlags} from '@angular/core/src/render3';
|
|||||||
import {defineComponent, defineDirective} from '../../src/render3/index';
|
import {defineComponent, defineDirective} from '../../src/render3/index';
|
||||||
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, elementStyleNamed, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions';
|
||||||
import {LViewFlags} from '../../src/render3/interfaces/view';
|
import {LViewFlags} from '../../src/render3/interfaces/view';
|
||||||
|
import {sanitizeUrl} from '../../src/sanitization/sanitization';
|
||||||
|
import {Sanitizer, SecurityContext} from '../../src/sanitization/security';
|
||||||
|
|
||||||
import {ComponentFixture, containerEl, renderToHtml} from './render_util';
|
import {ComponentFixture, containerEl, renderToHtml} from './render_util';
|
||||||
|
|
||||||
@ -847,4 +849,65 @@ describe('render3 integration test', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sanitization', () => {
|
||||||
|
it('should sanitize data using the provided sanitization interface', () => {
|
||||||
|
class SanitizationComp {
|
||||||
|
static ngComponentDef = defineComponent({
|
||||||
|
type: SanitizationComp,
|
||||||
|
selectors: [['sanitize-this']],
|
||||||
|
factory: () => new SanitizationComp(),
|
||||||
|
template: (rf: RenderFlags, ctx: SanitizationComp) => {
|
||||||
|
if (rf & RenderFlags.Create) {
|
||||||
|
elementStart(0, 'a');
|
||||||
|
elementEnd();
|
||||||
|
}
|
||||||
|
if (rf & RenderFlags.Update) {
|
||||||
|
elementProperty(0, 'href', bind(ctx.href), sanitizeUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private href = '';
|
||||||
|
|
||||||
|
updateLink(href: any) { this.href = href; }
|
||||||
|
}
|
||||||
|
|
||||||
|
const sanitizer = new LocalSanitizer((value) => { return 'http://bar'; });
|
||||||
|
|
||||||
|
const fixture = new ComponentFixture(SanitizationComp, {sanitizer});
|
||||||
|
fixture.component.updateLink('http://foo');
|
||||||
|
fixture.update();
|
||||||
|
|
||||||
|
const element = fixture.hostElement.querySelector('a') !;
|
||||||
|
expect(element.getAttribute('href')).toEqual('http://bar');
|
||||||
|
|
||||||
|
fixture.component.updateLink(sanitizer.bypassSecurityTrustUrl('http://foo'));
|
||||||
|
fixture.update();
|
||||||
|
|
||||||
|
expect(element.getAttribute('href')).toEqual('http://foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
class LocalSanitizedValue {
|
||||||
|
constructor(public value: any) {}
|
||||||
|
toString() { return this.value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalSanitizer implements Sanitizer {
|
||||||
|
constructor(private _interceptor: (value: string|null|any) => string) {}
|
||||||
|
|
||||||
|
sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null): string|null {
|
||||||
|
if (value instanceof LocalSanitizedValue) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
return this._interceptor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bypassSecurityTrustHtml(value: string) {}
|
||||||
|
bypassSecurityTrustStyle(value: string) {}
|
||||||
|
bypassSecurityTrustScript(value: string) {}
|
||||||
|
bypassSecurityTrustResourceUrl(value: string) {}
|
||||||
|
|
||||||
|
bypassSecurityTrustUrl(value: string) { return new LocalSanitizedValue(value); }
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ import {NG_HOST_SYMBOL, renderTemplate} from '../../src/render3/instructions';
|
|||||||
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
|
import {DirectiveDefList, DirectiveDefListOrFactory, DirectiveTypesOrFactory, PipeDef, PipeDefList, PipeDefListOrFactory, PipeTypesOrFactory} from '../../src/render3/interfaces/definition';
|
||||||
import {LElementNode} from '../../src/render3/interfaces/node';
|
import {LElementNode} from '../../src/render3/interfaces/node';
|
||||||
import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
import {RElement, RText, Renderer3, RendererFactory3, domRendererFactory3} from '../../src/render3/interfaces/renderer';
|
||||||
|
import {Sanitizer} from '../../src/sanitization/security';
|
||||||
import {Type} from '../../src/type';
|
import {Type} from '../../src/type';
|
||||||
|
|
||||||
import {getRendererFactory2} from './imported_renderer2';
|
import {getRendererFactory2} from './imported_renderer2';
|
||||||
@ -51,6 +52,8 @@ export class TemplateFixture extends BaseFixture {
|
|||||||
hostNode: LElementNode;
|
hostNode: LElementNode;
|
||||||
private _directiveDefs: DirectiveDefList|null;
|
private _directiveDefs: DirectiveDefList|null;
|
||||||
private _pipeDefs: PipeDefList|null;
|
private _pipeDefs: PipeDefList|null;
|
||||||
|
private _sanitizer: Sanitizer|null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param createBlock Instructions which go into the creation block:
|
* @param createBlock Instructions which go into the creation block:
|
||||||
@ -60,10 +63,12 @@ export class TemplateFixture extends BaseFixture {
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private createBlock: () => void, private updateBlock: () => void = noop,
|
private createBlock: () => void, private updateBlock: () => void = noop,
|
||||||
directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null) {
|
directives?: DirectiveTypesOrFactory|null, pipes?: PipeTypesOrFactory|null,
|
||||||
|
sanitizer?: Sanitizer) {
|
||||||
super();
|
super();
|
||||||
this._directiveDefs = toDefs(directives, extractDirectiveDef);
|
this._directiveDefs = toDefs(directives, extractDirectiveDef);
|
||||||
this._pipeDefs = toDefs(pipes, extractPipeDef);
|
this._pipeDefs = toDefs(pipes, extractPipeDef);
|
||||||
|
this._sanitizer = sanitizer || null;
|
||||||
this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => {
|
this.hostNode = renderTemplate(this.hostElement, (rf: RenderFlags, ctx: any) => {
|
||||||
if (rf & RenderFlags.Create) {
|
if (rf & RenderFlags.Create) {
|
||||||
this.createBlock();
|
this.createBlock();
|
||||||
@ -71,7 +76,7 @@ export class TemplateFixture extends BaseFixture {
|
|||||||
if (rf & RenderFlags.Update) {
|
if (rf & RenderFlags.Update) {
|
||||||
this.updateBlock();
|
this.updateBlock();
|
||||||
}
|
}
|
||||||
}, null !, domRendererFactory3, null, this._directiveDefs, this._pipeDefs);
|
}, null !, domRendererFactory3, null, this._directiveDefs, this._pipeDefs, sanitizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +87,7 @@ export class TemplateFixture extends BaseFixture {
|
|||||||
update(updateBlock?: () => void): void {
|
update(updateBlock?: () => void): void {
|
||||||
renderTemplate(
|
renderTemplate(
|
||||||
this.hostNode.native, updateBlock || this.updateBlock, null !, domRendererFactory3,
|
this.hostNode.native, updateBlock || this.updateBlock, null !, domRendererFactory3,
|
||||||
this.hostNode, this._directiveDefs, this._pipeDefs);
|
this.hostNode, this._directiveDefs, this._pipeDefs, this._sanitizer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +99,9 @@ export class ComponentFixture<T> extends BaseFixture {
|
|||||||
component: T;
|
component: T;
|
||||||
requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];};
|
requestAnimationFrame: {(fn: () => void): void; flush(): void; queue: (() => void)[];};
|
||||||
|
|
||||||
constructor(private componentType: ComponentType<T>, opts: {injector?: Injector} = {}) {
|
constructor(
|
||||||
|
private componentType: ComponentType<T>,
|
||||||
|
opts: {injector?: Injector, sanitizer?: Sanitizer} = {}) {
|
||||||
super();
|
super();
|
||||||
this.requestAnimationFrame = function(fn: () => void) {
|
this.requestAnimationFrame = function(fn: () => void) {
|
||||||
requestAnimationFrame.queue.push(fn);
|
requestAnimationFrame.queue.push(fn);
|
||||||
@ -106,9 +113,12 @@ export class ComponentFixture<T> extends BaseFixture {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.component = _renderComponent(
|
this.component = _renderComponent(componentType, {
|
||||||
componentType,
|
host: this.hostElement,
|
||||||
{host: this.hostElement, scheduler: this.requestAnimationFrame, injector: opts.injector});
|
scheduler: this.requestAnimationFrame,
|
||||||
|
injector: opts.injector,
|
||||||
|
sanitizer: opts.sanitizer
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update(): void {
|
update(): void {
|
||||||
@ -195,6 +205,7 @@ export function renderComponent<T>(type: ComponentType<T>, opts?: CreateComponen
|
|||||||
rendererFactory: opts && opts.rendererFactory || testRendererFactory,
|
rendererFactory: opts && opts.rendererFactory || testRendererFactory,
|
||||||
host: containerEl,
|
host: containerEl,
|
||||||
scheduler: requestAnimationFrame,
|
scheduler: requestAnimationFrame,
|
||||||
|
sanitizer: opts ? opts.sanitizer : undefined,
|
||||||
hostFeatures: opts && opts.hostFeatures
|
hostFeatures: opts && opts.hostFeatures
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user