Skip to content

Commit 2a11cda

Browse files
petebacondarwinthePunderWoman
authored andcommitted
fix(compiler): strip scoped selectors from @font-face rules (#41815)
`@font-face` rules cannot contain nested selectors. Nor can they be nested under a selector. Normally this would be a syntax error by the author of the styles. But in some rare cases, such as importing styles from a library, and applying `:host ::ng-deep` to the imported styles, we can end up with broken css if the imported styles happen to contain `@font-face` rules. This commit works around this problem by sanitizing such cases (erasing any scoping selectors) during emulated ShadowDOM encapsulation style processing. Fixes #41751 PR Close #41815
1 parent f9c1f08 commit 2a11cda

File tree

2 files changed

+41
-0
lines changed

2 files changed

+41
-0
lines changed

packages/compiler/src/shadow_css.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,40 @@ export class ShadowCss {
374374
rule.selector.startsWith('@media') || rule.selector.startsWith('@supports') ||
375375
rule.selector.startsWith('@page') || rule.selector.startsWith('@document')) {
376376
content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
377+
} else if (rule.selector.startsWith('@font-face')) {
378+
content = this._stripScopingSelectors(rule.content, scopeSelector, hostSelector);
377379
}
378380
return new CssRule(selector, content);
379381
});
380382
}
381383

384+
/**
385+
* Handle a css text that is within a rule that should not contain scope selectors by simply
386+
* removing them! An example of such a rule is `@font-face`.
387+
*
388+
* `@font-face` rules cannot contain nested selectors. Nor can they be nested under a selector.
389+
* Normally this would be a syntax error by the author of the styles. But in some rare cases, such
390+
* as importing styles from a library, and applying `:host ::ng-deep` to the imported styles, we
391+
* can end up with broken css if the imported styles happen to contain @font-face rules.
392+
*
393+
* For example:
394+
*
395+
* ```
396+
* :host ::ng-deep {
397+
* import 'some/lib/containing/font-face';
398+
* }
399+
* ```
400+
*/
401+
private _stripScopingSelectors(cssText: string, scopeSelector: string, hostSelector: string):
402+
string {
403+
return processRules(cssText, rule => {
404+
const selector = rule.selector.replace(_shadowDeepSelectors, ' ')
405+
.replace(_polyfillHostNoCombinatorRe, ' ');
406+
const content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
407+
return new CssRule(selector, content);
408+
});
409+
}
410+
382411
private _scopeSelector(
383412
selector: string, scopeSelector: string, hostSelector: string, strict: boolean): string {
384413
return selector.split(',')

packages/compiler/test/shadow_css_spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,18 @@ import {normalizeCSS} from '@angular/platform-browser/testing/src/browser_util';
369369
expect(s(css, 'contenta', 'h')).toEqual('[h] > > .x {}');
370370
});
371371

372+
it('should strip ::ng-deep and :host from within @font-face', () => {
373+
expect(s('@font-face { font-family {} }', 'contenta', 'h'))
374+
.toEqual('@font-face { font-family {}}');
375+
expect(s('@font-face { ::ng-deep font-family{} }', 'contenta', 'h'))
376+
.toEqual('@font-face { font-family{}}');
377+
expect(s('@font-face { :host ::ng-deep font-family{} }', 'contenta', 'h'))
378+
.toEqual('@font-face { font-family{}}');
379+
expect(s('@supports (display: flex) { @font-face { :host ::ng-deep font-family{} } }',
380+
'contenta', 'h'))
381+
.toEqual('@supports (display:flex) { @font-face { font-family{}}}');
382+
});
383+
372384
it('should pass through @import directives', () => {
373385
const styleStr = '@import url("https://fonts.googleapis.com/css?family=Roboto");';
374386
const css = s(styleStr, 'contenta');

0 commit comments

Comments
 (0)