feat(ShadowDomStrategy): implemented EmulatedUnscopedShadowDomStrategy
- The new strategy do not scope component styles but make them global, - The former EmulatedShadowStrategy has been renamed to EmulatedScopedShadowDomStrategy. It does scope the styles.
This commit is contained in:
parent
9f181f39e9
commit
8541cfd26d
|
@ -12,7 +12,7 @@ import {ElementBinderBuilder} from './element_binder_builder';
|
||||||
import {ResolveCss} from './resolve_css';
|
import {ResolveCss} from './resolve_css';
|
||||||
import {ShimShadowDom} from './shim_shadow_dom';
|
import {ShimShadowDom} from './shim_shadow_dom';
|
||||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
import {ShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
import {ShadowDomStrategy, EmulatedScopedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default steps used for compiling a template.
|
* Default steps used for compiling a template.
|
||||||
|
@ -39,7 +39,7 @@ export function createDefaultSteps(
|
||||||
new ElementBinderBuilder(parser),
|
new ElementBinderBuilder(parser),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
if (shadowDomStrategy instanceof EmulatedScopedShadowDomStrategy) {
|
||||||
var step = new ShimShadowDom(compiledComponent, shadowDomStrategy);
|
var step = new ShimShadowDom(compiledComponent, shadowDomStrategy);
|
||||||
ListWrapper.push(steps, step);
|
ListWrapper.push(steps, step);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,22 @@ export class ShadowDomStrategy {
|
||||||
shimHostElement(component: Type, element: Element) {}
|
shimHostElement(component: Type, element: Element) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
/**
|
||||||
_styleInliner: StyleInliner;
|
* This strategy emulates the Shadow DOM for the templates, styles **excluded**:
|
||||||
|
* - components templates are added as children of their component element,
|
||||||
|
* - styles are moved from the templates to the styleHost (i.e. the document head).
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - styles are **not** scoped to their component and will apply to the whole document,
|
||||||
|
* - you can **not** use shadow DOM specific selectors in the styles
|
||||||
|
*/
|
||||||
|
export class EmulatedUnscopedShadowDomStrategy extends ShadowDomStrategy {
|
||||||
_styleUrlResolver: StyleUrlResolver;
|
_styleUrlResolver: StyleUrlResolver;
|
||||||
_styleHost: Element;
|
|
||||||
_lastInsertedStyle: StyleElement;
|
_lastInsertedStyle: StyleElement;
|
||||||
|
_styleHost: Element;
|
||||||
|
|
||||||
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost: Element) {
|
constructor(styleUrlResolver: StyleUrlResolver, styleHost: Element) {
|
||||||
super();
|
super();
|
||||||
this._styleInliner = styleInliner;
|
|
||||||
this._styleUrlResolver = styleUrlResolver;
|
this._styleUrlResolver = styleUrlResolver;
|
||||||
this._styleHost = styleHost;
|
this._styleHost = styleHost;
|
||||||
}
|
}
|
||||||
|
@ -49,6 +56,58 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||||
return [Content];
|
return [Content];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transformStyleText(cssText: string, baseUrl: string, component: Type) {
|
||||||
|
return this._styleUrlResolver.resolveUrls(cssText, baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStyleElement(styleEl: StyleElement) {
|
||||||
|
DOM.remove(styleEl);
|
||||||
|
|
||||||
|
var cssText = DOM.getText(styleEl);
|
||||||
|
|
||||||
|
if (!MapWrapper.contains(_sharedStyleTexts, cssText)) {
|
||||||
|
// Styles are unscoped and shared across components, only append them to the head
|
||||||
|
// when there are not present yet
|
||||||
|
MapWrapper.set(_sharedStyleTexts, cssText, true);
|
||||||
|
this._insertStyleElement(this._styleHost, styleEl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_insertStyleElement(host: Element, style: StyleElement) {
|
||||||
|
if (isBlank(this._lastInsertedStyle)) {
|
||||||
|
var firstChild = DOM.firstChild(host);
|
||||||
|
if (isPresent(firstChild)) {
|
||||||
|
DOM.insertBefore(firstChild, style);
|
||||||
|
} else {
|
||||||
|
DOM.appendChild(host, style);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DOM.insertAfter(this._lastInsertedStyle, style);
|
||||||
|
}
|
||||||
|
this._lastInsertedStyle = style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This strategy emulates the Shadow DOM for the templates, styles **included**:
|
||||||
|
* - components templates are added as children of their component element,
|
||||||
|
* - both the template and the styles are modified so that styles are scoped to the component
|
||||||
|
* they belong to,
|
||||||
|
* - styles are moved from the templates to the styleHost (i.e. the document head).
|
||||||
|
*
|
||||||
|
* Notes:
|
||||||
|
* - styles are scoped to their component and will apply only to it,
|
||||||
|
* - a common subset of shadow DOM selectors are supported,
|
||||||
|
* - see `ShadowCss` for more information and limitations.
|
||||||
|
*/
|
||||||
|
export class EmulatedScopedShadowDomStrategy extends EmulatedUnscopedShadowDomStrategy {
|
||||||
|
_styleInliner: StyleInliner;
|
||||||
|
|
||||||
|
constructor(styleInliner: StyleInliner, styleUrlResolver: StyleUrlResolver, styleHost: Element) {
|
||||||
|
super(styleUrlResolver, styleHost);
|
||||||
|
this._styleInliner = styleInliner;
|
||||||
|
}
|
||||||
|
|
||||||
transformStyleText(cssText: string, baseUrl: string, component: Type) {
|
transformStyleText(cssText: string, baseUrl: string, component: Type) {
|
||||||
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
|
cssText = this._styleUrlResolver.resolveUrls(cssText, baseUrl);
|
||||||
var css = this._styleInliner.inlineImports(cssText, baseUrl);
|
var css = this._styleInliner.inlineImports(cssText, baseUrl);
|
||||||
|
@ -75,22 +134,14 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||||
var attrName = _getHostAttribute(id);
|
var attrName = _getHostAttribute(id);
|
||||||
DOM.setAttribute(element, attrName, '');
|
DOM.setAttribute(element, attrName, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
_insertStyleElement(host: Element, style: StyleElement) {
|
|
||||||
if (isBlank(this._lastInsertedStyle)) {
|
|
||||||
var firstChild = DOM.firstChild(host);
|
|
||||||
if (isPresent(firstChild)) {
|
|
||||||
DOM.insertBefore(firstChild, style);
|
|
||||||
} else {
|
|
||||||
DOM.appendChild(host, style);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DOM.insertAfter(this._lastInsertedStyle, style);
|
|
||||||
}
|
|
||||||
this._lastInsertedStyle = style;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This strategies uses the native Shadow DOM support.
|
||||||
|
*
|
||||||
|
* The templates for the component are inserted in a Shadow Root created on the component element.
|
||||||
|
* Hence they are strictly isolated.
|
||||||
|
*/
|
||||||
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||||
_styleUrlResolver: StyleUrlResolver;
|
_styleUrlResolver: StyleUrlResolver;
|
||||||
|
|
||||||
|
@ -124,6 +175,7 @@ function _moveViewNodesIntoParent(parent, view) {
|
||||||
|
|
||||||
var _componentUIDs: Map<Type, int> = MapWrapper.create();
|
var _componentUIDs: Map<Type, int> = MapWrapper.create();
|
||||||
var _nextComponentUID: int = 0;
|
var _nextComponentUID: int = 0;
|
||||||
|
var _sharedStyleTexts: Map<string, boolean> = MapWrapper.create();
|
||||||
|
|
||||||
function _getComponentId(component: Type) {
|
function _getComponentId(component: Type) {
|
||||||
var id = MapWrapper.get(_componentUIDs, component);
|
var id = MapWrapper.get(_componentUIDs, component);
|
||||||
|
@ -150,8 +202,9 @@ function _shimCssForComponent(cssText: string, component: Type): string {
|
||||||
return shadowCss.shimCssText(cssText, _getContentAttribute(id), _getHostAttribute(id));
|
return shadowCss.shimCssText(cssText, _getContentAttribute(id), _getHostAttribute(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the component cache - used for tests only
|
// Reset the caches - used for tests only
|
||||||
export function resetShadowDomCache() {
|
export function resetShadowDomCache() {
|
||||||
MapWrapper.clear(_componentUIDs);
|
MapWrapper.clear(_componentUIDs);
|
||||||
_nextComponentUID = 0;
|
_nextComponentUID = 0;
|
||||||
|
MapWrapper.clear(_sharedStyleTexts);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
|
||||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||||
import {ShadowDomStrategy,
|
import {ShadowDomStrategy,
|
||||||
NativeShadowDomStrategy,
|
NativeShadowDomStrategy,
|
||||||
EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
EmulatedScopedShadowDomStrategy,
|
||||||
|
EmulatedUnscopedShadowDomStrategy,
|
||||||
|
} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
|
import {TemplateLoader} from 'angular2/src/core/compiler/template_loader';
|
||||||
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
||||||
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
||||||
|
@ -35,7 +37,8 @@ export function main() {
|
||||||
|
|
||||||
StringMapWrapper.forEach({
|
StringMapWrapper.forEach({
|
||||||
"native" : new NativeShadowDomStrategy(styleUrlResolver),
|
"native" : new NativeShadowDomStrategy(styleUrlResolver),
|
||||||
"emulated" : new EmulatedShadowDomStrategy(styleInliner, styleUrlResolver, DOM.createElement('div'))
|
"scoped" : new EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, DOM.createElement('div')),
|
||||||
|
"unscoped" : new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, DOM.createElement('div')),
|
||||||
},
|
},
|
||||||
(strategy, name) => {
|
(strategy, name) => {
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'a
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NativeShadowDomStrategy,
|
NativeShadowDomStrategy,
|
||||||
EmulatedShadowDomStrategy,
|
EmulatedScopedShadowDomStrategy,
|
||||||
|
EmulatedUnscopedShadowDomStrategy,
|
||||||
resetShadowDomCache,
|
resetShadowDomCache,
|
||||||
} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
import {UrlResolver} from 'angular2/src/core/compiler/url_resolver';
|
||||||
|
@ -53,7 +54,7 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('EmulatedShadowDomStratgey', () => {
|
describe('EmulatedScopedShadowDomStratgey', () => {
|
||||||
var xhr, styleHost;
|
var xhr, styleHost;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -62,7 +63,7 @@ export function main() {
|
||||||
xhr = new FakeXHR();
|
xhr = new FakeXHR();
|
||||||
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
|
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
|
||||||
styleHost = el('<div></div>');
|
styleHost = el('<div></div>');
|
||||||
strategy = new EmulatedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost);
|
strategy = new EmulatedScopedShadowDomStrategy(styleInliner, styleUrlResolver, styleHost);
|
||||||
resetShadowDomCache();
|
resetShadowDomCache();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,6 +142,71 @@ export function main() {
|
||||||
expect(DOM.getAttribute(elt, '_nghost-0')).toEqual('');
|
expect(DOM.getAttribute(elt, '_nghost-0')).toEqual('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('EmulatedUnscopedShadowDomStratgey', () => {
|
||||||
|
var styleHost;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
var urlResolver = new UrlResolver();
|
||||||
|
var styleUrlResolver = new StyleUrlResolver(urlResolver);
|
||||||
|
styleHost = el('<div></div>');
|
||||||
|
strategy = new EmulatedUnscopedShadowDomStrategy(styleUrlResolver, styleHost);
|
||||||
|
resetShadowDomCache();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach the view nodes as child of the host element', () => {
|
||||||
|
var host = el('<div><span>original content</span></div>');
|
||||||
|
var nodes = el('<div>view</div>');
|
||||||
|
var pv = new ProtoView(nodes, new DynamicProtoChangeDetector(null), null);
|
||||||
|
var view = pv.instantiate(null, null);
|
||||||
|
|
||||||
|
strategy.attachTemplate(host, view);
|
||||||
|
var firstChild = DOM.firstChild(host);
|
||||||
|
expect(DOM.tagName(firstChild)).toEqual('DIV');
|
||||||
|
expect(firstChild).toHaveText('view');
|
||||||
|
expect(host).toHaveText('view');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should rewrite style urls', () => {
|
||||||
|
var css = '.foo {background-image: url("img.jpg");}';
|
||||||
|
expect(strategy.transformStyleText(css, 'http://base', null))
|
||||||
|
.toEqual(".foo {background-image: url('http://base/img.jpg');}");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not inline import rules', () => {
|
||||||
|
var css = '@import "other.css";';
|
||||||
|
expect(strategy.transformStyleText(css, 'http://base', null))
|
||||||
|
.toEqual("@import 'http://base/other.css';");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the style element to the style host', () => {
|
||||||
|
var originalHost = el('<div></div>');
|
||||||
|
var styleEl = el('<style>/*css*/</style>');
|
||||||
|
DOM.appendChild(originalHost, styleEl);
|
||||||
|
expect(originalHost).toHaveText('/*css*/');
|
||||||
|
|
||||||
|
strategy.handleStyleElement(styleEl);
|
||||||
|
expect(originalHost).toHaveText('');
|
||||||
|
expect(styleHost).toHaveText('/*css*/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert the same style only once in the style host', () => {
|
||||||
|
var originalHost = el('<div></div>');
|
||||||
|
var styleEl1 = el('<style>/*css 1*/</style>');
|
||||||
|
var styleEl2 = el('<style>/*css 2*/</style>');
|
||||||
|
var styleEl1bis = el('<style>/*css 1*/</style>');
|
||||||
|
|
||||||
|
DOM.appendChild(originalHost, styleEl1);
|
||||||
|
DOM.appendChild(originalHost, styleEl2);
|
||||||
|
DOM.appendChild(originalHost, styleEl1bis);
|
||||||
|
|
||||||
|
strategy.handleStyleElement(styleEl1);
|
||||||
|
strategy.handleStyleElement(styleEl2);
|
||||||
|
strategy.handleStyleElement(styleEl1bis);
|
||||||
|
expect(originalHost).toHaveText('');
|
||||||
|
expect(styleHost).toHaveText('/*css 1*//*css 2*/');
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeXHR extends XHR {
|
class FakeXHR extends XHR {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el, proxy} from 'angular2/test_lib';
|
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el, proxy} from 'angular2/test_lib';
|
||||||
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'angular2/src/core/compiler/view';
|
import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'angular2/src/core/compiler/view';
|
||||||
import {ProtoElementInjector, ElementInjector, DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
|
import {ProtoElementInjector, ElementInjector, DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
|
||||||
import {EmulatedShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
import {EmulatedScopedShadowDomStrategy, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||||
import {Component, Decorator, Viewport, Directive, onChange} from 'angular2/src/core/annotations/annotations';
|
import {Component, Decorator, Viewport, Directive, onChange} from 'angular2/src/core/annotations/annotations';
|
||||||
import {Lexer, Parser, DynamicProtoChangeDetector,
|
import {Lexer, Parser, DynamicProtoChangeDetector,
|
||||||
|
@ -396,7 +396,7 @@ export function main() {
|
||||||
new DynamicProtoChangeDetector(null), null);
|
new DynamicProtoChangeDetector(null), null);
|
||||||
|
|
||||||
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'),
|
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'),
|
||||||
new DynamicProtoChangeDetector(null), new EmulatedShadowDomStrategy(null, null, null));
|
new DynamicProtoChangeDetector(null), new EmulatedScopedShadowDomStrategy(null, null, null));
|
||||||
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
|
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
|
||||||
binder.componentDirective = new DirectiveMetadataReader().read(SomeComponent);
|
binder.componentDirective = new DirectiveMetadataReader().read(SomeComponent);
|
||||||
binder.nestedProtoView = subpv;
|
binder.nestedProtoView = subpv;
|
||||||
|
|
Loading…
Reference in New Issue