Skip to content

Commit

Permalink
fix(compiler): incorrectly encapsulating selectors with escape sequen…
Browse files Browse the repository at this point in the history
…ces (#40264)

CSS supports escaping in selectors, e.g. writing `.foo:bar` will match an element with the
`foo` class and `bar` pseudo-class, but `.foo\:bar` will match the `foo:bar` class. Our
shimmed shadow DOM encapsulation always assumes that `:` means a pseudo selector
which breaks a selector like `.foo\:bar`.

These changes add some extra logic so that escaped characters in selectors are preserved.

Fixes #31844.

PR Close #40264
  • Loading branch information
crisbeto authored and josephperrott committed Jan 6, 2021
1 parent add7cbb commit 1bfbfaa
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 8 deletions.
29 changes: 22 additions & 7 deletions packages/compiler/src/shadow_css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,12 +485,14 @@ class SafeSelector {
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;
});
selector = this._escapeRegexMatches(selector, /(\[[^\]]*\])/g);

// CSS allows for certain special characters to be used in selectors if they're escaped.
// E.g. `.foo:blue` won't match a class called `foo:blue`, because the colon denotes a
// pseudo-class, but writing `.foo\:blue` will match, because the colon was escaped.
// Replace all escape sequences (`\` followed by a character) with a placeholder so
// that our handling of pseudo-selectors doesn't mess with them.
selector = this._escapeRegexMatches(selector, /(\\.)/g);

// Replaces the expression in `:nth-child(2n + 1)` with a placeholder.
// WS and "+" would otherwise be interpreted as selector separators.
Expand All @@ -503,12 +505,25 @@ class SafeSelector {
}

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

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

/**
* Replaces all of the substrings that match a regex within a
* special string (e.g. `__ph-0__`, `__ph-1__`, etc).
*/
private _escapeRegexMatches(content: string, pattern: RegExp): string {
return content.replace(pattern, (_, keep) => {
const replaceBy = `__ph-${this.index}__`;
this.placeholders.push(keep);
this.index++;
return replaceBy;
});
}
}

const _cssContentNextSelectorRe =
Expand Down
11 changes: 10 additions & 1 deletion packages/compiler/test/shadow_css_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {CssRule, processRules, ShadowCss} from '@angular/compiler/src/shadow_css
import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';

{
describe('ShadowCss', function() {
describe('ShadowCss', () => {
function s(css: string, contentAttr: string, hostAttr: string = '') {
const shadowCss = new ShadowCss();
const shim = shadowCss.shimCssText(css, contentAttr, hostAttr);
Expand Down Expand Up @@ -112,6 +112,15 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
expect(s('[is="one"] {}', 'contenta')).toEqual('[is="one"][contenta] {}');
});

it('should handle escaped sequences in selectors', () => {
expect(s('one\\/two {}', 'contenta')).toEqual('one\\/two[contenta] {}');
expect(s('one\\:two {}', 'contenta')).toEqual('one\\:two[contenta] {}');
expect(s('one\\\\:two {}', 'contenta')).toEqual('one\\\\[contenta]:two {}');
expect(s('.one\\:two {}', 'contenta')).toEqual('.one\\:two[contenta] {}');
expect(s('.one\\:two .three\\:four {}', 'contenta'))
.toEqual('.one\\:two[contenta] .three\\:four[contenta] {}');
});

describe((':host'), () => {
it('should handle no context', () => {
expect(s(':host {}', 'contenta', 'a-host')).toEqual('[a-host] {}');
Expand Down

0 comments on commit 1bfbfaa

Please sign in to comment.