diff --git a/front_end/core/i18n/locales/en-US.json b/front_end/core/i18n/locales/en-US.json index c7021f4346..ed9b118048 100644 --- a/front_end/core/i18n/locales/en-US.json +++ b/front_end/core/i18n/locales/en-US.json @@ -4892,6 +4892,9 @@ "panels/elements/StylesSidebarPane.ts | inheritedFroms": { "message": "Inherited from " }, + "panels/elements/StylesSidebarPane.ts | inheritedFromSPseudoOf": { + "message": "Inherited from ::{PH1} pseudo of " + }, "panels/elements/StylesSidebarPane.ts | injectedStylesheet": { "message": "injected stylesheet" }, diff --git a/front_end/core/i18n/locales/en-XL.json b/front_end/core/i18n/locales/en-XL.json index 2018902d11..97b0a4cea2 100644 --- a/front_end/core/i18n/locales/en-XL.json +++ b/front_end/core/i18n/locales/en-XL.json @@ -4892,6 +4892,9 @@ "panels/elements/StylesSidebarPane.ts | inheritedFroms": { "message": "Îńĥér̂ít̂éd̂ f́r̂óm̂ " }, + "panels/elements/StylesSidebarPane.ts | inheritedFromSPseudoOf": { + "message": "Îńĥér̂ít̂éd̂ f́r̂óm̂ ::{PH1} ṕŝéûd́ô óf̂ " + }, "panels/elements/StylesSidebarPane.ts | injectedStylesheet": { "message": "îńĵéĉt́êd́ ŝt́ŷĺêśĥéêt́" }, diff --git a/front_end/core/sdk/CSSMatchedStyles.ts b/front_end/core/sdk/CSSMatchedStyles.ts index ea23c29e4d..bd215ea5dd 100644 --- a/front_end/core/sdk/CSSMatchedStyles.ts +++ b/front_end/core/sdk/CSSMatchedStyles.ts @@ -28,6 +28,7 @@ export class CSSMatchedStyles { cssModel: CSSModel, node: DOMNode, inlinePayload: Protocol.CSS.CSSStyle|null, attributesPayload: Protocol.CSS.CSSStyle|null, matchedPayload: Protocol.CSS.RuleMatch[], pseudoPayload: Protocol.CSS.PseudoElementMatches[], inheritedPayload: Protocol.CSS.InheritedStyleEntry[], + inheritedPseudoPayload: Protocol.CSS.InheritedPseudoElementMatches[], animationsPayload: Protocol.CSS.CSSKeyframesRule[]) { this.#cssModelInternal = cssModel; this.#nodeInternal = node; @@ -47,7 +48,7 @@ export class CSSMatchedStyles { } this.#mainDOMCascade = this.buildMainCascade(inlinePayload, attributesPayload, matchedPayload, inheritedPayload); - this.#pseudoDOMCascades = this.buildPseudoCascades(pseudoPayload); + this.#pseudoDOMCascades = this.buildPseudoCascades(pseudoPayload, inheritedPseudoPayload); this.#styleToDOMCascade = new Map(); for (const domCascade of Array.from(this.#pseudoDOMCascades.values()).concat(this.#mainDOMCascade)) { @@ -205,12 +206,16 @@ export class CSSMatchedStyles { } } - private buildPseudoCascades(pseudoPayload: Protocol.CSS.PseudoElementMatches[]): + private buildPseudoCascades( + pseudoPayload: Protocol.CSS.PseudoElementMatches[], + inheritedPseudoPayload: Protocol.CSS.InheritedPseudoElementMatches[]): Map { - const pseudoCascades = new Map(); + const pseudoInheritanceCascades = new Map(); if (!pseudoPayload) { - return pseudoCascades; + return pseudoInheritanceCascades; } + + const pseudoCascades = new Map(); for (let i = 0; i < pseudoPayload.length; ++i) { const entryPayload = pseudoPayload[i]; // PseudoElement nodes are not created unless "content" css property is set. @@ -226,9 +231,44 @@ export class CSSMatchedStyles { } } const nodeCascade = new NodeCascade(this, pseudoStyles, false /* #isInherited */); - pseudoCascades.set(entryPayload.pseudoType, new DOMInheritanceCascade([nodeCascade])); + pseudoCascades.set(entryPayload.pseudoType, [nodeCascade]); } - return pseudoCascades; + + if (inheritedPseudoPayload) { + let parentNode: (DOMNode|null) = this.#nodeInternal.parentNode; + for (let i = 0; parentNode && i < inheritedPseudoPayload.length; ++i) { + const inheritedPseudoMatches = inheritedPseudoPayload[i].pseudoElements; + for (let j = 0; j < inheritedPseudoMatches.length; ++j) { + const inheritedEntryPayload = inheritedPseudoMatches[j]; + const pseudoStyles = []; + const rules = inheritedEntryPayload.matches || []; + for (let k = rules.length - 1; k >= 0; --k) { + const pseudoRule = new CSSStyleRule(this.#cssModelInternal, rules[k].rule); + pseudoStyles.push(pseudoRule.style); + this.#nodeForStyleInternal.set(pseudoRule.style, parentNode); + this.#inheritedStyles.add(pseudoRule.style); + } + + const nodeCascade = new NodeCascade(this, pseudoStyles, true /* #isInherited */); + const cascadeListForPseudoType = pseudoCascades.get(inheritedEntryPayload.pseudoType); + if (cascadeListForPseudoType) { + cascadeListForPseudoType.push(nodeCascade); + } else { + pseudoCascades.set(inheritedEntryPayload.pseudoType, [nodeCascade]); + } + } + + parentNode = parentNode.parentNode; + } + } + + // Now that we've build the arrays of NodeCascades each pseudo type, convert them into + // DOMInheritanceCascades. + for (const [pseudoType, nodeCascade] of pseudoCascades.entries()) { + pseudoInheritanceCascades.set(pseudoType, new DOMInheritanceCascade(nodeCascade)); + } + + return pseudoInheritanceCascades; } private addMatchingSelectors( diff --git a/front_end/core/sdk/CSSModel.ts b/front_end/core/sdk/CSSModel.ts index 055d9eb554..9085ae8660 100644 --- a/front_end/core/sdk/CSSModel.ts +++ b/front_end/core/sdk/CSSModel.ts @@ -303,7 +303,7 @@ export class CSSModel extends SDKModel { return new CSSMatchedStyles( this, (node as DOMNode), response.inlineStyle || null, response.attributesStyle || null, response.matchedCSSRules || [], response.pseudoElements || [], response.inherited || [], - response.cssKeyframesRules || []); + response.inheritedPseudoElements || [], response.cssKeyframesRules || []); } async getClassNames(styleSheetId: Protocol.CSS.StyleSheetId): Promise { diff --git a/front_end/panels/elements/StylesSidebarPane.ts b/front_end/panels/elements/StylesSidebarPane.ts index f619694847..10bd79f000 100644 --- a/front_end/panels/elements/StylesSidebarPane.ts +++ b/front_end/panels/elements/StylesSidebarPane.ts @@ -98,6 +98,11 @@ const UIStrings = { */ inheritedFroms: 'Inherited from ', /** + *@description Text of an inherited psuedo element in Styles Sidebar Pane of the Elements panel + *@example {highlight} PH1 + */ + inheritedFromSPseudoOf: 'Inherited from ::{PH1} pseudo of ', + /** *@description Tooltip text that appears when hovering over the largeicon add button in the Styles Sidebar Pane of the Elements panel */ insertStyleRuleBelow: 'Insert Style Rule Below', @@ -921,10 +926,26 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin { @@ -1410,6 +1431,20 @@ export class SectionBlock { return new SectionBlock(separatorElement); } + static async createInheritedPseudoTypeBlock(pseudoType: Protocol.DOM.PseudoType, node: SDK.DOMModel.DOMNode): + Promise { + const separatorElement = document.createElement('div'); + separatorElement.className = 'sidebar-separator'; + + UI.UIUtils.createTextChild(separatorElement, i18nString(UIStrings.inheritedFromSPseudoOf, {PH1: pseudoType})); + const link = await Common.Linkifier.Linkifier.linkify(node, { + preventKeyboardFocus: true, + tooltip: undefined, + }); + separatorElement.appendChild(link); + return new SectionBlock(separatorElement); + } + static createKeyframesBlock(keyframesName: string): SectionBlock { const separatorElement = document.createElement('div'); separatorElement.className = 'sidebar-separator'; diff --git a/test/conductor/hooks.ts b/test/conductor/hooks.ts index 8a52c08b03..01965ace44 100644 --- a/test/conductor/hooks.ts +++ b/test/conductor/hooks.ts @@ -57,7 +57,7 @@ function launchChrome() { '--ignore-certificate-errors-spki-list=KLy6vv6synForXwI6lDIl+D3ZrMV6Y1EMTY6YpOcAos=', '--site-per-process', // Default on Desktop anyway, but ensure that we always use out-of-process frames when we intend to. '--host-resolver-rules=MAP *.test 127.0.0.1', '--disable-gpu', - '--enable-blink-features=CSSContainerQueries', // TODO(crbug.com/1218390) Remove globally enabled flag and conditionally enable it + '--enable-blink-features=CSSContainerQueries,HighlightInheritance', // TODO(crbug.com/1218390) Remove globally enabled flags and conditionally enable them ]; const opts: puppeteer.LaunchOptions&puppeteer.BrowserLaunchArgumentOptions&puppeteer.BrowserConnectOptions = { headless, diff --git a/test/e2e/elements/style-pane-properties_test.ts b/test/e2e/elements/style-pane-properties_test.ts index 2ee27de31f..b083dc3615 100644 --- a/test/e2e/elements/style-pane-properties_test.ts +++ b/test/e2e/elements/style-pane-properties_test.ts @@ -18,6 +18,7 @@ const FIRST_PROPERTY_VALUE_SELECTOR = '.tree-outline li:nth-of-type(1) > .value' const RULE1_SELECTOR = '.rule1'; const RULE2_SELECTOR = '.rule2'; const LAYER_SEPARATOR_SELECTOR = '.layer-separator'; +const SIDEBAR_SEPARATOR_SELECTOR = '.sidebar-separator'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const deletePropertyByBackspace = async (selector: string, root?: puppeteer.ElementHandle) => { @@ -403,4 +404,54 @@ describe('The Styles pane', async () => { const treeElement = await waitFor('[data-node-key="2: overrule"]'); assertNotNullOrUndefined(treeElement); }); + + it('can display inherited CSS highlight pseudo styles', async () => { + const {frontend} = getBrowserAndPages(); + await goToResourceAndWaitForStyleSection('elements/highlight-pseudo-inheritance.html'); + + const onH1RuleAppeared = waitForStyleRule('h1'); + + // Select the h1 for which we will inspect the pseudo styles + await frontend.keyboard.press('ArrowRight'); + + await onH1RuleAppeared; + + const h1Rules = await getDisplayedStyleRules(); + // The 6 rule blocks for the h1 are: + // 1. Inline styles from the style attribute + // 2. The h1's user agent styles + // 3. The h1's own highlight pseudo + // 4. The h1's inherited highlight pseudo + // 5. The h1's own selection pseudo + // 6. The h1's inherited selection pseudo + // And there is no 6th block for the ::first-letter style, since only + // highlight pseudos are inherited. + assert.strictEqual(h1Rules.length, 6, 'The h1 should have 6 style rule blocks'); + assert.deepEqual( + h1Rules[2], {selectorText: 'h1::highlight(foo)', propertyNames: ['background-color']}, + 'The h1\'s own highlight pseudo is displayed'); + assert.deepEqual( + h1Rules[3], {selectorText: 'body::highlight(bar)', propertyNames: ['color']}, + 'The h1\'s inherited highlight pseudo is displayed'); + assert.deepEqual( + h1Rules[4], {selectorText: 'h1::selection', propertyNames: ['background-color']}, + 'The h1\'s own selection pseudo is displayed'); + assert.deepEqual( + h1Rules[5], {selectorText: 'body::selection', propertyNames: ['text-shadow']}, + 'The h1\'s inherited selection pseudo is displayed'); + + const sidebarSeparators = await waitForFunction(async () => { + const separators = await $$(SIDEBAR_SEPARATOR_SELECTOR); + return separators.length === 4 ? separators : null; + }); + assertNotNullOrUndefined(sidebarSeparators); + + const layerText = await Promise.all(sidebarSeparators.map(element => element.evaluate(node => node.textContent))); + assert.deepEqual(layerText, [ + 'Pseudo ::highlight element', + 'Inherited from ::highlight pseudo of ', + 'Pseudo ::selection element', + 'Inherited from ::selection pseudo of ', + ]); + }); }); diff --git a/test/e2e/resources/elements/BUILD.gn b/test/e2e/resources/elements/BUILD.gn index 4f017a3433..c5e6d1e086 100644 --- a/test/e2e/resources/elements/BUILD.gn +++ b/test/e2e/resources/elements/BUILD.gn @@ -25,6 +25,7 @@ copy_to_gen("elements") { "flexbox-editor.html", "focus.html", "grid-editor.html", + "highlight-pseudo-inheritance.html", "hover.html", "limited-quirks-mode.html", "low-contrast.html", diff --git a/test/e2e/resources/elements/highlight-pseudo-inheritance.html b/test/e2e/resources/elements/highlight-pseudo-inheritance.html new file mode 100644 index 0000000000..9091cec69a --- /dev/null +++ b/test/e2e/resources/elements/highlight-pseudo-inheritance.html @@ -0,0 +1,31 @@ + + + + + +

target

+ \ No newline at end of file