Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 93 additions & 98 deletions modules/angular2/src/core/compiler/shadow_css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,6 @@ export class ShadowCss {

constructor() {}

/*
* Shim a style element with the given selector. Returns cssText that can
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
*/
shimStyle(cssText: string, selector: string, hostSelector: string = ''): string {
return this.shimCssText(cssText, selector, hostSelector);
}

/*
* Shim some cssText with the given selector. Returns cssText that can
* be included in the document via WebComponents.ShadowCSS.addCssToDocument(css).
Expand All @@ -156,12 +148,12 @@ export class ShadowCss {
* - hostSelector is the attribute added to the host itself.
*/
shimCssText(cssText: string, selector: string, hostSelector: string = ''): string {
cssText = stripComments(cssText);
cssText = this._insertDirectives(cssText);
return this._scopeCssText(cssText, selector, hostSelector);
}

/** @internal */
_insertDirectives(cssText: string): string {
private _insertDirectives(cssText: string): string {
cssText = this._insertPolyfillDirectivesInCssText(cssText);
return this._insertPolyfillRulesInCssText(cssText);
}
Expand All @@ -180,8 +172,7 @@ export class ShadowCss {
* scopeName menu-item {
*
**/
/** @internal */
_insertPolyfillDirectivesInCssText(cssText: string): string {
private _insertPolyfillDirectivesInCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments
return StringWrapper.replaceAllMapped(cssText, _cssContentNextSelectorRe,
function(m) { return m[1] + '{'; });
Expand All @@ -202,8 +193,7 @@ export class ShadowCss {
* scopeName menu-item {...}
*
**/
/** @internal */
_insertPolyfillRulesInCssText(cssText: string): string {
private _insertPolyfillRulesInCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments
return StringWrapper.replaceAllMapped(cssText, _cssContentRuleRe, function(m) {
var rule = m[0];
Expand All @@ -221,8 +211,7 @@ export class ShadowCss {
*
* scopeName .foo { ... }
*/
/** @internal */
_scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
var unscoped = this._extractUnscopedRulesFromCssText(cssText);
cssText = this._insertPolyfillHostInCssText(cssText);
cssText = this._convertColonHost(cssText);
Expand Down Expand Up @@ -250,8 +239,7 @@ export class ShadowCss {
* menu-item {...}
*
**/
/** @internal */
_extractUnscopedRulesFromCssText(cssText: string): string {
private _extractUnscopedRulesFromCssText(cssText: string): string {
// Difference with webcomponents.js: does not handle comments
var r = '', m;
var matcher = RegExpWrapper.matcher(_cssContentUnscopedRuleRe, cssText);
Expand All @@ -271,8 +259,7 @@ export class ShadowCss {
*
* scopeName.foo > .bar
*/
/** @internal */
_convertColonHost(cssText: string): string {
private _convertColonHost(cssText: string): string {
return this._convertColonRule(cssText, _cssColonHostRe, this._colonHostPartReplacer);
}

Expand All @@ -291,14 +278,12 @@ export class ShadowCss {
*
* scopeName.foo .bar { ... }
*/
/** @internal */
_convertColonHostContext(cssText: string): string {
private _convertColonHostContext(cssText: string): string {
return this._convertColonRule(cssText, _cssColonHostContextRe,
this._colonHostContextPartReplacer);
}

/** @internal */
_convertColonRule(cssText: string, regExp: RegExp, partReplacer: Function): string {
private _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])) {
Expand All @@ -316,70 +301,46 @@ export class ShadowCss {
});
}

/** @internal */
_colonHostContextPartReplacer(host: string, part: string, suffix: string): string {
private _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;
}
}

/** @internal */
_colonHostPartReplacer(host: string, part: string, suffix: string): string {
private _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.
*/
/** @internal */
_convertShadowDOMSelectors(cssText: string): string {
private _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'
/** @internal */
_scopeSelectors(cssText: string, scopeSelector: string, hostSelector: string): string {
var parts = splitCurlyBlocks(cssText);
var result = [];
for (var i = 0; i < parts.length; i += 2) {
var selectorTextWithCommands = parts[i];
var selectorStart = selectorTextWithCommands.lastIndexOf(';') + 1;
var selectorText =
selectorTextWithCommands.substring(selectorStart, selectorTextWithCommands.length);
var ruleContent = parts[i + 1];
var selectorMatch = RegExpWrapper.firstMatch(_singleSelectorRe, selectorText);
if (isPresent(selectorMatch) && ruleContent.length > 0) {
var selPrefix = selectorMatch[1];
var selAt = isPresent(selectorMatch[2]) ? selectorMatch[2] : '';
var selector = selectorMatch[3];
var selSuffix = selectorMatch[4];
if (selAt.length === 0 || selAt == '@page') {
var scopedSelector =
this._scopeSelector(selector, scopeSelector, hostSelector, this.strictStyling);
selectorText = `${selPrefix}${selAt}${scopedSelector}${selSuffix}`;
} else if (selAt == '@media' && ruleContent[0] == OPEN_CURLY &&
ruleContent[ruleContent.length - 1] == CLOSE_CURLY) {
var scopedContent = this._scopeSelectors(ruleContent.substring(1, ruleContent.length - 1),
scopeSelector, hostSelector);
ruleContent = `${OPEN_CURLY}${scopedContent}${CLOSE_CURLY}`;
}
private _scopeSelectors(cssText: string, scopeSelector: string, hostSelector: string): string {
return processRules(cssText, (rule: CssRule) => {
var selector = rule.selector;
var content = rule.content;
if (rule.selector[0] != '@' || rule.selector.startsWith('@page')) {
selector =
this._scopeSelector(rule.selector, scopeSelector, hostSelector, this.strictStyling);
} else if (rule.selector.startsWith('@media')) {
content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
}
result.push(selectorTextWithCommands.substring(0, selectorStart));
result.push(selectorText);
result.push(ruleContent);
}
return result.join('');
return new CssRule(selector, content);
});
}

/** @internal */
_scopeSelector(selector: string, scopeSelector: string, hostSelector: string,
strict: boolean): string {
private _scopeSelector(selector: string, scopeSelector: string, hostSelector: string,
strict: boolean): string {
var r = [], parts = selector.split(',');
for (var i = 0; i < parts.length; i++) {
var p = parts[i];
Expand All @@ -394,30 +355,28 @@ export class ShadowCss {
return r.join(', ');
}

/** @internal */
_selectorNeedsScoping(selector: string, scopeSelector: string): boolean {
private _selectorNeedsScoping(selector: string, scopeSelector: string): boolean {
var re = this._makeScopeMatcher(scopeSelector);
return !isPresent(RegExpWrapper.firstMatch(re, selector));
}

/** @internal */
_makeScopeMatcher(scopeSelector: string): RegExp {
private _makeScopeMatcher(scopeSelector: string): RegExp {
var lre = /\[/g;
var rre = /\]/g;
scopeSelector = StringWrapper.replaceAll(scopeSelector, lre, '\\[');
scopeSelector = StringWrapper.replaceAll(scopeSelector, rre, '\\]');
return RegExpWrapper.create('^(' + scopeSelector + ')' + _selectorReSuffix, 'm');
}

/** @internal */
_applySelectorScope(selector: string, scopeSelector: string, hostSelector: string): string {
private _applySelectorScope(selector: string, scopeSelector: string,
hostSelector: string): string {
// Difference from webcomponentsjs: scopeSelector could not be an array
return this._applySimpleSelectorScope(selector, scopeSelector, hostSelector);
}

// scope via name and [is=name]
/** @internal */
_applySimpleSelectorScope(selector: string, scopeSelector: string, hostSelector: string): string {
private _applySimpleSelectorScope(selector: string, scopeSelector: string,
hostSelector: string): string {
if (isPresent(RegExpWrapper.firstMatch(_polyfillHostRe, selector))) {
var replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
selector = StringWrapper.replace(selector, _polyfillHostNoCombinator, replaceBy);
Expand All @@ -428,9 +387,8 @@ export class ShadowCss {
}

// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
/** @internal */
_applyStrictSelectorScope(selector: string, scopeSelector: string): string {
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
private _applyStrictSelectorScope(selector: string, scopeSelector: string): string {
var isRe = /\[is=([^\]]*)\]/g;
scopeSelector = StringWrapper.replaceAllMapped(scopeSelector, isRe, (m) => m[1]);
var splits = [' ', '>', '+', '~'], scoped = selector, attrName = '[' + scopeSelector + ']';
Expand All @@ -455,8 +413,7 @@ export class ShadowCss {
return scoped;
}

/** @internal */
_insertPolyfillHostInCssText(selector: string): string {
private _insertPolyfillHostInCssText(selector: string): string {
selector = StringWrapper.replaceAll(selector, _colonHostContextRe, _polyfillHostContext);
selector = StringWrapper.replaceAll(selector, _colonHostRe, _polyfillHost);
return selector;
Expand Down Expand Up @@ -493,34 +450,72 @@ var _polyfillHostRe = RegExpWrapper.create(_polyfillHost, 'im');
var _colonHostRe = /:host/gim;
var _colonHostContextRe = /:host-context/gim;

var _singleSelectorRe = /^(\s*)(@\S+)?(.*?)(\s*)$/g;
var _commentRe = /\/\*[\s\S]*?\*\//g;

function stripComments(input:string):string {
return StringWrapper.replaceAllMapped(input, _commentRe, (_) => '');
}

var _ruleRe = /(\s*)([^;\{\}]+?)(\s*)((?:{%BLOCK%}?\s*;?)|(?:\s*;))/g;
var _curlyRe = /([{}])/g;
var OPEN_CURLY = '{';
var CLOSE_CURLY = '}';
const OPEN_CURLY = '{';
const CLOSE_CURLY = '}';
const BLOCK_PLACEHOLDER = '%BLOCK%';

export function splitCurlyBlocks(cssText:string):string[] {
var parts = StringWrapper.split(cssText, _curlyRe);
var result = [];
export class CssRule {
constructor(public selector:string, public content:string) {}
}

export function processRules(input:string, ruleCallback:Function):string {
var inputWithEscapedBlocks = escapeBlocks(input);
var nextBlockIndex = 0;
return StringWrapper.replaceAllMapped(inputWithEscapedBlocks.escapedString, _ruleRe, function(m) {
var selector = m[2];
var content = '';
var suffix = m[4];
var contentPrefix = '';
if (isPresent(m[4]) && m[4].startsWith('{'+BLOCK_PLACEHOLDER)) {
content = inputWithEscapedBlocks.blocks[nextBlockIndex++];
suffix = m[4].substring(BLOCK_PLACEHOLDER.length+1);
contentPrefix = '{';
}
var rule = ruleCallback(new CssRule(selector, content));
return `${m[1]}${rule.selector}${m[3]}${contentPrefix}${rule.content}${suffix}`;
});
}

class StringWithEscapedBlocks {
constructor(public escapedString:string, public blocks:string[]) {}
}

function escapeBlocks(input:string):StringWithEscapedBlocks {
var inputParts = StringWrapper.split(input, _curlyRe);
var resultParts = [];
var escapedBlocks = [];
var bracketCount = 0;
var currentCurlyParts = [];
for (var partIndex = 0; partIndex<parts.length; partIndex++) {
var part = parts[partIndex];
currentCurlyParts.push(part);
if (part == OPEN_CURLY) {
bracketCount++;
} else if (part == CLOSE_CURLY) {
var currentBlockParts = [];
for (var partIndex = 0; partIndex<inputParts.length; partIndex++) {
var part = inputParts[partIndex];
if (part == CLOSE_CURLY) {
bracketCount--;
}
if (bracketCount === 0) {
result.push(currentCurlyParts.join(''));
currentCurlyParts = [];
if (bracketCount > 0) {
currentBlockParts.push(part);
} else {
if (currentBlockParts.length > 0) {
escapedBlocks.push(currentBlockParts.join(''));
resultParts.push(BLOCK_PLACEHOLDER);
currentBlockParts = [];
}
resultParts.push(part);
}
if (part == OPEN_CURLY) {
bracketCount++;
}
}
result.push(currentCurlyParts.join(''));
if (result.length >= 2 && result[result.length-1] == '' && result[result.length-2] == '') {
result = result.slice(0, result.length-2);
if (currentBlockParts.length > 0) {
escapedBlocks.push(currentBlockParts.join(''));
resultParts.push(BLOCK_PLACEHOLDER);
}
return result;
return new StringWithEscapedBlocks(resultParts.join(''), escapedBlocks);
}

Loading