fix(core): fix pseudo-selector shimming (#12754)

fixes #12730
fixes #12354
This commit is contained in:
Victor Berchet 2016-11-07 13:56:04 -08:00 committed by vikerman
parent f3793b5953
commit acbf1d859c
2 changed files with 52 additions and 17 deletions

View File

@ -378,13 +378,18 @@ export class ShadowCss {
string { string {
// In Android browser, the lastIndex is not reset when the regex is used in String.replace() // In Android browser, the lastIndex is not reset when the regex is used in String.replace()
_polyfillHostRe.lastIndex = 0; _polyfillHostRe.lastIndex = 0;
if (_polyfillHostRe.test(selector)) { if (_polyfillHostRe.test(selector)) {
const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector; const replaceBy = this.strictStyling ? `[${hostSelector}]` : scopeSelector;
return selector return selector
.replace( .replace(
_polyfillHostNoCombinatorRe, _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 + ' '); .replace(_polyfillHostRe, replaceBy + ' ');
} }
@ -411,10 +416,10 @@ export class ShadowCss {
scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector); scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector);
} else { } else {
// remove :host since it should be unnecessary // remove :host since it should be unnecessary
var t = p.replace(_polyfillHostRe, ''); const t = p.replace(_polyfillHostRe, '');
if (t.length > 0) { if (t.length > 0) {
const matches = t.match(/([^:]*)(:*)(.*)/); const matches = t.match(/([^:]*)(:*)(.*)/);
if (matches !== null) { if (matches) {
scopedP = matches[1] + attrName + matches[2] + matches[3]; scopedP = matches[1] + attrName + matches[2] + matches[3];
} }
} }
@ -423,17 +428,8 @@ export class ShadowCss {
return scopedP; return scopedP;
}; };
let attrSelectorIndex = 0; const safeContent = new SafeSelector(selector);
const attrSelectors: string[] = []; selector = safeContent.content();
// 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;
});
let scopedSelector = ''; let scopedSelector = '';
let startIndex = 0; let startIndex = 0;
@ -454,7 +450,7 @@ export class ShadowCss {
scopedSelector += _scopeSelectorPart(selector.substring(startIndex)); scopedSelector += _scopeSelectorPart(selector.substring(startIndex));
// replace the placeholders with their original values // 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 { private _insertPolyfillHostInCssText(selector: string): string {
@ -462,6 +458,39 @@ export class ShadowCss {
.replace(_colonHostRe, _polyfillHost); .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 = const _cssContentNextSelectorRe =
/polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim; /polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim;
const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;

View File

@ -140,9 +140,15 @@ export function main() {
.toEqual('[a="b"][a-host], [c="d"][a-host] {}'); .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: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 {}');
}); });
}); });