From c32f5fd3934d188512787216ac4da7ed125ad7fe Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Tue, 7 Nov 2017 16:55:34 -0800 Subject: [PATCH] fix(compiler): fix corner cases in shadow CSS `cmp:host {}` and `cmp:host some-other-selector {}` were not handled consistently. Note those should not match anything but are made equivalent to respectively `:host(cmp)` and `:host(cmp) some-other-selector` to avoid breaking legacy apps. --- packages/compiler/src/shadow_css.ts | 25 ++++++++++++++++++----- packages/compiler/test/shadow_css_spec.ts | 12 +++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index 0c3065e557..166622d611 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -435,19 +435,34 @@ export class ShadowCss { let startIndex = 0; let res: RegExpExecArray|null; const sep = /( |>|\+|~(?!=))\s*/g; - const scopeAfter = selector.indexOf(_polyfillHostNoCombinator); + + // If a selector appears before :host it should not be shimmed as it + // matches on ancestor elements and not on elements in the host's shadow + // `:host-context(div)` is transformed to + // `-shadowcsshost-no-combinatordiv, div -shadowcsshost-no-combinator` + // the `div` is not part of the component in the 2nd selectors and should not be scoped. + // Historically `component-tag:host` was matching the component so we also want to preserve + // this behavior to avoid breaking legacy apps (it should not match). + // The behavior should be: + // - `tag:host` -> `tag[h]` (this is to avoid breaking legacy apps, should not match anything) + // - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a + // `:host-context(tag)`) + const hasHost = selector.indexOf(_polyfillHostNoCombinator) > -1; + // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present + let shouldScope = !hasHost; while ((res = sep.exec(selector)) !== null) { const separator = res[1]; const part = selector.slice(startIndex, res.index).trim(); - // if a selector appears before :host-context it should not be shimmed as it - // matches on ancestor elements and not on elements in the host's shadow - const scopedPart = startIndex >= scopeAfter ? _scopeSelectorPart(part) : part; + shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1; + const scopedPart = shouldScope ? _scopeSelectorPart(part) : part; scopedSelector += `${scopedPart} ${separator} `; startIndex = sep.lastIndex; } - scopedSelector += _scopeSelectorPart(selector.substring(startIndex)); + const part = selector.substring(startIndex); + shouldScope = shouldScope || part.indexOf(_polyfillHostNoCombinator) > -1; + scopedSelector += shouldScope ? _scopeSelectorPart(part) : part; // replace the placeholders with their original values return safeContent.restore(scopedSelector); diff --git a/packages/compiler/test/shadow_css_spec.ts b/packages/compiler/test/shadow_css_spec.ts index 372a0ef4e1..a4f67128b7 100644 --- a/packages/compiler/test/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css_spec.ts @@ -150,6 +150,18 @@ export function main() { 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 {}'); }); + + // see b/63672152 + it('should handle unexpected selectors in the most reasonable way', () => { + expect(s('cmp:host {}', 'a', 'a-host')).toEqual('cmp[a-host] {}'); + expect(s('cmp:host >>> {}', 'a', 'a-host')).toEqual('cmp[a-host] {}'); + expect(s('cmp:host child {}', 'a', 'a-host')).toEqual('cmp[a-host] child[a] {}'); + expect(s('cmp:host >>> child {}', 'a', 'a-host')).toEqual('cmp[a-host] child {}'); + expect(s('cmp :host {}', 'a', 'a-host')).toEqual('cmp [a-host] {}'); + expect(s('cmp :host >>> {}', 'a', 'a-host')).toEqual('cmp [a-host] {}'); + expect(s('cmp :host child {}', 'a', 'a-host')).toEqual('cmp [a-host] child[a] {}'); + expect(s('cmp :host >>> child {}', 'a', 'a-host')).toEqual('cmp [a-host] child {}'); + }); }); describe((':host-context'), () => {