diff --git a/devtools/server/actors/page-style.js b/devtools/server/actors/page-style.js index 4122d5c866e2..1f518874879f 100644 --- a/devtools/server/actors/page-style.js +++ b/devtools/server/actors/page-style.js @@ -844,7 +844,7 @@ class PageStyleActor extends Actor { } const domRule = entry.rule.rawRule; - const desugaredSelectors = CssLogic.getSelectors(domRule, true); + const desugaredSelectors = entry.rule.getDesugaredSelectors(); const element = entry.inherited ? entry.inherited.rawNode : node.rawNode; diff --git a/devtools/server/actors/style-rule.js b/devtools/server/actors/style-rule.js index 47fd482681d5..4011c2f34ec4 100644 --- a/devtools/server/actors/style-rule.js +++ b/devtools/server/actors/style-rule.js @@ -269,6 +269,41 @@ class StyleRuleActor extends Actor { return sheet.associatedDocument; } + /** + * When a rule is nested in another non-at-rule (aka CSS Nesting), the client + * will need its desugared selector, i.e. the full selector, which includes ancestor + * selectors, that is computed by the platform when applying the rule. + * To compute it, the parent selector (&) is recursively replaced by the parent + * rule selector wrapped in `:is()`. + * For example, with the following nested rule: `body { & > main {} }`, + * the desugared selector will be `:is(body) > main`. + * See https://www.w3.org/TR/css-nesting-1/#nest-selector for more information. + * + * Returns an array of the desugared selectors. For example, if rule is: + * + * body { + * & > main, & section { + * } + * } + * + * this will return: + * + * [ + * `:is(body) > main`, + * `:is(body) section`, + * ] + * + * @returns Array + */ + getDesugaredSelectors() { + // Cache the desugared selectors as it can be expensive to compute + if (!this._desugaredSelectors) { + this._desugaredSelectors = CssLogic.getSelectors(this.rawRule, true); + } + + return this._desugaredSelectors; + } + toString() { return "[StyleRuleActor for " + this.rawRule + "]"; } @@ -309,15 +344,7 @@ class StyleRuleActor extends Actor { case CSSRule.STYLE_RULE: form.selectors = CssLogic.getSelectors(this.rawRule); if (computeDesugaredSelector) { - // When a rule is nested in another non-at-rule (aka CSS Nesting), the client - // will need its desugared selector, i.e. the full selector, which includes ancestor - // selectors, that is computed by the platform when applying the rule. - // To compute it, the parent selector (&) is recursively replaced by the parent - // rule selector wrapped in `:is()`. - // For example, with the following nested rule: `body { & > main {} }`, - // the desugared selector will be `:is(body) > main`. - // See https://www.w3.org/TR/css-nesting-1/#nest-selector for more information. - form.desugaredSelectors = CssLogic.getSelectors(this.rawRule, true); + form.desugaredSelectors = this.getDesugaredSelectors(); } form.cssText = this.rawStyle.cssText || ""; break; @@ -1095,6 +1122,9 @@ class StyleRuleActor extends Actor { return { ruleProps: null, isMatching: true }; } + // Nullify cached desugared selectors as it might be outdated + this._desugaredSelectors = null; + // The rule's previous selector is lost after calling _addNewSelector(). Save it now. const oldValue = this.rawRule.selectorText; let selectorPromise = this._addNewSelector(value, editAuthored);