Skip to content

Commit 69dce38

Browse files
JeanMechealxhub
authored andcommitted
Revert fix(compiler): transform pseudo selectors correctly for the encapsulated view. (#58417)
This commit reverts #57796 for v18 PR Close #58417
1 parent 1f13a5e commit 69dce38

File tree

3 files changed

+30
-353
lines changed

3 files changed

+30
-353
lines changed

packages/compiler/src/shadow_css.ts

+30-140
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ export class ShadowCss {
338338
* captures how many (if any) leading whitespaces are present or a comma
339339
* - (?:(?:(['"])((?:\\\\|\\\2|(?!\2).)+)\2)|(-?[A-Za-z][\w\-]*))
340340
* captures two different possible keyframes, ones which are quoted or ones which are valid css
341-
* indents (custom properties excluded)
341+
* idents (custom properties excluded)
342342
* - (?=[,\s;]|$)
343343
* simply matches the end of the possible keyframe, valid endings are: a comma, a space, a
344344
* semicolon or the end of the string
@@ -459,7 +459,7 @@ export class ShadowCss {
459459
*/
460460
private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string {
461461
const unscopedRules = this._extractUnscopedRulesFromCssText(cssText);
462-
// replace :host and :host-context with -shadowcsshost and -shadowcsshostcontext respectively
462+
// replace :host and :host-context -shadowcsshost and -shadowcsshost respectively
463463
cssText = this._insertPolyfillHostInCssText(cssText);
464464
cssText = this._convertColonHost(cssText);
465465
cssText = this._convertColonHostContext(cssText);
@@ -539,7 +539,7 @@ export class ShadowCss {
539539
* .foo<scopeName> .bar { ... }
540540
*/
541541
private _convertColonHostContext(cssText: string): string {
542-
return cssText.replace(_cssColonHostContextReGlobal, (selectorText, pseudoPrefix) => {
542+
return cssText.replace(_cssColonHostContextReGlobal, (selectorText) => {
543543
// We have captured a selector that contains a `:host-context` rule.
544544

545545
// For backward compatibility `:host-context` may contain a comma separated list of selectors.
@@ -594,12 +594,10 @@ export class ShadowCss {
594594
}
595595

596596
// The context selectors now must be combined with each other to capture all the possible
597-
// selectors that `:host-context` can match. See `_combineHostContextSelectors()` for more
597+
// selectors that `:host-context` can match. See `combineHostContextSelectors()` for more
598598
// info about how this is done.
599599
return contextSelectorGroups
600-
.map((contextSelectors) =>
601-
_combineHostContextSelectors(contextSelectors, selectorText, pseudoPrefix),
602-
)
600+
.map((contextSelectors) => combineHostContextSelectors(contextSelectors, selectorText))
603601
.join(', ');
604602
});
605603
}
@@ -618,12 +616,7 @@ export class ShadowCss {
618616
let selector = rule.selector;
619617
let content = rule.content;
620618
if (rule.selector[0] !== '@') {
621-
selector = this._scopeSelector({
622-
selector,
623-
scopeSelector,
624-
hostSelector,
625-
isParentSelector: true,
626-
});
619+
selector = this._scopeSelector(rule.selector, scopeSelector, hostSelector);
627620
} else if (scopedAtRuleIdentifiers.some((atRule) => rule.selector.startsWith(atRule))) {
628621
content = this._scopeSelectors(rule.content, scopeSelector, hostSelector);
629622
} else if (rule.selector.startsWith('@font-face') || rule.selector.startsWith('@page')) {
@@ -663,44 +656,15 @@ export class ShadowCss {
663656
});
664657
}
665658

666-
private _safeSelector: SafeSelector | undefined;
667-
private _shouldScopeIndicator: boolean | undefined;
668-
669-
// `isParentSelector` is used to distinguish the selectors which are coming from
670-
// the initial selector string and any nested selectors, parsed recursively,
671-
// for example `selector = 'a:where(.one)'` could be the parent, while recursive call
672-
// would have `selector = '.one'`.
673-
private _scopeSelector({
674-
selector,
675-
scopeSelector,
676-
hostSelector,
677-
isParentSelector = false,
678-
}: {
679-
selector: string;
680-
scopeSelector: string;
681-
hostSelector: string;
682-
isParentSelector?: boolean;
683-
}): string {
684-
// Split the selector into independent parts by `,` (comma) unless
685-
// comma is within parenthesis, for example `:is(.one, two)`.
686-
// Negative lookup after comma allows not splitting inside nested parenthesis,
687-
// up to three levels (((,))).
688-
const selectorSplitRe =
689-
/ ?,(?!(?:[^)(]*(?:\([^)(]*(?:\([^)(]*(?:\([^)(]*\)[^)(]*)*\)[^)(]*)*\)[^)(]*)*\))) ?/;
690-
659+
private _scopeSelector(selector: string, scopeSelector: string, hostSelector: string): string {
691660
return selector
692-
.split(selectorSplitRe)
661+
.split(/ ?, ?/)
693662
.map((part) => part.split(_shadowDeepSelectors))
694663
.map((deepParts) => {
695664
const [shallowPart, ...otherParts] = deepParts;
696665
const applyScope = (shallowPart: string) => {
697666
if (this._selectorNeedsScoping(shallowPart, scopeSelector)) {
698-
return this._applySelectorScope({
699-
selector: shallowPart,
700-
scopeSelector,
701-
hostSelector,
702-
isParentSelector,
703-
});
667+
return this._applySelectorScope(shallowPart, scopeSelector, hostSelector);
704668
} else {
705669
return shallowPart;
706670
}
@@ -733,9 +697,9 @@ export class ShadowCss {
733697
if (_polyfillHostRe.test(selector)) {
734698
const replaceBy = `[${hostSelector}]`;
735699
return selector
736-
.replace(_polyfillHostNoCombinatorReGlobal, (_hnc, selector) => {
700+
.replace(_polyfillHostNoCombinatorRe, (hnc, selector) => {
737701
return selector.replace(
738-
/([^:\)]*)(:*)(.*)/,
702+
/([^:]*)(:*)(.*)/,
739703
(_: string, before: string, colon: string, after: string) => {
740704
return before + replaceBy + colon + after;
741705
},
@@ -749,17 +713,11 @@ export class ShadowCss {
749713

750714
// return a selector with [name] suffix on each simple selector
751715
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */
752-
private _applySelectorScope({
753-
selector,
754-
scopeSelector,
755-
hostSelector,
756-
isParentSelector,
757-
}: {
758-
selector: string;
759-
scopeSelector: string;
760-
hostSelector: string;
761-
isParentSelector?: boolean;
762-
}): string {
716+
private _applySelectorScope(
717+
selector: string,
718+
scopeSelector: string,
719+
hostSelector: string,
720+
): string {
763721
const isRe = /\[is=([^\]]*)\]/g;
764722
scopeSelector = scopeSelector.replace(isRe, (_: string, ...parts: string[]) => parts[0]);
765723

@@ -774,10 +732,6 @@ export class ShadowCss {
774732

775733
if (p.includes(_polyfillHostNoCombinator)) {
776734
scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector);
777-
if (_polyfillHostNoCombinatorWithinPseudoFunction.test(p)) {
778-
const [_, before, colon, after] = scopedP.match(/([^:]*)(:*)(.*)/)!;
779-
scopedP = before + attrName + colon + after;
780-
}
781735
} else {
782736
// remove :host since it should be unnecessary
783737
const t = p.replace(_polyfillHostRe, '');
@@ -792,60 +746,13 @@ export class ShadowCss {
792746
return scopedP;
793747
};
794748

795-
// Wraps `_scopeSelectorPart()` to not use it directly on selectors with
796-
// pseudo selector functions like `:where()`. Selectors within pseudo selector
797-
// functions are recursively sent to `_scopeSelector()`.
798-
const _pseudoFunctionAwareScopeSelectorPart = (selectorPart: string) => {
799-
let scopedPart = '';
800-
801-
const cssPrefixWithPseudoSelectorFunctionMatch = selectorPart.match(
802-
_cssPrefixWithPseudoSelectorFunction,
803-
);
804-
if (cssPrefixWithPseudoSelectorFunctionMatch) {
805-
const [cssPseudoSelectorFunction] = cssPrefixWithPseudoSelectorFunctionMatch;
806-
807-
// Unwrap the pseudo selector to scope its contents.
808-
// For example,
809-
// - `:where(selectorToScope)` -> `selectorToScope`;
810-
// - `:is(.foo, .bar)` -> `.foo, .bar`.
811-
const selectorToScope = selectorPart.slice(cssPseudoSelectorFunction.length, -1);
812-
813-
if (selectorToScope.includes(_polyfillHostNoCombinator)) {
814-
this._shouldScopeIndicator = true;
815-
}
816-
817-
const scopedInnerPart = this._scopeSelector({
818-
selector: selectorToScope,
819-
scopeSelector,
820-
hostSelector,
821-
});
822-
823-
// Put the result back into the pseudo selector function.
824-
scopedPart = `${cssPseudoSelectorFunction}${scopedInnerPart})`;
825-
} else {
826-
this._shouldScopeIndicator =
827-
this._shouldScopeIndicator || selectorPart.includes(_polyfillHostNoCombinator);
828-
scopedPart = this._shouldScopeIndicator ? _scopeSelectorPart(selectorPart) : selectorPart;
829-
}
830-
831-
return scopedPart;
832-
};
833-
834-
if (isParentSelector) {
835-
this._safeSelector = new SafeSelector(selector);
836-
selector = this._safeSelector.content();
837-
}
749+
const safeContent = new SafeSelector(selector);
750+
selector = safeContent.content();
838751

839752
let scopedSelector = '';
840753
let startIndex = 0;
841754
let res: RegExpExecArray | null;
842-
// Combinators aren't used as a delimiter if they are within parenthesis,
843-
// for example `:where(.one .two)` stays intact.
844-
// Similarly to selector separation by comma initially, negative lookahead
845-
// is used here to not break selectors within nested parenthesis up to three
846-
// nested layers.
847-
const sep =
848-
/( |>|\+|~(?!=))(?!([^)(]*(?:\([^)(]*(?:\([^)(]*(?:\([^)(]*\)[^)(]*)*\)[^)(]*)*\)[^)(]*)*\)))\s*/g;
755+
const sep = /( |>|\+|~(?!=))\s*/g;
849756

850757
// If a selector appears before :host it should not be shimmed as it
851758
// matches on ancestor elements and not on elements in the host's shadow
@@ -859,13 +766,8 @@ export class ShadowCss {
859766
// - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a
860767
// `:host-context(tag)`)
861768
const hasHost = selector.includes(_polyfillHostNoCombinator);
862-
// Only scope parts after or on the same level as the first `-shadowcsshost-no-combinator`
863-
// when it is present. The selector has the same level when it is a part of a pseudo
864-
// selector, like `:where()`, for example `:where(:host, .foo)` would result in `.foo`
865-
// being scoped.
866-
if (isParentSelector || this._shouldScopeIndicator) {
867-
this._shouldScopeIndicator = !hasHost;
868-
}
769+
// Only scope parts after the first `-shadowcsshost-no-combinator` when it is present
770+
let shouldScope = !hasHost;
869771

870772
while ((res = sep.exec(selector)) !== null) {
871773
const separator = res[1];
@@ -884,17 +786,18 @@ export class ShadowCss {
884786
continue;
885787
}
886788

887-
const scopedPart = _pseudoFunctionAwareScopeSelectorPart(part);
789+
shouldScope = shouldScope || part.includes(_polyfillHostNoCombinator);
790+
const scopedPart = shouldScope ? _scopeSelectorPart(part) : part;
888791
scopedSelector += `${scopedPart} ${separator} `;
889792
startIndex = sep.lastIndex;
890793
}
891794

892795
const part = selector.substring(startIndex);
893-
scopedSelector += _pseudoFunctionAwareScopeSelectorPart(part);
796+
shouldScope = shouldScope || part.includes(_polyfillHostNoCombinator);
797+
scopedSelector += shouldScope ? _scopeSelectorPart(part) : part;
894798

895799
// replace the placeholders with their original values
896-
// using values stored inside the `safeSelector` instance.
897-
return this._safeSelector!.restore(scopedSelector);
800+
return safeContent.restore(scopedSelector);
898801
}
899802

900803
private _insertPolyfillHostInCssText(selector: string): string {
@@ -959,8 +862,6 @@ class SafeSelector {
959862
}
960863
}
961864

962-
const _cssScopedPseudoFunctionPrefix = '(:(where|is)\\()?';
963-
const _cssPrefixWithPseudoSelectorFunction = /^:(where|is)\(/i;
964865
const _cssContentNextSelectorRe =
965866
/polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim;
966867
const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
@@ -971,17 +872,10 @@ const _polyfillHost = '-shadowcsshost';
971872
const _polyfillHostContext = '-shadowcsscontext';
972873
const _parenSuffix = '(?:\\((' + '(?:\\([^)(]*\\)|[^)(]*)+?' + ')\\))?([^,{]*)';
973874
const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix, 'gim');
974-
const _cssColonHostContextReGlobal = new RegExp(
975-
_cssScopedPseudoFunctionPrefix + '(' + _polyfillHostContext + _parenSuffix + ')',
976-
'gim',
977-
);
875+
const _cssColonHostContextReGlobal = new RegExp(_polyfillHostContext + _parenSuffix, 'gim');
978876
const _cssColonHostContextRe = new RegExp(_polyfillHostContext + _parenSuffix, 'im');
979877
const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
980-
const _polyfillHostNoCombinatorWithinPseudoFunction = new RegExp(
981-
`:.*\\(.*${_polyfillHostNoCombinator}.*\\)`,
982-
);
983878
const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;
984-
const _polyfillHostNoCombinatorReGlobal = new RegExp(_polyfillHostNoCombinatorRe, 'g');
985879
const _shadowDOMSelectorsRe = [
986880
/::shadow/g,
987881
/::content/g,
@@ -1232,11 +1126,7 @@ function unescapeQuotes(str: string, isQuoted: boolean): string {
12321126
* @param contextSelectors an array of context selectors that will be combined.
12331127
* @param otherSelectors the rest of the selectors that are not context selectors.
12341128
*/
1235-
function _combineHostContextSelectors(
1236-
contextSelectors: string[],
1237-
otherSelectors: string,
1238-
pseudoPrefix = '',
1239-
): string {
1129+
function combineHostContextSelectors(contextSelectors: string[], otherSelectors: string): string {
12401130
const hostMarker = _polyfillHostNoCombinator;
12411131
_polyfillHostRe.lastIndex = 0; // reset the regex to ensure we get an accurate test
12421132
const otherSelectorsHasHost = _polyfillHostRe.test(otherSelectors);
@@ -1265,8 +1155,8 @@ function _combineHostContextSelectors(
12651155
return combined
12661156
.map((s) =>
12671157
otherSelectorsHasHost
1268-
? `${pseudoPrefix}${s}${otherSelectors}`
1269-
: `${pseudoPrefix}${s}${hostMarker}${otherSelectors}, ${pseudoPrefix}${s} ${hostMarker}${otherSelectors}`,
1158+
? `${s}${otherSelectors}`
1159+
: `${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`,
12701160
)
12711161
.join(',');
12721162
}

packages/compiler/test/shadow_css/host_and_host_context_spec.ts

-36
Original file line numberDiff line numberDiff line change
@@ -107,42 +107,6 @@ describe('ShadowCss, :host and :host-context', () => {
107107
});
108108

109109
describe(':host-context', () => {
110-
it('should transform :host-context with pseudo selectors', () => {
111-
expect(
112-
shim(':host-context(backdrop:not(.borderless)) .backdrop {}', 'contenta', 'hosta'),
113-
).toEqualCss(
114-
'backdrop:not(.borderless)[hosta] .backdrop[contenta], backdrop:not(.borderless) [hosta] .backdrop[contenta] {}',
115-
);
116-
expect(shim(':where(:host-context(backdrop)) {}', 'contenta', 'hosta')).toEqualCss(
117-
':where(backdrop[hosta]), :where(backdrop [hosta]) {}',
118-
);
119-
expect(shim(':where(:host-context(outer1)) :host(bar) {}', 'contenta', 'hosta')).toEqualCss(
120-
':where(outer1) bar[hosta] {}',
121-
);
122-
expect(
123-
shim(':where(:host-context(.one)) :where(:host-context(.two)) {}', 'contenta', 'a-host'),
124-
).toEqualCss(
125-
':where(.one.two[a-host]), ' + // `one` and `two` both on the host
126-
':where(.one.two [a-host]), ' + // `one` and `two` are both on the same ancestor
127-
':where(.one .two[a-host]), ' + // `one` is an ancestor and `two` is on the host
128-
':where(.one .two [a-host]), ' + // `one` and `two` are both ancestors (in that order)
129-
':where(.two .one[a-host]), ' + // `two` is an ancestor and `one` is on the host
130-
':where(.two .one [a-host])' + // `two` and `one` are both ancestors (in that order)
131-
' {}',
132-
);
133-
expect(
134-
shim(':where(:host-context(backdrop)) .foo ~ .bar {}', 'contenta', 'hosta'),
135-
).toEqualCss(
136-
':where(backdrop[hosta]) .foo[contenta] ~ .bar[contenta], :where(backdrop [hosta]) .foo[contenta] ~ .bar[contenta] {}',
137-
);
138-
expect(shim(':where(:host-context(backdrop)) :host {}', 'contenta', 'hosta')).toEqualCss(
139-
':where(backdrop) [hosta] {}',
140-
);
141-
expect(shim('div:where(:host-context(backdrop)) :host {}', 'contenta', 'hosta')).toEqualCss(
142-
'div:where(backdrop) [hosta] {}',
143-
);
144-
});
145-
146110
it('should handle tag selector', () => {
147111
expect(shim(':host-context(div) {}', 'contenta', 'a-host')).toEqualCss(
148112
'div[a-host], div [a-host] {}',

0 commit comments

Comments
 (0)