diff --git a/modules/angular2/src/core/compiler/shadow_dom_emulation/shadow_css.js b/modules/angular2/src/core/compiler/shadow_dom_emulation/shadow_css.js
new file mode 100644
index 0000000000..0630bc13c5
--- /dev/null
+++ b/modules/angular2/src/core/compiler/shadow_dom_emulation/shadow_css.js
@@ -0,0 +1,555 @@
+import {
+ StyleElement,
+ DOM,
+ CssRule,
+ CssKeyframesRule,
+ CSSRuleWrapper
+} from 'angular2/src/facade/dom';
+import {List, ListWrapper} from 'angular2/src/facade/collection';
+import {
+ StringWrapper,
+ RegExp,
+ RegExpWrapper,
+ RegExpMatcherWrapper,
+ isPresent,
+ isBlank,
+ BaseException,
+ int
+} from 'angular2/src/facade/lang';
+
+/**
+ * This file is a port of shadowCSS from webcomponents.js to AtScript.
+ *
+ * Please make sure to keep to edits in sync with the source file.
+ *
+ * Source: https://github.com/webcomponents/webcomponentsjs/blob/4efecd7e0e/src/ShadowCSS/ShadowCSS.js
+ *
+ * The original file level comment is reproduced below
+ */
+
+/*
+ This is a limited shim for ShadowDOM css styling.
+ https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
+
+ The intention here is to support only the styling features which can be
+ relatively simply implemented. The goal is to allow users to avoid the
+ most obvious pitfalls and do so without compromising performance significantly.
+ For ShadowDOM styling that's not covered here, a set of best practices
+ can be provided that should allow users to accomplish more complex styling.
+
+ The following is a list of specific ShadowDOM styling features and a brief
+ discussion of the approach used to shim.
+
+ Shimmed features:
+
+ * :host, :host-context: ShadowDOM allows styling of the shadowRoot's host
+ element using the :host rule. To shim this feature, the :host styles are
+ reformatted and prefixed with a given scope name and promoted to a
+ document level stylesheet.
+ For example, given a scope name of .foo, a rule like this:
+
+ :host {
+ background: red;
+ }
+ }
+
+ becomes:
+
+ .foo {
+ background: red;
+ }
+
+ * encapsultion: Styles defined within ShadowDOM, apply only to
+ dom inside the ShadowDOM. Polymer uses one of two techniques to imlement
+ this feature.
+
+ By default, rules are prefixed with the host element tag name
+ as a descendant selector. This ensures styling does not leak out of the 'top'
+ of the element's ShadowDOM. For example,
+
+ div {
+ font-weight: bold;
+ }
+
+ becomes:
+
+ x-foo div {
+ font-weight: bold;
+ }
+
+ becomes:
+
+
+ Alternatively, if WebComponents.ShadowCSS.strictStyling is set to true then
+ selectors are scoped by adding an attribute selector suffix to each
+ simple selector that contains the host element tag name. Each element
+ in the element's ShadowDOM template is also given the scope attribute.
+ Thus, these rules match only elements that have the scope attribute.
+ For example, given a scope name of x-foo, a rule like this:
+
+ div {
+ font-weight: bold;
+ }
+
+ becomes:
+
+ div[x-foo] {
+ font-weight: bold;
+ }
+
+ Note that elements that are dynamically added to a scope must have the scope
+ selector added to them manually.
+
+ * upper/lower bound encapsulation: Styles which are defined outside a
+ shadowRoot should not cross the ShadowDOM boundary and should not apply
+ inside a shadowRoot.
+
+ This styling behavior is not emulated. Some possible ways to do this that
+ were rejected due to complexity and/or performance concerns include: (1) reset
+ every possible property for every possible selector for a given scope name;
+ (2) re-implement css in javascript.
+
+ As an alternative, users should make sure to use selectors
+ specific to the scope in which they are working.
+
+ * ::distributed: This behavior is not emulated. It's often not necessary
+ to style the contents of a specific insertion point and instead, descendants
+ of the host element can be styled selectively. Users can also create an
+ extra node around an insertion point and style that node's contents
+ via descendent selectors. For example, with a shadowRoot like this:
+
+
+
+
+ could become:
+
+
+
+
+
+
+ Note the use of @polyfill in the comment above a ShadowDOM specific style
+ declaration. This is a directive to the styling shim to use the selector
+ in comments in lieu of the next selector when running under polyfill.
+*/
+
+export class ShadowCss {
+ strictStyling: boolean;
+
+ constructor() {
+ this.strictStyling = true;
+ }
+
+ /*
+ * Shim a style element with the given selector. Returns cssText that can
+ * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
+ */
+ shimStyle(style: StyleElement, selector: string): string {
+ var cssText = DOM.getText(style);
+ return this.shimCssText(cssText, selector);
+ }
+
+ /*
+ * Shim some cssText with the given selector. Returns cssText that can
+ * be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
+ */
+ shimCssText(cssText: string, selector: string): string {
+ cssText = this._insertDirectives(cssText);
+ return this._scopeCssText(cssText, selector);
+ }
+
+ _insertDirectives(cssText: string): string {
+ cssText = this._insertPolyfillDirectivesInCssText(cssText);
+ return this._insertPolyfillRulesInCssText(cssText);
+ }
+
+ /*
+ * Process styles to convert native ShadowDOM rules that will trip
+ * up the css parser; we rely on decorating the stylesheet with inert rules.
+ *
+ * For example, we convert this rule:
+ *
+ * polyfill-next-selector { content: ':host menu-item'; }
+ * ::content menu-item {
+ *
+ * to this:
+ *
+ * scopeName menu-item {
+ *
+ **/
+ _insertPolyfillDirectivesInCssText(cssText: string): string {
+ // Difference with webcomponents.js: does not handle comments
+ return StringWrapper.replaceAllMapped(cssText, _cssContentNextSelectorRe, function(m) {
+ return m[1] + '{';
+ });
+ }
+
+ /*
+ * Process styles to add rules which will only apply under the polyfill
+ *
+ * For example, we convert this rule:
+ *
+ * polyfill-rule {
+ * content: ':host menu-item';
+ * ...
+ * }
+ *
+ * to this:
+ *
+ * scopeName menu-item {...}
+ *
+ **/
+ _insertPolyfillRulesInCssText(cssText: string): string {
+ // Difference with webcomponents.js: does not handle comments
+ return StringWrapper.replaceAllMapped(cssText, _cssContentRuleRe, function(m) {
+ var rule = m[0];
+ rule = StringWrapper.replace(rule, m[1], '');
+ rule = StringWrapper.replace(rule, m[2], '');
+ return m[3] + rule;
+ });
+ }
+
+ /* Ensure styles are scoped. Pseudo-scoping takes a rule like:
+ *
+ * .foo {... }
+ *
+ * and converts this to
+ *
+ * scopeName .foo { ... }
+ */
+ _scopeCssText(cssText: string, scopeSelector: string): string {
+
+ var unscoped = this._extractUnscopedRulesFromCssText(cssText);
+ cssText = this._insertPolyfillHostInCssText(cssText);
+ cssText = this._convertColonHost(cssText);
+ cssText = this._convertColonHostContext(cssText);
+ cssText = this._convertShadowDOMSelectors(cssText);
+ if (isPresent(scopeSelector)) {
+ _withCssRules(cssText, (rules) => {
+ cssText = this._scopeRules(rules, scopeSelector);
+ });
+ }
+ cssText = cssText + '\n' + unscoped;
+ return cssText.trim();
+ }
+
+ /*
+ * Process styles to add rules which will only apply under the polyfill
+ * and do not process via CSSOM. (CSSOM is destructive to rules on rare
+ * occasions, e.g. -webkit-calc on Safari.)
+ * For example, we convert this rule:
+ *
+ * @polyfill-unscoped-rule {
+ * content: 'menu-item';
+ * ... }
+ *
+ * to this:
+ *
+ * menu-item {...}
+ *
+ **/
+ _extractUnscopedRulesFromCssText(cssText: string): string {
+ // Difference with webcomponents.js: does not handle comments
+ var r = '', m;
+ var matcher = RegExpWrapper.matcher(_cssContentUnscopedRuleRe, cssText);
+ while (isPresent(m = RegExpMatcherWrapper.next(matcher))) {
+ var rule = m[0];
+ rule = StringWrapper.replace(rule, m[2], '');
+ rule = StringWrapper.replace(rule, m[1], m[3]);
+ r = rule + '\n\n';
+ }
+ return r;
+ }
+
+ /*
+ * convert a rule like :host(.foo) > .bar { }
+ *
+ * to
+ *
+ * scopeName.foo > .bar
+ */
+ _convertColonHost(cssText: string): string {
+ return this._convertColonRule(cssText, _cssColonHostRe,
+ this._colonHostPartReplacer);
+ }
+
+ /*
+ * convert a rule like :host-context(.foo) > .bar { }
+ *
+ * to
+ *
+ * scopeName.foo > .bar, .foo scopeName > .bar { }
+ *
+ * and
+ *
+ * :host-context(.foo:host) .bar { ... }
+ *
+ * to
+ *
+ * scopeName.foo .bar { ... }
+ */
+ _convertColonHostContext(cssText: string): string {
+ return this._convertColonRule(cssText, _cssColonHostContextRe,
+ this._colonHostContextPartReplacer);
+ }
+
+ _convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string {
+ // p1 = :host, p2 = contents of (), p3 rest of rule
+ return StringWrapper.replaceAllMapped(cssText, regExp, function(m) {
+ if (isPresent(m[2])) {
+ var parts = m[2].split(','), r = [];
+ for (var i = 0; i < parts.length; i++) {
+ var p = parts[i];
+ if (isBlank(p)) break;
+ p = p.trim();
+ ListWrapper.push(r, partReplacer(_polyfillHostNoCombinator, p, m[3]));
+ }
+ return r.join(',');
+ } else {
+ return _polyfillHostNoCombinator + m[3];
+ }
+ });
+ }
+
+ _colonHostContextPartReplacer(host: string, part: string, suffix: string): string {
+ if (StringWrapper.contains(part, _polyfillHost)) {
+ return this._colonHostPartReplacer(host, part, suffix);
+ } else {
+ return host + part + suffix + ', ' + part + ' ' + host + suffix;
+ }
+ }
+
+ _colonHostPartReplacer(host: string, part: string, suffix: string): string {
+ return host + StringWrapper.replace(part, _polyfillHost, '') + suffix;
+ }
+
+ /*
+ * Convert combinators like ::shadow and pseudo-elements like ::content
+ * by replacing with space.
+ */
+ _convertShadowDOMSelectors(cssText: string): string {
+ for (var i = 0; i < _shadowDOMSelectorsRe.length; i++) {
+ cssText = StringWrapper.replaceAll(cssText, _shadowDOMSelectorsRe[i], ' ');
+ }
+ return cssText;
+ }
+
+ // change a selector like 'div' to 'name div'
+ _scopeRules(cssRules, scopeSelector: string): string {
+ var cssText = '';
+ if (isPresent(cssRules)) {
+ for (var i = 0; i < cssRules.length; i++) {
+ var rule = cssRules[i];
+ if (CSSRuleWrapper.isStyleRule(rule) || CSSRuleWrapper.isPageRule(rule)) {
+ cssText += this._scopeSelector(rule.selectorText, scopeSelector,
+ this.strictStyling) + ' {\n';
+ cssText += this._propertiesFromRule(rule) + '\n}\n\n';
+ } else if (CSSRuleWrapper.isMediaRule(rule)) {
+ cssText += '@media ' + rule.media.mediaText + ' {\n';
+ cssText += this._scopeRules(rule.cssRules, scopeSelector);
+ cssText += '\n}\n\n';
+ } else {
+ // KEYFRAMES_RULE in IE throws when we query cssText
+ // when it contains a -webkit- property.
+ // if this happens, we fallback to constructing the rule
+ // from the CSSRuleSet
+ // https://connect.microsoft.com/IE/feedbackdetail/view/955703/accessing-csstext-of-a-keyframe-rule-that-contains-a-webkit-property-via-cssom-generates-exception
+ try {
+ if (isPresent(rule.cssText)) {
+ cssText += rule.cssText + '\n\n';
+ }
+ } catch(x) {
+ if (CSSRuleWrapper.isKeyframesRule(rule) && isPresent(rule.cssRules)) {
+ cssText += this._ieSafeCssTextFromKeyFrameRule(rule);
+ }
+ }
+ }
+ }
+ }
+ return cssText;
+ }
+
+ _ieSafeCssTextFromKeyFrameRule(rule: CssKeyframesRule): string {
+ var cssText = '@keyframes ' + rule.name + ' {';
+ for (var i = 0; i < rule.cssRules.length; i++) {
+ var r = rule.cssRules[i];
+ cssText += ' ' + r.keyText + ' {' + r.style.cssText + '}';
+ }
+ cssText += ' }';
+ return cssText;
+ }
+
+ _scopeSelector(selector: string, scopeSelector: string, strict: boolean): string {
+ var r = [], parts = selector.split(',');
+ for (var i = 0; i < parts.length; i++) {
+ var p = parts[i];
+ p = p.trim();
+ if (this._selectorNeedsScoping(p, scopeSelector)) {
+ p = strict && !StringWrapper.contains(p, _polyfillHostNoCombinator) ?
+ this._applyStrictSelectorScope(p, scopeSelector) :
+ this._applySelectorScope(p, scopeSelector);
+ }
+ ListWrapper.push(r, p);
+ }
+ return r.join(', ');
+ }
+
+ _selectorNeedsScoping(selector: string, scopeSelector: string): boolean {
+ var re = this._makeScopeMatcher(scopeSelector);
+ return !isPresent(RegExpWrapper.firstMatch(re, selector));
+ }
+
+ _makeScopeMatcher(scopeSelector: string): RegExp {
+ var lre = RegExpWrapper.create('\\[');
+ var rre = RegExpWrapper.create('\\]');
+ scopeSelector = StringWrapper.replaceAll(scopeSelector, lre, '\\[');
+ scopeSelector = StringWrapper.replaceAll(scopeSelector, rre, '\\]');
+ return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
+ }
+
+ _applySelectorScope(selector: string, scopeSelector: string): string {
+ // Difference from webcomponentsjs: scopeSelector could not be an array
+ return this._applySimpleSelectorScope(selector, scopeSelector);
+ }
+
+ // scope via name and [is=name]
+ _applySimpleSelectorScope(selector: string, scopeSelector: string): string {
+ if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) {
+ selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, scopeSelector);
+ return StringWrapper.replaceAll(selector, _polyfillHostRe, scopeSelector + ' ');
+ } else {
+ return scopeSelector + ' ' + selector;
+ }
+ }
+
+ // return a selector with [name] suffix on each simple selector
+ // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
+ _applyStrictSelectorScope(selector: string, scopeSelector: string): string {
+ var isRe = RegExpWrapper.create('\\[is=([^\\]]*)\\]');
+ scopeSelector = StringWrapper.replaceAllMapped(scopeSelector, isRe, (m) => m[1]);
+ var splits = [' ', '>', '+', '~'],
+ scoped = selector,
+ attrName = '[' + scopeSelector + ']';
+ for (var i = 0; i < splits.length; i++) {
+ var sep = splits[i];
+ var parts = scoped.split(sep);
+ scoped = ListWrapper.map(parts, function(p) {
+ // remove :host since it should be unnecessary
+ var t = StringWrapper.replaceAll(p.trim(), _polyfillHostRe, '');
+ if (t.length > 0 &&
+ !ListWrapper.contains(splits, t) &&
+ !StringWrapper.contains(t, attrName)) {
+ var re = RegExpWrapper.create('([^:]*)(:*)(.*)');
+ var m = RegExpWrapper.firstMatch(re, t);
+ if (isPresent(m)) {
+ p = m[1] + attrName + m[2] + m[3];
+ }
+ }
+ return p;
+ }).join(sep);
+ }
+ return scoped;
+ }
+
+ _insertPolyfillHostInCssText(selector: string): string {
+ selector = StringWrapper.replaceAll(selector, _colonHostContextRe, _polyfillHostContext);
+ selector = StringWrapper.replaceAll(selector, _colonHostRe, _polyfillHost);
+ return selector;
+ }
+
+ _propertiesFromRule(rule): string {
+ var cssText = rule.style.cssText;
+ // TODO(sorvell): Safari cssom incorrectly removes quotes from the content
+ // property. (https://bugs.webkit.org/show_bug.cgi?id=118045)
+ // don't replace attr rules
+ var attrRe = RegExpWrapper.create('[\'"]+|attr');
+ if (rule.style.content.length > 0 &&
+ !isPresent(RegExpWrapper.firstMatch(attrRe, rule.style.content))) {
+ var contentRe = RegExpWrapper.create('content:[^;]*;');
+ cssText = StringWrapper.replaceAll(cssText, contentRe, 'content: \'' +
+ rule.style.content + '\';');
+ }
+ // TODO(sorvell): we can workaround this issue here, but we need a list
+ // of troublesome properties to fix https://github.com/Polymer/platform/issues/53
+ //
+ // inherit rules can be omitted from cssText
+ // TODO(sorvell): remove when Blink bug is fixed:
+ // https://code.google.com/p/chromium/issues/detail?id=358273
+ //var style = rule.style;
+ //for (var i = 0; i < style.length; i++) {
+ // var name = style.item(i);
+ // var value = style.getPropertyValue(name);
+ // if (value == 'initial') {
+ // cssText += name + ': initial; ';
+ // }
+ //}
+ return cssText;
+ }
+}
+
+var _cssContentNextSelectorRe = RegExpWrapper.create(
+ 'polyfill-next-selector[^}]*content:[\\s]*?[\'"](.*?)[\'"][;\\s]*}([^{]*?){', 'im');
+var _cssContentRuleRe = RegExpWrapper.create(
+ '(polyfill-rule)[^}]*(content:[\\s]*[\'"](.*?)[\'"])[;\\s]*[^}]*}', 'im');
+var _cssContentUnscopedRuleRe = RegExpWrapper.create(
+ '(polyfill-unscoped-rule)[^}]*(content:[\\s]*[\'"](.*?)[\'"])[;\\s]*[^}]*}', 'im');
+var _polyfillHost = '-shadowcsshost';
+// note: :host-context pre-processed to -shadowcsshostcontext.
+var _polyfillHostContext = '-shadowcsscontext';
+var _parenSuffix = ')(?:\\((' +
+ '(?:\\([^)(]*\\)|[^)(]*)+?' +
+ ')\\))?([^,{]*)';
+var _cssColonHostRe = RegExpWrapper.create('(' + _polyfillHost + _parenSuffix, 'im');
+var _cssColonHostContextRe = RegExpWrapper.create('(' + _polyfillHostContext + _parenSuffix, 'im');
+var _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
+var _shadowDOMSelectorsRe = [
+ RegExpWrapper.create('/shadow/'),
+ RegExpWrapper.create('/shadow-deep/'),
+ RegExpWrapper.create('::shadow'),
+ RegExpWrapper.create('/deep/'),
+ RegExpWrapper.create('::content'),
+];
+var _selectorReSuffix = '([>\\s~+\[.,{:][\\s\\S]*)?$';
+var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
+var _colonHostRe = RegExpWrapper.create(':host', 'im');
+var _colonHostContextRe = RegExpWrapper.create(':host-context', 'im');
+
+function _cssTextToStyle(cssText: string) {
+ return DOM.createStyleElement(cssText);
+}
+
+function _cssToRules(cssText: string) {
+ var style = _cssTextToStyle(cssText);
+ DOM.appendChild(DOM.defaultDoc().head, style);
+ var rules = [];
+ if (isPresent(style.sheet)) {
+ // TODO(sorvell): Firefox throws when accessing the rules of a stylesheet
+ // with an @import
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=625013
+ try {
+ rules = style.sheet.cssRules;
+ } catch(e) {
+ //
+ }
+ } else {
+ // console.warn('sheet not found', style);
+ }
+ DOM.remove(style);
+ return rules;
+}
+
+function _withCssRules(cssText: string, callback: Function) {
+ // Difference from webcomponentjs: remove the workaround for an old bug in Chrome
+ if (isBlank(callback)) return;
+ var rules = _cssToRules(cssText);
+ callback(rules);
+}
diff --git a/modules/angular2/src/facade/dom.dart b/modules/angular2/src/facade/dom.dart
index d9c3762067..79d0ec859c 100644
--- a/modules/angular2/src/facade/dom.dart
+++ b/modules/angular2/src/facade/dom.dart
@@ -4,6 +4,8 @@ import 'dart:html';
import 'dart:js' show JsObject, context;
export 'dart:html' show
+ CssRule,
+ CssKeyframesRule,
document,
DocumentFragment,
Element,
@@ -109,6 +111,9 @@ class DOM {
if (doc == null) doc = document;
return doc.createElement(tagName);
}
+ static createTextNode(String text, [HtmlDocument doc = null]) {
+ return new Text(text);
+ }
static createScriptTag(String attrName, String attrValue,
[HtmlDocument doc = null]) {
if (doc == null) doc = document;
@@ -183,3 +188,10 @@ class DOM {
static bool isElementNode(Node node) =>
node.nodeType == Node.ELEMENT_NODE;
}
+
+class CSSRuleWrapper {
+ static isPageRule(CssRule rule) => rule is CssPageRule;
+ static isStyleRule(CssRule rule) => rule is CssStyleRule;
+ static isMediaRule(CssRule rule) => rule is CssMediaRule;
+ static isKeyframesRule(CssRule rule) => rule is CssKeyframesRule;
+}
diff --git a/modules/angular2/src/facade/dom.es6 b/modules/angular2/src/facade/dom.es6
index 47566e6f94..f2175e218f 100644
--- a/modules/angular2/src/facade/dom.es6
+++ b/modules/angular2/src/facade/dom.es6
@@ -11,6 +11,8 @@ export var StyleElement = window.HTMLStyleElement;
export var document = window.document;
export var location = window.location;
export var gc = window.gc ? () => window.gc() : () => null;
+export var CssRule = window.CSSRule;
+export var CssKeyframesRule = window.CSSKeyframesRule;
export class DOM {
static query(selector) {
@@ -129,6 +131,9 @@ export class DOM {
static createElement(tagName, doc=document) {
return doc.createElement(tagName);
}
+ static createTextNode(text: string, doc=document) {
+ return doc.createTextNode(text);
+ }
static createScriptTag(attrName:string, attrValue:string, doc=document) {
var el = doc.createElement("SCRIPT");
el.setAttribute(attrName, attrValue);
@@ -215,3 +220,18 @@ export class DOM {
return node.nodeType === Node.ELEMENT_NODE;
}
}
+
+export class CSSRuleWrapper {
+ static isPageRule(rule) {
+ return rule.type === CSSRule.PAGE_RULE;
+ }
+ static isStyleRule(rule) {
+ return rule.type === CSSRule.STYLE_RULE;
+ }
+ static isMediaRule(rule) {
+ return rule.type === CSSRule.MEDIA_RULE;
+ }
+ static isKeyframesRule(rule) {
+ return rule.type === CSSRule.KEYFRAMES_RULE;
+ }
+}
diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart
index 33ef216697..0250a99a3c 100644
--- a/modules/angular2/src/facade/lang.dart
+++ b/modules/angular2/src/facade/lang.dart
@@ -59,6 +59,10 @@ class StringWrapper {
return s == s2;
}
+ static String replace(String s, Pattern from, String replace) {
+ return s.replaceFirst(from, replace);
+ }
+
static String replaceAll(String s, RegExp from, String replace) {
return s.replaceAll(from, replace);
}
diff --git a/modules/angular2/src/facade/lang.es6 b/modules/angular2/src/facade/lang.es6
index 485933a97b..78198143bc 100644
--- a/modules/angular2/src/facade/lang.es6
+++ b/modules/angular2/src/facade/lang.es6
@@ -77,6 +77,14 @@ export class StringWrapper {
return s === s2;
}
+ static replace(s:string, from , replace:string): string {
+ if (typeof(from) === "string") {
+ return s.replace(from, replace);
+ } else {
+ return s.replace(from.single, replace);
+ }
+ }
+
static replaceAll(s:string, from:RegExp, replace:string):string {
return s.replace(from.multiple, replace);
}
@@ -91,6 +99,9 @@ export class StringWrapper {
static replaceAllMapped(s:string, from:RegExp, cb:Function): string {
return s.replace(from.multiple, function(...matches) {
+ // Remove offset & string from the result array
+ matches.splice(-2, 2);
+ // The callback receives match, p1, ..., pn
return cb(matches);
});
}
@@ -171,7 +182,7 @@ export class NumberWrapper {
}
}
-var RegExp;
+export var RegExp;
if (assertionsEnabled_) {
RegExp = assert.define('RegExp', function(obj) {
assert(obj).is(assert.structure({
diff --git a/modules/angular2/test/core/compiler/shadow_dom/shadow_css_spec.js b/modules/angular2/test/core/compiler/shadow_dom/shadow_css_spec.js
new file mode 100644
index 0000000000..0cf6088dce
--- /dev/null
+++ b/modules/angular2/test/core/compiler/shadow_dom/shadow_css_spec.js
@@ -0,0 +1,112 @@
+import {describe, beforeEach, it, expect, ddescribe, iit, SpyObject, el} from 'angular2/test_lib';
+import {ShadowCss} from 'angular2/src/core/compiler/shadow_dom_emulation/shadow_css';
+
+import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
+
+export function main() {
+ describe('ShadowCss', function() {
+
+ function s(css: string, tag:string) {
+ var shadowCss = new ShadowCss();
+ var shim = shadowCss.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('[is="one"][a] {}');
+ });
+
+ it('should handle :host', () => {
+ expect(s(':host {}', 'a')).toEqual('a {}');
+ expect(s(':host(.x,.y) {}', 'a')).toEqual('a.x, a.y {}');
+ expect(s(':host(.x,.y) > .z {}', 'a')).toEqual('a.x > .z, a.y > .z {}');
+ });
+
+ it('should handle :host-context', () => {
+ expect(s(':host-context(.x) {}', 'a')).toEqual('a.x, .x a {}');
+ expect(s(':host-context(.x) > .y {}', 'a')).toEqual('a.x > .y, .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-rule', () => {
+ var css = s("polyfill-unscoped-rule {content: '#menu > .bar';background: blue;}", 'a');
+ expect(StringWrapper.contains(css, '#menu > .bar {;background: blue;}')).toBeTruthy();
+
+ css = s('polyfill-unscoped-rule {content: "#menu > .bar";background: blue;}', 'a');
+ expect(StringWrapper.contains(css, '#menu > .bar {;background: blue;}')).toBeTruthy();
+ });
+
+ it('should support polyfill-rule', () => {
+ var css = s("polyfill-rule {content: ':host.foo .bar';background: blue;}", 'a');
+ expect(css).toEqual('a.foo .bar {background: blue;}');
+
+ css = s('polyfill-rule {content: ":host.foo .bar";background: blue;}', 'a');
+ expect(css).toEqual('a.foo .bar {background: blue;}');
+ });
+
+ it('should handle ::shadow', () => {
+ var css = s('x::shadow > y {}', 'a');
+ expect(css).toEqual('x[a] > y[a] {}');
+ });
+
+ it('should handle /deep/', () => {
+ var css = s('x /deep/ y {}', 'a');
+ expect(css).toEqual('x[a] y[a] {}');
+ });
+ });
+}