Skip to content

Commit

Permalink
fix(core): fix pseudo-selector shimming (#12754)
Browse files Browse the repository at this point in the history
fixes #12730
fixes #12354
  • Loading branch information
vicb authored and vikerman committed Nov 7, 2016
1 parent f3793b5 commit acbf1d8
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 17 deletions.
61 changes: 45 additions & 16 deletions modules/@angular/compiler/src/shadow_css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,18 @@ export class ShadowCss {
string {
// In Android browser, the lastIndex is not reset when the regex is used in String.replace()
_polyfillHostRe.lastIndex = 0;

if (_polyfillHostRe.test(selector)) {
const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
return selector
.replace(
_polyfillHostNoCombinatorRe,
(hnc, selector) => selector[0] === ':' ? replaceBy + selector : selector + replaceBy)
(hnc, selector) => {
return selector.replace(
/([^:]*)(:*)(.*)/,
(_: string, before: string, colon: string, after: string) => {
return before + replaceBy + colon + after;
});
})
.replace(_polyfillHostRe, replaceBy + ' ');
}

Expand All @@ -411,10 +416,10 @@ export class ShadowCss {
scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector);
} else {
// remove :host since it should be unnecessary
var t = p.replace(_polyfillHostRe, '');
const t = p.replace(_polyfillHostRe, '');
if (t.length > 0) {
const matches = t.match(/([^:]*)(:*)(.*)/);
if (matches !== null) {
if (matches) {
scopedP = matches[1] + attrName + matches[2] + matches[3];
}
}
Expand All @@ -423,17 +428,8 @@ export class ShadowCss {
return scopedP;
};

let attrSelectorIndex = 0;
const attrSelectors: string[] = [];

// replace attribute selectors with placeholders to avoid issue with white space being treated
// as separator
selector = selector.replace(/\[[^\]]*\]/g, (attrSelector) => {
const replaceBy = `__attr_sel_${attrSelectorIndex}__`;
attrSelectors.push(attrSelector);
attrSelectorIndex++;
return replaceBy;
});
const safeContent = new SafeSelector(selector);
selector = safeContent.content();

let scopedSelector = '';
let startIndex = 0;
Expand All @@ -454,14 +450,47 @@ export class ShadowCss {
scopedSelector += _scopeSelectorPart(selector.substring(startIndex));

// replace the placeholders with their original values
return scopedSelector.replace(/__attr_sel_(\d+)__/g, (ph, index) => attrSelectors[+index]);
return safeContent.restore(scopedSelector);
}

private _insertPolyfillHostInCssText(selector: string): string {
return selector.replace(_colonHostContextRe, _polyfillHostContext)
.replace(_colonHostRe, _polyfillHost);
}
}

class SafeSelector {
private placeholders: string[] = [];
private index = 0;
private _content: string;

constructor(selector: string) {
// Replaces attribute selectors with placeholders.
// The WS in [attr="va lue"] would otherwise be interpreted as a selector separator.
selector = selector.replace(/(\[[^\]]*\])/g, (_, keep) => {
const replaceBy = `__ph-${this.index}__`;
this.placeholders.push(keep);
this.index++;
return replaceBy;
});

// Replaces the expression in `:nth-child(2n + 1)` with a placeholder.
// WS and "+" would otherwise be interpreted as selector separators.
this._content = selector.replace(/(:nth-[-\w]+)(\([^)]+\))/g, (_, pseudo, exp) => {
const replaceBy = `__ph-${this.index}__`;
this.placeholders.push(exp);
this.index++;
return pseudo + replaceBy;
});
};

restore(content: string): string {
return content.replace(/__ph-(\d+)__/g, (ph, index) => this.placeholders[+index]);
}

content(): string { return this._content; }
}

const _cssContentNextSelectorRe =
/polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim;
const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
Expand Down
8 changes: 7 additions & 1 deletion modules/@angular/compiler/test/shadow_css_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,15 @@ export function main() {
.toEqual('[a="b"][a-host], [c="d"][a-host] {}');
});

it('should handle pseudo selector', () => {
it('should handle pseudo selectors', () => {
expect(s(':host(:before) {}', 'a', 'a-host')).toEqual('[a-host]:before {}');
expect(s(':host:before {}', 'a', 'a-host')).toEqual('[a-host]:before {}');
expect(s(':host:nth-child(8n+1) {}', 'a', 'a-host')).toEqual('[a-host]:nth-child(8n+1) {}');
expect(s(':host:nth-of-type(8n+1) {}', 'a', 'a-host'))
.toEqual('[a-host]:nth-of-type(8n+1) {}');
expect(s(':host(.class):before {}', 'a', 'a-host')).toEqual('.class[a-host]:before {}');
expect(s(':host.class:before {}', 'a', 'a-host')).toEqual('.class[a-host]:before {}');
expect(s(':host(:not(p)):before {}', 'a', 'a-host')).toEqual('[a-host]:not(p):before {}');
});
});

Expand Down

0 comments on commit acbf1d8

Please sign in to comment.