refactor(Compiler): introduce ShimComponent to shim CSS & DOM in emulated mode
Closes #715
This commit is contained in:
parent
5111f9ae37
commit
d0ca07afaa
|
@ -9,9 +9,10 @@ import {ElementBindingMarker} from './element_binding_marker';
|
||||||
import {ProtoViewBuilder} from './proto_view_builder';
|
import {ProtoViewBuilder} from './proto_view_builder';
|
||||||
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
import {ProtoElementInjectorBuilder} from './proto_element_injector_builder';
|
||||||
import {ElementBinderBuilder} from './element_binder_builder';
|
import {ElementBinderBuilder} from './element_binder_builder';
|
||||||
import {ShadowDomTransformer} from './shadow_dom_transformer';
|
import {ShimShadowCss} from './shim_shadow_css';
|
||||||
|
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, NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
import {ShadowDomStrategy, EmulatedShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {stringify} from 'angular2/src/facade/lang';
|
import {stringify} from 'angular2/src/facade/lang';
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
|
|
||||||
|
@ -31,8 +32,8 @@ export function createDefaultSteps(
|
||||||
|
|
||||||
var steps = [new ViewSplitter(parser, compilationUnit)];
|
var steps = [new ViewSplitter(parser, compilationUnit)];
|
||||||
|
|
||||||
if (!(shadowDomStrategy instanceof NativeShadowDomStrategy)) {
|
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
||||||
var step = new ShadowDomTransformer(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
|
var step = new ShimShadowCss(compiledComponent, shadowDomStrategy, DOM.defaultDoc().head);
|
||||||
ListWrapper.push(steps, step);
|
ListWrapper.push(steps, step);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,5 +47,10 @@ export function createDefaultSteps(
|
||||||
new ElementBinderBuilder(parser, compilationUnit)
|
new ElementBinderBuilder(parser, compilationUnit)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (shadowDomStrategy instanceof EmulatedShadowDomStrategy) {
|
||||||
|
var step = new ShimShadowDom(compiledComponent, shadowDomStrategy);
|
||||||
|
ListWrapper.push(steps, step);
|
||||||
|
}
|
||||||
|
|
||||||
return steps;
|
return steps;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,20 @@ import {CompileControl} from './compile_control';
|
||||||
|
|
||||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css';
|
|
||||||
|
|
||||||
import {DOM, Element} from 'angular2/src/facade/dom';
|
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, Type} from 'angular2/src/facade/lang';
|
||||||
import {StringMapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
var _cssCache = StringMapWrapper.create();
|
export class ShimShadowCss extends CompileStep {
|
||||||
|
|
||||||
export class ShadowDomTransformer extends CompileStep {
|
|
||||||
_selector: string;
|
|
||||||
_strategy: ShadowDomStrategy;
|
_strategy: ShadowDomStrategy;
|
||||||
_styleHost: Element;
|
_styleHost: Element;
|
||||||
_lastInsertedStyle: Element;
|
_lastInsertedStyle: Element;
|
||||||
|
_component: Type;
|
||||||
|
|
||||||
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, styleHost: Element) {
|
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy, styleHost: Element) {
|
||||||
super();
|
super();
|
||||||
this._strategy = strategy;
|
this._strategy = strategy;
|
||||||
this._selector = cmpMetadata.annotation.selector;
|
this._component = cmpMetadata.type;
|
||||||
this._styleHost = styleHost;
|
this._styleHost = styleHost;
|
||||||
this._lastInsertedStyle = null;
|
this._lastInsertedStyle = null;
|
||||||
}
|
}
|
||||||
|
@ -33,33 +29,12 @@ export class ShadowDomTransformer extends CompileStep {
|
||||||
if (this._strategy.extractStyles()) {
|
if (this._strategy.extractStyles()) {
|
||||||
DOM.remove(current.element);
|
DOM.remove(current.element);
|
||||||
var css = DOM.getText(current.element);
|
var css = DOM.getText(current.element);
|
||||||
if (this._strategy.shim()) {
|
var shimComponent = this._strategy.getShimComponent(this._component);
|
||||||
// The css generated here is unique for the component (because of the shim).
|
css = shimComponent.shimCssText(css);
|
||||||
// Then we do not need to cache it.
|
|
||||||
css = shimCssText(css, this._selector);
|
|
||||||
this._insertStyle(this._styleHost, css);
|
|
||||||
} else {
|
|
||||||
var seen = isPresent(StringMapWrapper.get(_cssCache, css));
|
|
||||||
if (!seen) {
|
|
||||||
StringMapWrapper.set(_cssCache, css, true);
|
|
||||||
this._insertStyle(this._styleHost, css);
|
this._insertStyle(this._styleHost, css);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (this._strategy.shim()) {
|
|
||||||
try {
|
|
||||||
DOM.setAttribute(current.element, this._selector, '');
|
|
||||||
} catch(e) {
|
|
||||||
// TODO(vicb): for now only simple selector (tag name) are supported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearCache() {
|
|
||||||
_cssCache = StringMapWrapper.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
_insertStyle(el: Element, css: string) {
|
_insertStyle(el: Element, css: string) {
|
||||||
var style = DOM.createStyleElement(css);
|
var style = DOM.createStyleElement(css);
|
|
@ -0,0 +1,38 @@
|
||||||
|
import {CompileStep} from './compile_step';
|
||||||
|
import {CompileElement} from './compile_element';
|
||||||
|
import {CompileControl} from './compile_control';
|
||||||
|
|
||||||
|
import {isPresent} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
|
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
|
|
||||||
|
import {ShimComponent} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component';
|
||||||
|
|
||||||
|
export class ShimShadowDom extends CompileStep {
|
||||||
|
_strategy: ShadowDomStrategy;
|
||||||
|
_shimComponent: ShimComponent;
|
||||||
|
|
||||||
|
constructor(cmpMetadata: DirectiveMetadata, strategy: ShadowDomStrategy) {
|
||||||
|
super();
|
||||||
|
this._strategy = strategy;
|
||||||
|
this._shimComponent = strategy.getShimComponent(cmpMetadata.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
if (current.ignoreBindings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shim the element as a child of the compiled component
|
||||||
|
this._shimComponent.shimContentElement(current.element);
|
||||||
|
|
||||||
|
// If the current element is also a component, shim it as a host
|
||||||
|
var host = current.componentDirective;
|
||||||
|
if (isPresent(host)) {
|
||||||
|
var shimComponent = this._strategy.getShimComponent(host.type);
|
||||||
|
shimComponent.shimHostElement(current.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import {Element, DOM} from 'angular2/src/facade/dom';
|
||||||
|
import {Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {int, isBlank, Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
import {ShadowCss} from './shadow_css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to shim component CSS & DOM
|
||||||
|
*/
|
||||||
|
export class ShimComponent {
|
||||||
|
constructor(component: Type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
shimCssText(cssText: string): string {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
shimContentElement(element: Element) {}
|
||||||
|
|
||||||
|
shimHostElement(element: Element) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native components does not need to the shim.
|
||||||
|
*
|
||||||
|
* All methods are no-ops.
|
||||||
|
*/
|
||||||
|
export class ShimNativeComponent extends ShimComponent {
|
||||||
|
constructor(component: Type) {
|
||||||
|
super(component);
|
||||||
|
};
|
||||||
|
|
||||||
|
shimCssText(cssText: string): string {
|
||||||
|
return cssText;
|
||||||
|
}
|
||||||
|
|
||||||
|
shimContentElement(element: Element) {
|
||||||
|
}
|
||||||
|
|
||||||
|
shimHostElement(element: Element) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _componentCache: Map<Type, int> = MapWrapper.create();
|
||||||
|
var _componentId: int = 0;
|
||||||
|
|
||||||
|
// Reset the component cache - used for tests only
|
||||||
|
export function resetShimComponentCache() {
|
||||||
|
MapWrapper.clear(_componentCache);
|
||||||
|
_componentId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emulated components need to be shimmed:
|
||||||
|
* - An attribute needs to be added to the host,
|
||||||
|
* - An attribute needs to be added to all nodes in their content,
|
||||||
|
* - The CSS needs to be scoped.
|
||||||
|
*/
|
||||||
|
export class ShimEmulatedComponent extends ShimComponent {
|
||||||
|
_cmpId: int;
|
||||||
|
|
||||||
|
constructor(component: Type) {
|
||||||
|
super(component);
|
||||||
|
|
||||||
|
// Generates a unique ID for components
|
||||||
|
var componentId = MapWrapper.get(_componentCache, component);
|
||||||
|
if (isBlank(componentId)) {
|
||||||
|
componentId = _componentId++;
|
||||||
|
MapWrapper.set(_componentCache, component, componentId);
|
||||||
|
}
|
||||||
|
this._cmpId = componentId;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scope the CSS
|
||||||
|
shimCssText(cssText: string): string {
|
||||||
|
var shadowCss = new ShadowCss();
|
||||||
|
return shadowCss.shimCssText(cssText, this._getContentAttribute(), this._getHostAttribute());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an attribute on a content element
|
||||||
|
shimContentElement(element: Element) {
|
||||||
|
DOM.setAttribute(element, this._getContentAttribute(), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an attribute to the host
|
||||||
|
shimHostElement(element: Element) {
|
||||||
|
DOM.setAttribute(element, this._getHostAttribute(), '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the attribute to be added to the component
|
||||||
|
_getHostAttribute() {
|
||||||
|
return `_nghost-${this._cmpId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the attribute to be added on every single nodes in the component
|
||||||
|
_getContentAttribute() {
|
||||||
|
return `_ngcontent-${this._cmpId}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,431 +0,0 @@
|
||||||
import {StringWrapper, RegExpWrapper, isPresent, BaseException, int} from 'angular2/src/facade/lang';
|
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
export function shimCssText(css: string, tag: string) {
|
|
||||||
return new CssShim(tag).shimCssText(css);
|
|
||||||
}
|
|
||||||
|
|
||||||
var _HOST_RE = RegExpWrapper.create(':host', 'i');
|
|
||||||
var _HOST_TOKEN = '-host-element';
|
|
||||||
var _HOST_TOKEN_RE = RegExpWrapper.create('-host-element');
|
|
||||||
var _PAREN_SUFFIX = ')(?:\\((' +
|
|
||||||
'(?:\\([^)(]*\\)|[^)(]*)+?' +
|
|
||||||
')\\))?([^,{]*)';
|
|
||||||
var _COLON_HOST_RE = RegExpWrapper.create(`(${_HOST_TOKEN}${_PAREN_SUFFIX}`, 'im');
|
|
||||||
|
|
||||||
var _POLYFILL_NON_STRICT = 'polyfill-non-strict';
|
|
||||||
var _POLYFILL_UNSCOPED_NEXT_SELECTOR = 'polyfill-unscoped-next-selector';
|
|
||||||
var _POLYFILL_NEXT_SELECTOR = 'polyfill-next-selector';
|
|
||||||
var _CONTENT_RE = RegExpWrapper.create('[^}]*content:[\\s]*[\'"](.*?)[\'"][;\\s]*[^}]*}', 'im');
|
|
||||||
var _COMBINATORS = [
|
|
||||||
RegExpWrapper.create('/shadow/', 'i'),
|
|
||||||
RegExpWrapper.create('/shadow-deep/', 'i'),
|
|
||||||
RegExpWrapper.create('::shadow', 'i'),
|
|
||||||
RegExpWrapper.create('/deep/', 'i'),
|
|
||||||
];
|
|
||||||
var _COLON_SELECTORS = RegExpWrapper.create('(' + _HOST_TOKEN + ')(\\(.*\\))?(.*)', 'i');
|
|
||||||
var _SELECTOR_SPLITS = [' ', '>', '+', '~'];
|
|
||||||
var _SIMPLE_SELECTORS = RegExpWrapper.create('([^:]*)(:*)(.*)', 'i');
|
|
||||||
var _IS_SELECTORS = RegExpWrapper.create('\\[is=[\'"]([^\\]]*)[\'"]\\]', 'i');
|
|
||||||
|
|
||||||
var _$EOF = 0;
|
|
||||||
var _$LBRACE = 123;
|
|
||||||
var _$RBRACE = 125;
|
|
||||||
var _$TAB = 9;
|
|
||||||
var _$SPACE = 32;
|
|
||||||
var _$NBSP = 160;
|
|
||||||
|
|
||||||
export class CssShim {
|
|
||||||
_tag: string;
|
|
||||||
_attr: string;
|
|
||||||
|
|
||||||
constructor(tag: string) {
|
|
||||||
this._tag = tag;
|
|
||||||
this._attr = `[${tag}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
shimCssText(css: string): string {
|
|
||||||
var preprocessed = this.convertColonHost(css);
|
|
||||||
var rules = this.cssToRules(preprocessed);
|
|
||||||
return this.scopeRules(rules);
|
|
||||||
}
|
|
||||||
|
|
||||||
convertColonHost(css: string):string {
|
|
||||||
css = StringWrapper.replaceAll(css, _HOST_RE, _HOST_TOKEN);
|
|
||||||
|
|
||||||
var partReplacer = function(host, part, suffix) {
|
|
||||||
part = StringWrapper.replaceAll(part, _HOST_TOKEN_RE, '');
|
|
||||||
return `${host}${part}${suffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return StringWrapper.replaceAllMapped(css, _COLON_HOST_RE, function(m) {
|
|
||||||
var base = _HOST_TOKEN;
|
|
||||||
var inParens = m[2];
|
|
||||||
var rest = m[3];
|
|
||||||
|
|
||||||
if (isPresent(inParens)) {
|
|
||||||
var srcParts = inParens.split(',');
|
|
||||||
var dstParts = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < srcParts.length; i++) {
|
|
||||||
var part = srcParts[i].trim();
|
|
||||||
if (part.length > 0) {
|
|
||||||
ListWrapper.push(dstParts, partReplacer(base, part, rest));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListWrapper.join(dstParts, ',');
|
|
||||||
} else {
|
|
||||||
return `${base}${rest}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cssToRules(css: string): List<_Rule> {
|
|
||||||
return new _Parser(css).parse();
|
|
||||||
}
|
|
||||||
|
|
||||||
scopeRules(rules: List<_Rule>): string {
|
|
||||||
var scopedRules = [];
|
|
||||||
var prevRule = null;
|
|
||||||
|
|
||||||
for (var i = 0; i < rules.length; i++) {
|
|
||||||
var rule = rules[i];
|
|
||||||
if (isPresent(prevRule) &&
|
|
||||||
prevRule.selectorText == _POLYFILL_NON_STRICT) {
|
|
||||||
ListWrapper.push(scopedRules, this.scopeNonStrictMode(rule));
|
|
||||||
|
|
||||||
} else if (isPresent(prevRule) &&
|
|
||||||
prevRule.selectorText == _POLYFILL_UNSCOPED_NEXT_SELECTOR) {
|
|
||||||
var content = this.extractContent(prevRule);
|
|
||||||
var r = new _Rule(content, rule.body, null);
|
|
||||||
ListWrapper.push(scopedRules, this.ruleToString(r));
|
|
||||||
|
|
||||||
} else if (isPresent(prevRule) &&
|
|
||||||
prevRule.selectorText == _POLYFILL_NEXT_SELECTOR) {
|
|
||||||
|
|
||||||
var content = this.extractContent(prevRule);
|
|
||||||
var r = new _Rule(content, rule.body, null);
|
|
||||||
ListWrapper.push(scopedRules, this.scopeStrictMode(r))
|
|
||||||
|
|
||||||
} else if (rule.selectorText != _POLYFILL_NON_STRICT &&
|
|
||||||
rule.selectorText != _POLYFILL_UNSCOPED_NEXT_SELECTOR &&
|
|
||||||
rule.selectorText != _POLYFILL_NEXT_SELECTOR) {
|
|
||||||
ListWrapper.push(scopedRules, this.scopeStrictMode(rule));
|
|
||||||
}
|
|
||||||
prevRule = rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListWrapper.join(scopedRules, '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
extractContent(rule: _Rule): string {
|
|
||||||
var match = RegExpWrapper.firstMatch(_CONTENT_RE, rule.body);
|
|
||||||
return isPresent(match) ? match[1] : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
ruleToString(rule: _Rule): string {
|
|
||||||
return `${rule.selectorText} ${rule.body}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
scopeStrictMode(rule: _Rule): string {
|
|
||||||
if (rule.hasNestedRules()) {
|
|
||||||
var selector = rule.selectorText;
|
|
||||||
var rules = this.scopeRules(rule.rules);
|
|
||||||
return `${selector} {\n${rules}\n}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scopedSelector = this.scopeSelector(rule.selectorText, true);
|
|
||||||
var scopedBody = rule.body;
|
|
||||||
return `${scopedSelector} ${scopedBody}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
scopeNonStrictMode(rule: _Rule): string {
|
|
||||||
var scopedSelector = this.scopeSelector(rule.selectorText, false);
|
|
||||||
var scopedBody = rule.body;
|
|
||||||
return `${scopedSelector} ${scopedBody}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
scopeSelector(selector: string, strict: boolean) {
|
|
||||||
var parts = this.replaceCombinators(selector).split(',');
|
|
||||||
var scopedParts = [];
|
|
||||||
for (var i = 0; i < parts.length; i++) {
|
|
||||||
var part = parts[i];
|
|
||||||
var sel = this.scopeSimpleSelector(part.trim(), strict);
|
|
||||||
ListWrapper.push(scopedParts, sel)
|
|
||||||
}
|
|
||||||
return ListWrapper.join(scopedParts, ', ');
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceCombinators(selector: string): string {
|
|
||||||
for (var i = 0; i < _COMBINATORS.length; i++) {
|
|
||||||
var combinator = _COMBINATORS[i];
|
|
||||||
selector = StringWrapper.replaceAll(selector, combinator, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
scopeSimpleSelector(selector: string, strict: boolean) {
|
|
||||||
if (StringWrapper.contains(selector, _HOST_TOKEN)) {
|
|
||||||
return this.replaceColonSelectors(selector);
|
|
||||||
} else if (strict) {
|
|
||||||
return this.insertTagToEverySelectorPart(selector);
|
|
||||||
} else {
|
|
||||||
return `${this._tag} ${selector}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceColonSelectors(css: string): string {
|
|
||||||
return StringWrapper.replaceAllMapped(css, _COLON_SELECTORS, (m) => {
|
|
||||||
var selectorInParens;
|
|
||||||
if (isPresent(m[2])) {
|
|
||||||
var len = selectorInParens.length;
|
|
||||||
selectorInParens = StringWrapper.substring(selectorInParens, 1, len - 1);
|
|
||||||
} else {
|
|
||||||
selectorInParens = '';
|
|
||||||
}
|
|
||||||
var rest = m[3];
|
|
||||||
return `${this._tag}${selectorInParens}${rest}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
insertTagToEverySelectorPart(selector: string): string {
|
|
||||||
selector = this.handleIsSelector(selector);
|
|
||||||
|
|
||||||
for (var i = 0; i < _SELECTOR_SPLITS.length; i++) {
|
|
||||||
var split = _SELECTOR_SPLITS[i];
|
|
||||||
var parts = selector.split(split);
|
|
||||||
for (var j = 0; j < parts.length; j++) {
|
|
||||||
parts[j] = this.insertAttrSuffixIntoSelectorPart(parts[j].trim());
|
|
||||||
}
|
|
||||||
selector = parts.join(split);
|
|
||||||
}
|
|
||||||
return selector;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertAttrSuffixIntoSelectorPart(p: string): string {
|
|
||||||
var shouldInsert = p.length > 0 &&
|
|
||||||
!ListWrapper.contains(_SELECTOR_SPLITS, p) &&
|
|
||||||
!StringWrapper.contains(p, this._attr);
|
|
||||||
return shouldInsert ? this.insertAttr(p) : p;
|
|
||||||
}
|
|
||||||
|
|
||||||
insertAttr(selector: string): string {
|
|
||||||
return StringWrapper.replaceAllMapped(selector, _SIMPLE_SELECTORS, (m) => {
|
|
||||||
var basePart = m[1];
|
|
||||||
var colonPart = m[2];
|
|
||||||
var rest = m[3];
|
|
||||||
return (m[0].length > 0) ? `${basePart}${this._attr}${colonPart}${rest}` : '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleIsSelector(selector: string) {
|
|
||||||
return StringWrapper.replaceAllMapped(selector, _IS_SELECTORS, function(m) {
|
|
||||||
return m[1];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Token {
|
|
||||||
string: string;
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
constructor(string: string, type: string) {
|
|
||||||
this.string = string;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _EOF_TOKEN = new _Token(null, null);
|
|
||||||
|
|
||||||
class _Lexer {
|
|
||||||
peek: int;
|
|
||||||
index: int;
|
|
||||||
input: string;
|
|
||||||
length: int;
|
|
||||||
|
|
||||||
constructor(input: string) {
|
|
||||||
this.input = input;
|
|
||||||
this.length = input.length;
|
|
||||||
this.index = -1;
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(): List<_Token> {
|
|
||||||
var tokens = [];
|
|
||||||
var token = this.scanToken();
|
|
||||||
while (token !== _EOF_TOKEN) {
|
|
||||||
ListWrapper.push(tokens, token);
|
|
||||||
token = this.scanToken();
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
scanToken(): _Token {
|
|
||||||
this.skipWhitespace();
|
|
||||||
if (this.peek === _$EOF) return _EOF_TOKEN;
|
|
||||||
if (this.isBodyEnd(this.peek)) {
|
|
||||||
this.advance();
|
|
||||||
return new _Token('}', 'rparen');
|
|
||||||
}
|
|
||||||
if (this.isMedia(this.peek)) return this.scanMedia();
|
|
||||||
if (this.isSelector(this.peek)) return this.scanSelector();
|
|
||||||
if (this.isBodyStart(this.peek)) return this.scanBody();
|
|
||||||
|
|
||||||
return _EOF_TOKEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSelector(v: int): boolean {
|
|
||||||
return !this.isBodyStart(v) && v !== _$EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
isBodyStart(v: int): boolean {
|
|
||||||
return v === _$LBRACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
isBodyEnd(v: int): boolean {
|
|
||||||
return v === _$RBRACE;
|
|
||||||
}
|
|
||||||
|
|
||||||
isMedia(v: int): boolean {
|
|
||||||
return v === 64; // @ -> 64
|
|
||||||
}
|
|
||||||
|
|
||||||
isWhitespace(v: int): boolean {
|
|
||||||
return (v >= _$TAB && v <= _$SPACE) || (v == _$NBSP)
|
|
||||||
}
|
|
||||||
|
|
||||||
skipWhitespace() {
|
|
||||||
while (this.isWhitespace(this.peek)) {
|
|
||||||
if (++this.index >= this.length) {
|
|
||||||
this.peek = _$EOF;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.peek = StringWrapper.charCodeAt(this.input, this.index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scanSelector(): _Token {
|
|
||||||
var start = this.index;
|
|
||||||
this.advance();
|
|
||||||
while (this.isSelector(this.peek)) {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
var selector = StringWrapper.substring(this.input, start, this.index);
|
|
||||||
return new _Token(selector.trim(), 'selector');
|
|
||||||
}
|
|
||||||
|
|
||||||
scanBody(): _Token {
|
|
||||||
var start = this.index;
|
|
||||||
this.advance();
|
|
||||||
while (!this.isBodyEnd(this.peek)) {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
this.advance();
|
|
||||||
var body = StringWrapper.substring(this.input, start, this.index);
|
|
||||||
return new _Token(body, 'body');
|
|
||||||
}
|
|
||||||
|
|
||||||
scanMedia(): _Token {
|
|
||||||
var start = this.index;
|
|
||||||
this.advance();
|
|
||||||
while (!this.isBodyStart(this.peek)) {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
var media = StringWrapper.substring(this.input, start, this.index);
|
|
||||||
this.advance(); // skip "{"
|
|
||||||
return new _Token(media, 'media');
|
|
||||||
}
|
|
||||||
|
|
||||||
advance() {
|
|
||||||
this.index++;
|
|
||||||
if (this.index >= this.length) {
|
|
||||||
this.peek = _$EOF;
|
|
||||||
} else {
|
|
||||||
this.peek = StringWrapper.charCodeAt(this.input, this.index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _Parser {
|
|
||||||
tokens: List<_Token>;
|
|
||||||
currentIndex: int;
|
|
||||||
|
|
||||||
constructor(input: string) {
|
|
||||||
this.tokens = new _Lexer(input).parse();
|
|
||||||
this.currentIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(): List<_Rule> {
|
|
||||||
var rules = [];
|
|
||||||
var rule;
|
|
||||||
while (isPresent(rule = this.parseRule())) {
|
|
||||||
ListWrapper.push(rules, rule);
|
|
||||||
}
|
|
||||||
return rules;
|
|
||||||
}
|
|
||||||
|
|
||||||
parseRule(): _Rule {
|
|
||||||
try {
|
|
||||||
if (this.getNext().type === 'media') {
|
|
||||||
return this.parseMedia();
|
|
||||||
} else {
|
|
||||||
return this.parseCssRule();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseMedia(): _Rule {
|
|
||||||
this.advance('media');
|
|
||||||
var media = this.getCurrent().string;
|
|
||||||
var rules = [];
|
|
||||||
while (this.getNext().type !== 'rparen') {
|
|
||||||
ListWrapper.push(rules, this.parseCssRule());
|
|
||||||
}
|
|
||||||
this.advance('rparen');
|
|
||||||
return new _Rule(media.trim(), null, rules);
|
|
||||||
}
|
|
||||||
|
|
||||||
parseCssRule() {
|
|
||||||
this.advance('selector');
|
|
||||||
var selector = this.getCurrent().string;
|
|
||||||
this.advance('body');
|
|
||||||
var body = this.getCurrent().string;
|
|
||||||
return new _Rule(selector, body, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
advance(expected: string) {
|
|
||||||
this.currentIndex++;
|
|
||||||
if (this.getCurrent().type !== expected) {
|
|
||||||
throw new BaseException(`Unexpected token "${this.getCurrent().type}". Expected "${expected}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getNext(): _Token {
|
|
||||||
return this.tokens[this.currentIndex + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrent(): _Token {
|
|
||||||
return this.tokens[this.currentIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class _Rule {
|
|
||||||
selectorText: string;
|
|
||||||
body: string;
|
|
||||||
rules: List<_Rule>;
|
|
||||||
|
|
||||||
constructor(selectorText: string, body: string, rules: List<_Rule>) {
|
|
||||||
this.selectorText = selectorText;
|
|
||||||
this.body = body;
|
|
||||||
this.rules = rules;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasNestedRules() {
|
|
||||||
return isPresent(this.rules);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1,21 @@
|
||||||
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
import {Type, isBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {DOM, Element} from 'angular2/src/facade/dom';
|
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {View} from './view';
|
import {View} from './view';
|
||||||
|
|
||||||
import {Content} from './shadow_dom_emulation/content_tag';
|
import {Content} from './shadow_dom_emulation/content_tag';
|
||||||
import {LightDom} from './shadow_dom_emulation/light_dom';
|
import {LightDom} from './shadow_dom_emulation/light_dom';
|
||||||
|
import {ShimComponent, ShimEmulatedComponent, ShimNativeComponent} from './shadow_dom_emulation/shim_component';
|
||||||
|
|
||||||
export class ShadowDomStrategy {
|
export class ShadowDomStrategy {
|
||||||
attachTemplate(el:Element, view:View){}
|
attachTemplate(el:Element, view:View){}
|
||||||
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
constructLightDom(lightDomView:View, shadowDomView:View, el:Element){}
|
||||||
polyfillDirectives():List<Type>{ return null; }
|
polyfillDirectives():List<Type>{ return null; }
|
||||||
shim(): boolean { return false; }
|
|
||||||
extractStyles(): boolean { return false; }
|
extractStyles(): boolean { return false; }
|
||||||
|
getShimComponent(component: Type): ShimComponent {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||||
|
@ -31,12 +36,12 @@ export class EmulatedShadowDomStrategy extends ShadowDomStrategy {
|
||||||
return [Content];
|
return [Content];
|
||||||
}
|
}
|
||||||
|
|
||||||
shim(): boolean {
|
extractStyles(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractStyles(): boolean {
|
getShimComponent(component: Type): ShimComponent {
|
||||||
return true;
|
return new ShimEmulatedComponent(component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,12 +62,12 @@ export class NativeShadowDomStrategy extends ShadowDomStrategy {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
shim(): boolean {
|
extractStyles(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractStyles(): boolean {
|
getShimComponent(component: Type): ShimComponent {
|
||||||
return false;
|
return new ShimNativeComponent(component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -535,12 +535,15 @@ export class ProtoView {
|
||||||
): ProtoView {
|
): ProtoView {
|
||||||
|
|
||||||
DOM.addClass(insertionElement, NG_BINDING_CLASS);
|
DOM.addClass(insertionElement, NG_BINDING_CLASS);
|
||||||
|
var cmpType = rootComponentAnnotatedType.type;
|
||||||
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
|
var rootProtoView = new ProtoView(insertionElement, protoChangeDetector, shadowDomStrategy);
|
||||||
rootProtoView.instantiateInPlace = true;
|
rootProtoView.instantiateInPlace = true;
|
||||||
var binder = rootProtoView.bindElement(
|
var binder = rootProtoView.bindElement(
|
||||||
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
|
new ProtoElementInjector(null, 0, [cmpType], true));
|
||||||
binder.componentDirective = rootComponentAnnotatedType;
|
binder.componentDirective = rootComponentAnnotatedType;
|
||||||
binder.nestedProtoView = protoView;
|
binder.nestedProtoView = protoView;
|
||||||
|
var shimComponent = shadowDomStrategy.getShimComponent(cmpType);
|
||||||
|
shimComponent.shimHostElement(insertionElement);
|
||||||
return rootProtoView;
|
return rootProtoView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/test_lib';
|
|
||||||
|
|
||||||
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
|
||||||
import {ShadowDomTransformer} from 'angular2/src/core/compiler/pipeline/shadow_dom_transformer';
|
|
||||||
import {Component} from 'angular2/src/core/annotations/annotations';
|
|
||||||
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
|
||||||
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
|
||||||
import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css';
|
|
||||||
|
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
|
||||||
import {MapWrapper} from 'angular2/src/facade/collection';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('ShadowDomTransformer', () => {
|
|
||||||
function createPipeline(selector, strategy:ShadowDomStrategy, styleHost) {
|
|
||||||
var component = new Component({selector: selector});
|
|
||||||
var meta = new DirectiveMetadata(null, component);
|
|
||||||
var pipe = new ShadowDomTransformer(meta, strategy, styleHost);
|
|
||||||
pipe.clearCache();
|
|
||||||
return new CompilePipeline([pipe]);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('it should set ignoreBindings to true for style elements', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(false, false), host);
|
|
||||||
var results = pipeline.process(el('<div><style></style></div>'));
|
|
||||||
expect(results[0].ignoreBindings).toBe(false);
|
|
||||||
expect(results[1].ignoreBindings).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('css', () => {
|
|
||||||
it('should not extract the styles when extractStyles() is false', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(false, false), host);
|
|
||||||
var template = el('<style>.s{}</style>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(template).toHaveText('.s{}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move the styles to the host when extractStyles() is true', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
|
||||||
var template = el('<div><style>.s{}</style></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(template).toHaveText('');
|
|
||||||
expect(host).toHaveText('.s{}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should preserve original content when moving styles', () => {
|
|
||||||
var host = el('<div>original content</div>');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
|
||||||
var template = el('<div><style>.s{}</style></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(template).toHaveText('');
|
|
||||||
expect(host).toHaveText('.s{}original content');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should move the styles to the host in the original order', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
|
||||||
var template = el('<div><style>.s1{}</style><style>.s2{}</style></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(host).toHaveText('.s1{}.s2{}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should shim the styles when shim() and extractStyles() are true', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(true, true), host);
|
|
||||||
var template = el('<div><style>.s1{}</style></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(host).toHaveText(shimCssText('.s1{}', 'foo'));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should deduplicate styles before moving them when shim() is false', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(true, false), host);
|
|
||||||
var template = el('<div><style>.s1{}</style><style>.s1{}</style><style>.s1{}</style></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(host).toHaveText('.s1{}');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('html', () => {
|
|
||||||
it('should add an attribute to all children when shim() is true', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(false, true), host);
|
|
||||||
var template = el('<div><span></span></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(DOM.getOuterHTML(template)).toEqual('<div foo=""><span foo=""></span></div>')
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not modify the template when shim() is false', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo', new FakeStrategy(false, false), host);
|
|
||||||
var template = el('<div><span></span></div>');
|
|
||||||
pipeline.process(template);
|
|
||||||
expect(DOM.getOuterHTML(template)).toEqual('<div><span></span></div>')
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw with complex selectors', () => {
|
|
||||||
var host = DOM.createElement('div');
|
|
||||||
var pipeline = createPipeline('foo[bar]', new FakeStrategy(false, true), host);
|
|
||||||
var template = el('<div><span></span></div>');
|
|
||||||
expect(() => pipeline.process(template)).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class FakeStrategy extends ShadowDomStrategy {
|
|
||||||
_extractStyles: boolean;
|
|
||||||
_shim: boolean;
|
|
||||||
|
|
||||||
constructor(extractStyles: boolean, shim: boolean) {
|
|
||||||
super();
|
|
||||||
this._extractStyles = extractStyles;
|
|
||||||
this._shim = shim;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractStyles(): boolean {
|
|
||||||
return this._extractStyles;
|
|
||||||
}
|
|
||||||
|
|
||||||
shim(): boolean {
|
|
||||||
return this._shim;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
||||||
|
import {ShimShadowCss} from 'angular2/src/core/compiler/pipeline/shim_shadow_css';
|
||||||
|
import {ShimComponent} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component';
|
||||||
|
|
||||||
|
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||||
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
|
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
|
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('ShimShadowCss', () => {
|
||||||
|
function createPipeline(strategy:ShadowDomStrategy, styleHost) {
|
||||||
|
var component = new Component({selector: 'selector'});
|
||||||
|
var meta = new DirectiveMetadata(null, component);
|
||||||
|
var shimShadowCss = new ShimShadowCss(meta, strategy, styleHost);
|
||||||
|
return new CompilePipeline([shimShadowCss]);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('it should set ignoreBindings to true for style elements', () => {
|
||||||
|
var host = el('<div></div>');
|
||||||
|
var pipeline = createPipeline(new FakeStrategy(false), host);
|
||||||
|
var results = pipeline.process(el('<div><style></style></div>'));
|
||||||
|
expect(results[0].ignoreBindings).toBe(false);
|
||||||
|
expect(results[1].ignoreBindings).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not extract the styles when extractStyles() is false', () => {
|
||||||
|
var host = el('<div></div>');
|
||||||
|
var pipeline = createPipeline(new FakeStrategy(false), host);
|
||||||
|
var template = el('<style>.s{}</style>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(template).toHaveText('.s{}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the styles to the host when extractStyles() is true', () => {
|
||||||
|
var host = el('<div></div>');
|
||||||
|
var pipeline = createPipeline(new FakeStrategy(true), host);
|
||||||
|
var template = el('<div><style>.s{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(template).toHaveText('');
|
||||||
|
expect(host).toHaveText('/* shim */.s{}');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve original content when moving styles', () => {
|
||||||
|
var host = el('<div>original content</div>');
|
||||||
|
var pipeline = createPipeline(new FakeStrategy(true), host);
|
||||||
|
var template = el('<div><style>.s{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(template).toHaveText('');
|
||||||
|
expect(host).toHaveText('/* shim */.s{}original content');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should move the styles to the host in the original order', () => {
|
||||||
|
var host = el('<div></div>');
|
||||||
|
var pipeline = createPipeline(new FakeStrategy(true), host);
|
||||||
|
var template = el('<div><style>.s1{}</style><style>.s2{}</style></div>');
|
||||||
|
pipeline.process(template);
|
||||||
|
expect(host).toHaveText('/* shim */.s1{}/* shim */.s2{}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeStrategy extends ShadowDomStrategy {
|
||||||
|
_extractStyles: boolean;
|
||||||
|
|
||||||
|
constructor(extractStyles: boolean) {
|
||||||
|
super();
|
||||||
|
this._extractStyles = extractStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
extractStyles(): boolean {
|
||||||
|
return this._extractStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShimComponent(component: Type): ShimComponent {
|
||||||
|
return new FakeShimComponent(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeShimComponent extends ShimComponent {
|
||||||
|
constructor(component: Type) {
|
||||||
|
super(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
shimCssText(cssText: string): string {
|
||||||
|
return '/* shim */' + cssText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
import {describe, beforeEach, expect, it, iit, ddescribe, el} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {CompilePipeline} from 'angular2/src/core/compiler/pipeline/compile_pipeline';
|
||||||
|
import {ShimShadowDom} from 'angular2/src/core/compiler/pipeline/shim_shadow_dom';
|
||||||
|
import {CompileElement} from 'angular2/src/core/compiler/pipeline/compile_element';
|
||||||
|
import {CompileStep} from 'angular2/src/core/compiler/pipeline/compile_step';
|
||||||
|
import {CompileControl} from 'angular2/src/core/compiler/pipeline/compile_control';
|
||||||
|
import {ShimComponent} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component';
|
||||||
|
|
||||||
|
import {Component} from 'angular2/src/core/annotations/annotations';
|
||||||
|
import {DirectiveMetadata} from 'angular2/src/core/compiler/directive_metadata';
|
||||||
|
import {ShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_strategy';
|
||||||
|
|
||||||
|
import {Type, isBlank} from 'angular2/src/facade/lang';
|
||||||
|
import {DOM, Element} from 'angular2/src/facade/dom';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('ShimShadowDom', () => {
|
||||||
|
function createPipeline(ignoreBindings: boolean) {
|
||||||
|
var component = new Component({selector: 'selector'});
|
||||||
|
var meta = new DirectiveMetadata(null, component);
|
||||||
|
var shimShadowDom = new ShimShadowDom(meta, new FakeStrategy());
|
||||||
|
|
||||||
|
return new CompilePipeline([
|
||||||
|
new MockStep((parent, current, control) => {
|
||||||
|
current.ignoreBindings = ignoreBindings;
|
||||||
|
}),
|
||||||
|
new MockStep((parent, current, control) => {
|
||||||
|
var el = current.element;
|
||||||
|
if (DOM.hasClass(el, 'host')) {
|
||||||
|
current.componentDirective = new DirectiveMetadata(SomeComponent, null);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
shimShadowDom
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should add the content attribute to content element', () => {
|
||||||
|
var pipeline = createPipeline(false);
|
||||||
|
var results = pipeline.process(el('<div></div>'));
|
||||||
|
expect(DOM.getAttribute(results[0].element, '_ngcontent')).toEqual('content');
|
||||||
|
expect(isBlank(DOM.getAttribute(results[0].element, '_nghost'))).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add both the content and host attributes to host element', () => {
|
||||||
|
var pipeline = createPipeline(false);
|
||||||
|
var results = pipeline.process(el('<div class="host"></div>'));
|
||||||
|
expect(DOM.getAttribute(results[0].element, '_ngcontent')).toEqual('content');
|
||||||
|
expect(DOM.getAttribute(results[0].element, '_nghost')).toEqual('host');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when ignoreBindings is true', () => {
|
||||||
|
var pipeline = createPipeline(true);
|
||||||
|
var results = pipeline.process(el('<div class="host"></div>'));
|
||||||
|
expect(isBlank(DOM.getAttribute(results[0].element, '_ngcontent'))).toBeTruthy();
|
||||||
|
expect(isBlank(DOM.getAttribute(results[0].element, '_nghost'))).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeStrategy extends ShadowDomStrategy {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getShimComponent(component: Type): ShimComponent {
|
||||||
|
return new FakeShimComponent(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeShimComponent extends ShimComponent {
|
||||||
|
constructor(component: Type) {
|
||||||
|
super(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
shimContentElement(element: Element) {
|
||||||
|
DOM.setAttribute(element, '_ngcontent', 'content');
|
||||||
|
}
|
||||||
|
|
||||||
|
shimHostElement(element: Element) {
|
||||||
|
DOM.setAttribute(element, '_nghost', 'host');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockStep extends CompileStep {
|
||||||
|
processClosure:Function;
|
||||||
|
constructor(process) {
|
||||||
|
super();
|
||||||
|
this.processClosure = process;
|
||||||
|
}
|
||||||
|
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
|
||||||
|
this.processClosure(parent, current, control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeComponent {}
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ShimNativeComponent,
|
||||||
|
ShimEmulatedComponent,
|
||||||
|
resetShimComponentCache
|
||||||
|
} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_component';
|
||||||
|
|
||||||
|
import {ShadowCss} from 'angular2/src/core/compiler/shadow_dom_emulation/shadow_css';
|
||||||
|
|
||||||
|
import {Type} from 'angular2/src/facade/lang';
|
||||||
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('ShimComponent', () => {
|
||||||
|
|
||||||
|
describe('ShimNativeComponent', () => {
|
||||||
|
function createShim(component: Type) {
|
||||||
|
return new ShimNativeComponent(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should not transform the CSS', () => {
|
||||||
|
var css = '.foo {color: blue;} :host{color: red;}';
|
||||||
|
var shim = createShim(SomeComponent);
|
||||||
|
var shimCss = shim.shimCssText(css);
|
||||||
|
expect(css).toEqual(shimCss);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not transform content elements', () => {
|
||||||
|
var html = '<p>foo</p>';
|
||||||
|
var element = el(html);
|
||||||
|
var shim = createShim(SomeComponent);
|
||||||
|
shim.shimContentElement(element);
|
||||||
|
expect(DOM.getOuterHTML(element)).toEqual(html);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not transform host elements', () => {
|
||||||
|
var html = '<p>foo</p>';
|
||||||
|
var element = el(html);
|
||||||
|
var shim = createShim(SomeComponent);
|
||||||
|
shim.shimHostElement(element);
|
||||||
|
expect(DOM.getOuterHTML(element)).toEqual(html);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ShimEmulatedComponent', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
resetShimComponentCache();
|
||||||
|
});
|
||||||
|
|
||||||
|
function createShim(component: Type) {
|
||||||
|
return new ShimEmulatedComponent(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should transform the CSS', () => {
|
||||||
|
var css = '.foo {color: blue;} :host{color: red;}';
|
||||||
|
var shim = createShim(SomeComponent);
|
||||||
|
var shimCss = shim.shimCssText(css);
|
||||||
|
expect(shimCss).not.toEqual(css);
|
||||||
|
var shadowCss = new ShadowCss();
|
||||||
|
expect(shimCss).toEqual(shadowCss.shimCssText(css, '_ngcontent-0', '_nghost-0'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should transform content elements', () => {
|
||||||
|
var html = '<p>foo</p>';
|
||||||
|
var element = el(html);
|
||||||
|
var shim = createShim(SomeComponent);
|
||||||
|
shim.shimContentElement(element);
|
||||||
|
expect(DOM.getOuterHTML(element)).toEqual('<p _ngcontent-0="">foo</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not transform host elements', () => {
|
||||||
|
var html = '<p>foo</p>';
|
||||||
|
var element = el(html);
|
||||||
|
var shim = createShim(SomeComponent);
|
||||||
|
shim.shimHostElement(element);
|
||||||
|
expect(DOM.getOuterHTML(element)).toEqual('<p _nghost-0="">foo</p>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate the same output for the same component', () => {
|
||||||
|
var html = '<p>foo</p>';
|
||||||
|
var content1 = el(html);
|
||||||
|
var host1 = el(html);
|
||||||
|
var css = '.foo {color: blue;} :host{color: red;}';
|
||||||
|
var shim1 = createShim(SomeComponent);
|
||||||
|
shim1.shimContentElement(content1);
|
||||||
|
shim1.shimHostElement(host1);
|
||||||
|
var shimCss1 = shim1.shimCssText(css);
|
||||||
|
|
||||||
|
var content2 = el(html);
|
||||||
|
var host2 = el(html);
|
||||||
|
var shim2 = createShim(SomeComponent);
|
||||||
|
shim2.shimContentElement(content2);
|
||||||
|
shim2.shimHostElement(host2);
|
||||||
|
var shimCss2 = shim2.shimCssText(css);
|
||||||
|
|
||||||
|
expect(DOM.getOuterHTML(content1)).toEqual(DOM.getOuterHTML(content2));
|
||||||
|
expect(DOM.getOuterHTML(host1)).toEqual(DOM.getOuterHTML(host2));
|
||||||
|
expect(shimCss1).toEqual(shimCss2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should generate different outputs for different components', () => {
|
||||||
|
var html = '<p>foo</p>';
|
||||||
|
var content1 = el(html);
|
||||||
|
var host1 = el(html);
|
||||||
|
var css = '.foo {color: blue;} :host{color: red;}';
|
||||||
|
var shim1 = createShim(SomeComponent);
|
||||||
|
shim1.shimContentElement(content1);
|
||||||
|
shim1.shimHostElement(host1);
|
||||||
|
var shimCss1 = shim1.shimCssText(css);
|
||||||
|
|
||||||
|
var content2 = el(html);
|
||||||
|
var host2 = el(html);
|
||||||
|
var shim2 = createShim(SomeComponent2);
|
||||||
|
shim2.shimContentElement(content2);
|
||||||
|
shim2.shimHostElement(host2);
|
||||||
|
var shimCss2 = shim2.shimCssText(css);
|
||||||
|
|
||||||
|
expect(DOM.getOuterHTML(content1)).not.toEqual(DOM.getOuterHTML(content2));
|
||||||
|
expect(DOM.getOuterHTML(host1)).not.toEqual(DOM.getOuterHTML(host2));
|
||||||
|
expect(shimCss1).not.toEqual(shimCss2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeComponent {}
|
||||||
|
class SomeComponent2 {}
|
|
@ -1,102 +0,0 @@
|
||||||
import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'angular2/test_lib';
|
|
||||||
import {shimCssText} from 'angular2/src/core/compiler/shadow_dom_emulation/shim_css';
|
|
||||||
|
|
||||||
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('shim css', function() {
|
|
||||||
|
|
||||||
function s(css: string, tag:string) {
|
|
||||||
var shim = shimCssText(css, tag);
|
|
||||||
var nlRegexp = RegExpWrapper.create('\\n');
|
|
||||||
return StringWrapper.replaceAll(shim, nlRegexp, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should handle empty string', () => {
|
|
||||||
expect(s('', 'a')).toEqual('');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add an attribute to every rule', () => {
|
|
||||||
var css = 'one {color: red;}two {color: red;}';
|
|
||||||
var expected = 'one[a] {color: red;}two[a] {color: red;}';
|
|
||||||
expect(s(css, 'a')).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should hanlde invalid css', () => {
|
|
||||||
var css = 'one {color: red;}garbage';
|
|
||||||
var expected = 'one[a] {color: red;}';
|
|
||||||
expect(s(css, 'a')).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add an attribute to every selector', () => {
|
|
||||||
var css = 'one, two {color: red;}';
|
|
||||||
var expected = 'one[a], two[a] {color: red;}';
|
|
||||||
expect(s(css, 'a')).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle media rules', () => {
|
|
||||||
var css = '@media screen and (max-width: 800px) {div {font-size: 50px;}}';
|
|
||||||
var expected = '@media screen and (max-width: 800px) {div[a] {font-size: 50px;}}';
|
|
||||||
expect(s(css, 'a')).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle media rules with simple rules', () => {
|
|
||||||
var css = '@media screen and (max-width: 800px) {div {font-size: 50px;}} div {}';
|
|
||||||
var expected = '@media screen and (max-width: 800px) {div[a] {font-size: 50px;}}div[a] {}';
|
|
||||||
expect(s(css, 'a')).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle complicated selectors', () => {
|
|
||||||
expect(s('one::before {}', 'a')).toEqual('one[a]::before {}');
|
|
||||||
expect(s('one two {}', 'a')).toEqual('one[a] two[a] {}');
|
|
||||||
expect(s('one>two {}', 'a')).toEqual('one[a]>two[a] {}');
|
|
||||||
expect(s('one+two {}', 'a')).toEqual('one[a]+two[a] {}');
|
|
||||||
expect(s('one~two {}', 'a')).toEqual('one[a]~two[a] {}');
|
|
||||||
expect(s('.one.two > three {}', 'a')).toEqual('.one.two[a]>three[a] {}');
|
|
||||||
expect(s('one[attr="value"] {}', 'a')).toEqual('one[attr="value"][a] {}');
|
|
||||||
expect(s('one[attr=value] {}', 'a')).toEqual('one[attr=value][a] {}');
|
|
||||||
expect(s('one[attr^="value"] {}', 'a')).toEqual('one[attr^="value"][a] {}');
|
|
||||||
expect(s('one[attr\$="value"] {}', 'a')).toEqual('one[attr\$="value"][a] {}');
|
|
||||||
expect(s('one[attr*="value"] {}', 'a')).toEqual('one[attr*="value"][a] {}');
|
|
||||||
expect(s('one[attr|="value"] {}', 'a')).toEqual('one[attr|="value"][a] {}');
|
|
||||||
expect(s('one[attr] {}', 'a')).toEqual('one[attr][a] {}');
|
|
||||||
expect(s('[is="one"] {}', 'a')).toEqual('one[a] {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle :host', () => {
|
|
||||||
expect(s(':host {}', 'a')).toEqual('a {}');
|
|
||||||
expect(s(':host(.x,.y) {}', 'a')).toEqual('a.x, a.y {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support polyfill-next-selector', () => {
|
|
||||||
var css = s("polyfill-next-selector {content: 'x > y'} z {}", 'a');
|
|
||||||
expect(css).toEqual('x[a]>y[a] {}');
|
|
||||||
|
|
||||||
css = s('polyfill-next-selector {content: "x > y"} z {}', 'a');
|
|
||||||
expect(css).toEqual('x[a]>y[a] {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support polyfill-unscoped-next-selector', () => {
|
|
||||||
var css = s("polyfill-unscoped-next-selector {content: 'x > y'} z {}", 'a');
|
|
||||||
expect(css).toEqual('x > y {}');
|
|
||||||
|
|
||||||
css = s('polyfill-unscoped-next-selector {content: "x > y"} z {}', 'a');
|
|
||||||
expect(css).toEqual('x > y {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support polyfill-non-strict-next-selector', () => {
|
|
||||||
var css = s('polyfill-non-strict {} one, two {}', 'a');
|
|
||||||
expect(css).toEqual('a one, a two {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle ::shadow', () => {
|
|
||||||
var css = s('polyfill-non-strict {} x::shadow > y {}', 'a');
|
|
||||||
expect(css).toEqual('a x > y {}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle /deep/', () => {
|
|
||||||
var css = s('polyfill-non-strict {} x /deep/ y {}', 'a');
|
|
||||||
expect(css).toEqual('a x y {}');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in New Issue