feat(compiler): implement style encapsulation for new view engine (#14518)
Included refactoring: - splits the `RendererV2` into a `RendererFactoryV2` and a `RendererV2` - makes the `DebugRendererV2` a private class in `@angular/core` - remove `setBindingDebugInfo` from `RendererV2`, but rename `RendererV2.setText` to `RendererV2.setValue` and allow it on comments and text nodes. Part of #14013
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CommonModule, PlatformLocation} from '@angular/common';
|
||||
import {ApplicationModule, ErrorHandler, NgModule, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, Sanitizer, SkipSelf, Testability, createPlatformFactory, platformCore} from '@angular/core';
|
||||
import {ApplicationModule, ErrorHandler, NgModule, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactoryV2, RootRenderer, Sanitizer, SkipSelf, Testability, createPlatformFactory, platformCore} from '@angular/core';
|
||||
|
||||
import {AnimationDriver} from '../src/dom/animation_driver';
|
||||
import {WebAnimationsDriver} from '../src/dom/web_animations_driver';
|
||||
@@ -19,7 +19,7 @@ import {BrowserGetTestability} from './browser/testability';
|
||||
import {Title} from './browser/title';
|
||||
import {ELEMENT_PROBE_PROVIDERS} from './dom/debug/ng_probe';
|
||||
import {getDOM} from './dom/dom_adapter';
|
||||
import {DomRendererV2, DomRootRenderer, DomRootRenderer_} from './dom/dom_renderer';
|
||||
import {DomRendererFactoryV2, DomRootRenderer, DomRootRenderer_} from './dom/dom_renderer';
|
||||
import {DOCUMENT} from './dom/dom_tokens';
|
||||
import {DomEventsPlugin} from './dom/events/dom_events';
|
||||
import {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager';
|
||||
@@ -86,8 +86,8 @@ export function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
{provide: HAMMER_GESTURE_CONFIG, useClass: HammerGestureConfig},
|
||||
{provide: DomRootRenderer, useClass: DomRootRenderer_},
|
||||
{provide: RootRenderer, useExisting: DomRootRenderer},
|
||||
{provide: RENDERER_V2_DIRECT, useClass: DomRendererV2},
|
||||
{provide: RendererV2, useExisting: RENDERER_V2_DIRECT},
|
||||
DomRendererFactoryV2,
|
||||
{provide: RendererFactoryV2, useExisting: DomRendererFactoryV2},
|
||||
{provide: SharedStylesHost, useExisting: DomSharedStylesHost},
|
||||
{provide: AnimationDriver, useFactory: _resolveDefaultAnimationDriver},
|
||||
DomSharedStylesHost,
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
import * as core from '@angular/core';
|
||||
|
||||
import {StringMapWrapper} from '../../facade/collection';
|
||||
import {DebugDomRendererV2, DebugDomRootRenderer} from '../../private_import_core';
|
||||
import {DebugDomRootRenderer} from '../../private_import_core';
|
||||
import {getDOM} from '../dom_adapter';
|
||||
import {DomRootRenderer} from '../dom_renderer';
|
||||
import {DomRendererFactoryV2, DomRootRenderer} from '../dom_renderer';
|
||||
|
||||
const CORE_TOKENS = {
|
||||
'ApplicationRef': core.ApplicationRef,
|
||||
@@ -58,10 +58,6 @@ function _ngProbeTokensToMap(tokens: NgProbeToken[]): {[name: string]: any} {
|
||||
return tokens.reduce((prev: any, t: any) => (prev[t.name] = t.token, prev), {});
|
||||
}
|
||||
|
||||
export function _createDebugRendererV2(renderer: core.RendererV2): core.RendererV2 {
|
||||
return core.isDevMode() ? new DebugDomRendererV2(renderer) : renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Providers which support debugging Angular applications (e.g. via `ng.probe`).
|
||||
*/
|
||||
@@ -75,9 +71,4 @@ export const ELEMENT_PROBE_PROVIDERS: core.Provider[] = [
|
||||
[core.NgProbeToken, new core.Optional()],
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: core.RendererV2,
|
||||
useFactory: _createDebugRendererV2,
|
||||
deps: [core.RENDERER_V2_DIRECT],
|
||||
}
|
||||
];
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {APP_ID, Inject, Injectable, RenderComponentType, Renderer, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||
import {APP_ID, ComponentRenderTypeV2, Inject, Injectable, RenderComponentType, Renderer, RendererFactoryV2, RendererV2, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {isPresent, stringify} from '../facade/lang';
|
||||
import {AnimationKeyframe, AnimationPlayer, AnimationStyles, DirectRenderer, NoOpAnimationPlayer, RenderDebugInfo} from '../private_import_core';
|
||||
@@ -229,13 +229,8 @@ export class DomRenderer implements Renderer {
|
||||
TEMPLATE_COMMENT_TEXT.replace('{}', JSON.stringify(parsedBindings, null, 2));
|
||||
} else {
|
||||
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
|
||||
if (propertyName[propertyName.length - 1] === '$') {
|
||||
const attrNode: Attr = createAttributeNode(propertyName).cloneNode(true) as Attr;
|
||||
attrNode.value = propertyValue;
|
||||
renderElement.setAttributeNode(attrNode);
|
||||
} else {
|
||||
this.setElementAttribute(renderElement, propertyName, propertyValue);
|
||||
}
|
||||
propertyName = propertyName.replace(/\$/g, '_');
|
||||
this.setElementAttribute(renderElement, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,10 +361,51 @@ function createAttributeNode(name: string): Attr {
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DomRendererV2 implements RendererV2 {
|
||||
constructor(private eventManager: EventManager){};
|
||||
export class DomRendererFactoryV2 implements RendererFactoryV2 {
|
||||
private rendererByCompId = new Map<string, RendererV2>();
|
||||
private defaultRenderer: RendererV2;
|
||||
|
||||
createElement(name: string, namespace?: string, debugInfo?: any): any {
|
||||
constructor(private eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost) {
|
||||
this.defaultRenderer = new DefaultDomRendererV2(eventManager);
|
||||
};
|
||||
|
||||
createRenderer(element: any, type: ComponentRenderTypeV2): RendererV2 {
|
||||
if (!element || !type) {
|
||||
return this.defaultRenderer;
|
||||
}
|
||||
switch (type.encapsulation) {
|
||||
case ViewEncapsulation.Emulated: {
|
||||
let renderer = this.rendererByCompId.get(type.id);
|
||||
if (!renderer) {
|
||||
renderer = new EmulatedEncapsulationDomRendererV2(
|
||||
this.eventManager, this.sharedStylesHost, type);
|
||||
this.rendererByCompId.set(type.id, renderer);
|
||||
}
|
||||
(<EmulatedEncapsulationDomRendererV2>renderer).applyToHost(element);
|
||||
return renderer;
|
||||
}
|
||||
case ViewEncapsulation.Native:
|
||||
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
|
||||
default: {
|
||||
if (!this.rendererByCompId.has(type.id)) {
|
||||
const styles = flattenStyles(type.id, type.styles, []);
|
||||
this.sharedStylesHost.addStyles(styles);
|
||||
this.rendererByCompId.set(type.id, this.defaultRenderer);
|
||||
}
|
||||
return this.defaultRenderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultDomRendererV2 implements RendererV2 {
|
||||
constructor(private eventManager: EventManager) {}
|
||||
|
||||
destroy(): void {}
|
||||
|
||||
destroyNode: null;
|
||||
|
||||
createElement(name: string, namespace?: string): any {
|
||||
if (namespace) {
|
||||
return document.createElementNS(NAMESPACE_URIS[namespace], name);
|
||||
}
|
||||
@@ -377,9 +413,9 @@ export class DomRendererV2 implements RendererV2 {
|
||||
return document.createElement(name);
|
||||
}
|
||||
|
||||
createComment(value: string, debugInfo?: any): any { return document.createComment(value); }
|
||||
createComment(value: string): any { return document.createComment(value); }
|
||||
|
||||
createText(value: string, debugInfo?: any): any { return document.createTextNode(value); }
|
||||
createText(value: string): any { return document.createTextNode(value); }
|
||||
|
||||
appendChild(parent: any, newChild: any): void { parent.appendChild(newChild); }
|
||||
|
||||
@@ -395,7 +431,7 @@ export class DomRendererV2 implements RendererV2 {
|
||||
}
|
||||
}
|
||||
|
||||
selectRootElement(selectorOrNode: string|any, debugInfo?: any): any {
|
||||
selectRootElement(selectorOrNode: string|any): any {
|
||||
let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) :
|
||||
selectorOrNode;
|
||||
el.textContent = '';
|
||||
@@ -422,43 +458,6 @@ export class DomRendererV2 implements RendererV2 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
setBindingDebugInfo(el: any, propertyName: string, propertyValue: string): void {
|
||||
if (el.nodeType === Node.COMMENT_NODE) {
|
||||
const m = el.nodeValue.replace(/\n/g, '').match(TEMPLATE_BINDINGS_EXP);
|
||||
const obj = m === null ? {} : JSON.parse(m[1]);
|
||||
obj[propertyName] = propertyValue;
|
||||
el.nodeValue = TEMPLATE_COMMENT_TEXT.replace('{}', JSON.stringify(obj, null, 2));
|
||||
} else {
|
||||
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
|
||||
if (propertyName[propertyName.length - 1] === '$') {
|
||||
const attrNode: Attr = createAttributeNode(propertyName).cloneNode(true) as Attr;
|
||||
attrNode.value = propertyValue;
|
||||
el.setAttributeNode(attrNode);
|
||||
} else {
|
||||
this.setAttribute(el, propertyName, propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeBindingDebugInfo(el: any, propertyName: string): void {
|
||||
if (el.nodeType === Node.COMMENT_NODE) {
|
||||
const m = el.nodeValue.replace(/\n/g, '').match(TEMPLATE_BINDINGS_EXP);
|
||||
const obj = m === null ? {} : JSON.parse(m[1]);
|
||||
delete obj[propertyName];
|
||||
el.nodeValue = TEMPLATE_COMMENT_TEXT.replace('{}', JSON.stringify(obj, null, 2));
|
||||
} else {
|
||||
// Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
|
||||
if (propertyName[propertyName.length - 1] === '$') {
|
||||
const attrNode: Attr = createAttributeNode(propertyName).cloneNode(true) as Attr;
|
||||
attrNode.value = '';
|
||||
el.setAttributeNode(attrNode);
|
||||
} else {
|
||||
this.removeAttribute(el, propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addClass(el: any, name: string): void { el.classList.add(name); }
|
||||
|
||||
removeClass(el: any, name: string): void { el.classList.remove(name); }
|
||||
@@ -476,7 +475,7 @@ export class DomRendererV2 implements RendererV2 {
|
||||
|
||||
setProperty(el: any, name: string, value: any): void { el[name] = value; }
|
||||
|
||||
setText(node: any, value: string): void { node.nodeValue = value; }
|
||||
setValue(node: any, value: string): void { node.nodeValue = value; }
|
||||
|
||||
listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
|
||||
() => void {
|
||||
@@ -488,3 +487,62 @@ export class DomRendererV2 implements RendererV2 {
|
||||
target, event, decoratePreventDefault(callback)) as() => void;
|
||||
}
|
||||
}
|
||||
|
||||
class EmulatedEncapsulationDomRendererV2 extends DefaultDomRendererV2 {
|
||||
private contentAttr: string;
|
||||
private hostAttr: string;
|
||||
|
||||
constructor(
|
||||
eventManager: EventManager, sharedStylesHost: DomSharedStylesHost,
|
||||
private component: ComponentRenderTypeV2) {
|
||||
super(eventManager);
|
||||
const styles = flattenStyles(component.id, component.styles, []);
|
||||
sharedStylesHost.addStyles(styles);
|
||||
|
||||
this.contentAttr = shimContentAttribute(component.id);
|
||||
this.hostAttr = shimHostAttribute(component.id);
|
||||
}
|
||||
|
||||
applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); }
|
||||
|
||||
createElement(parent: any, name: string): Element {
|
||||
const el = super.createElement(parent, name);
|
||||
super.setAttribute(el, this.contentAttr, '');
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
class ShadowDomRenderer extends DefaultDomRendererV2 {
|
||||
private shadowRoot: any;
|
||||
|
||||
constructor(
|
||||
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
|
||||
private hostEl: any, private component: ComponentRenderTypeV2) {
|
||||
super(eventManager);
|
||||
this.shadowRoot = (hostEl as any).createShadowRoot();
|
||||
this.sharedStylesHost.addHost(this.shadowRoot);
|
||||
const styles = flattenStyles(component.id, component.styles, []);
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = styles[i];
|
||||
this.shadowRoot.appendChild(styleEl);
|
||||
}
|
||||
}
|
||||
|
||||
private nodeOrShadowRoot(node: any): any { return node === this.hostEl ? this.shadowRoot : node; }
|
||||
|
||||
destroy() { this.sharedStylesHost.removeHost(this.shadowRoot); }
|
||||
|
||||
appendChild(parent: any, newChild: any): void {
|
||||
return super.appendChild(this.nodeOrShadowRoot(parent), newChild);
|
||||
}
|
||||
insertBefore(parent: any, newChild: any, refChild: any): void {
|
||||
return super.insertBefore(this.nodeOrShadowRoot(parent), newChild, refChild);
|
||||
}
|
||||
removeChild(parent: any, oldChild: any): void {
|
||||
return super.removeChild(this.nodeOrShadowRoot(parent), oldChild);
|
||||
}
|
||||
parentNode(node: any): any {
|
||||
return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(node)));
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@ export const ReflectionCapabilities: typeof r.ReflectionCapabilities = r.Reflect
|
||||
|
||||
export type DebugDomRootRenderer = typeof r._DebugDomRootRenderer;
|
||||
export const DebugDomRootRenderer: typeof r.DebugDomRootRenderer = r.DebugDomRootRenderer;
|
||||
export type DebugDomRendererV2 = typeof r._DebugDomRendererV2;
|
||||
export const DebugDomRendererV2: typeof r.DebugDomRendererV2 = r.DebugDomRendererV2;
|
||||
|
||||
export const reflector: typeof r.reflector = r.reflector;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user