From 7081fa3aa281a740d12f75a12ed6d8ab6cf7a42a Mon Sep 17 00:00:00 2001
From: sg <147118896+shangguan2024@users.noreply.github.com>
Date: Wed, 12 Aug 2026 19:24:33 +0800
Subject: [PATCH] feat: add CSS cascade layers support to Styles pane
Display matched CSS rules grouped by cascade layer and provide a CSS Layers tree view with navigation from layer separators.
Backport notes:
- Protocol definitions and generated bindings are based on ChromeDevTools/devtools-frontend@1ab363b4 (CSSLayer, CSSLayerData, CSSRule.layers and CSS.getLayersForNode).
- SDK, Styles pane grouping, LayersWidget, assets and e2e coverage are based on ChromeDevTools/devtools-frontend@52f5be82.
- Anonymous and implicit layer naming follows ChromeDevTools/devtools-frontend@e573a564.
- The feature is enabled by default, matching the launch change in ChromeDevTools/devtools-frontend@58e3685c.
This is a manual backport rather than a cherry-pick: adapt ToolbarToggle and TreeOutline calls to the older APIs in this fork, retain the existing @supports extensions and Lynx customizations, and update the checked-in protocol bindings without unrelated generator churn.
---
.../config/gni/devtools_grd_files.gni | 4 +
.../config/gni/devtools_image_files.gni | 1 +
.../front_end/Images/src/ic_layers_16x16.svg | 11 ++
.../front_end/core/i18n/locales/en-US.json | 12 ++
.../front_end/core/i18n/locales/en-XL.json | 12 ++
.../front_end/core/sdk/BUILD.gn | 1 +
.../front_end/core/sdk/CSSLayer.ts | 30 ++++
.../front_end/core/sdk/CSSModel.ts | 9 +
.../front_end/core/sdk/CSSQuery.ts | 3 +-
.../front_end/core/sdk/CSSRule.ts | 3 +
.../front_end/core/sdk/sdk.ts | 2 +
.../generated/InspectorBackendCommands.js | 2 +
.../front_end/generated/protocol-mapping.d.ts | 5 +
.../generated/protocol-proxy-api.d.ts | 6 +
.../front_end/generated/protocol.d.ts | 49 ++++++
.../front_end/panels/elements/BUILD.gn | 6 +
.../front_end/panels/elements/LayersWidget.ts | 163 ++++++++++++++++++
.../panels/elements/StylesSidebarPane.ts | 95 +++++++++-
.../panels/elements/elements-meta.ts | 17 +-
.../front_end/panels/elements/elements.ts | 2 +
.../panels/elements/layersWidget.css | 28 +++
.../panels/elements/stylesSidebarPane.css | 4 +
.../elements/style-pane-properties_test.ts | 38 +++-
.../test/e2e/resources/elements/BUILD.gn | 1 +
.../e2e/resources/elements/css-layers.html | 37 ++++
.../devtools_protocol/browser_protocol.json | 79 +++++++++
.../devtools_protocol/browser_protocol.pdl | 35 ++++
27 files changed, 645 insertions(+), 10 deletions(-)
create mode 100644 packages/devtools-frontend-lynx/front_end/Images/src/ic_layers_16x16.svg
create mode 100644 packages/devtools-frontend-lynx/front_end/core/sdk/CSSLayer.ts
create mode 100644 packages/devtools-frontend-lynx/front_end/panels/elements/LayersWidget.ts
create mode 100644 packages/devtools-frontend-lynx/front_end/panels/elements/layersWidget.css
create mode 100644 packages/devtools-frontend-lynx/test/e2e/resources/elements/css-layers.html
diff --git a/packages/devtools-frontend-lynx/config/gni/devtools_grd_files.gni b/packages/devtools-frontend-lynx/config/gni/devtools_grd_files.gni
index b962e7d4a..96c88429d 100644
--- a/packages/devtools-frontend-lynx/config/gni/devtools_grd_files.gni
+++ b/packages/devtools-frontend-lynx/config/gni/devtools_grd_files.gni
@@ -74,6 +74,7 @@ grd_files_release_sources = [
"front_end/Images/ic_folder_network.svg",
"front_end/Images/ic_help_16x16.svg",
"front_end/Images/ic_info_black_18dp.svg",
+ "front_end/Images/ic_layers_16x16.svg",
"front_end/Images/ic_memory_16x16.svg",
"front_end/Images/ic_page_next_16x16_icon.svg",
"front_end/Images/ic_page_prev_16x16_icon.svg",
@@ -563,6 +564,7 @@ grd_files_debug_sources = [
"front_end/core/sdk/CPUThrottlingManager.js",
"front_end/core/sdk/CSSContainerQuery.js",
"front_end/core/sdk/CSSFontFace.js",
+ "front_end/core/sdk/CSSLayer.js",
"front_end/core/sdk/CSSMatchedStyles.js",
"front_end/core/sdk/CSSMedia.js",
"front_end/core/sdk/CSSMetadata.js",
@@ -845,6 +847,7 @@ grd_files_debug_sources = [
"front_end/panels/elements/EventListenersWidget.js",
"front_end/panels/elements/ImagePreviewPopover.js",
"front_end/panels/elements/InspectElementModeController.js",
+ "front_end/panels/elements/LayersWidget.js",
"front_end/panels/elements/LayoutSidebarPane.js",
"front_end/panels/elements/MarkerDecorator.js",
"front_end/panels/elements/MetricsSidebarPane.js",
@@ -882,6 +885,7 @@ grd_files_debug_sources = [
"front_end/panels/elements/components/nodeText.css.js",
"front_end/panels/elements/components/queryContainer.css.js",
"front_end/panels/elements/components/stylePropertyEditor.css.js",
+ "front_end/panels/elements/layersWidget.css.js",
"front_end/panels/emulation/AdvancedApp.js",
"front_end/panels/emulation/DeviceModeModel.js",
"front_end/panels/emulation/DeviceModeToolbar.js",
diff --git a/packages/devtools-frontend-lynx/config/gni/devtools_image_files.gni b/packages/devtools-frontend-lynx/config/gni/devtools_image_files.gni
index 2046ae189..f5c81c804 100644
--- a/packages/devtools-frontend-lynx/config/gni/devtools_image_files.gni
+++ b/packages/devtools-frontend-lynx/config/gni/devtools_image_files.gni
@@ -86,6 +86,7 @@ devtools_svg_sources = [
"ic_folder_network.svg",
"ic_help_16x16.svg",
"ic_info_black_18dp.svg",
+ "ic_layers_16x16.svg",
"ic_memory_16x16.svg",
"ic_page_next_16x16_icon.svg",
"ic_page_prev_16x16_icon.svg",
diff --git a/packages/devtools-frontend-lynx/front_end/Images/src/ic_layers_16x16.svg b/packages/devtools-frontend-lynx/front_end/Images/src/ic_layers_16x16.svg
new file mode 100644
index 000000000..1899969c3
--- /dev/null
+++ b/packages/devtools-frontend-lynx/front_end/Images/src/ic_layers_16x16.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-US.json b/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-US.json
index a560d554e..9acbb8192 100644
--- a/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-US.json
+++ b/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-US.json
@@ -4190,6 +4190,12 @@
"panels/elements/EventListenersWidget.ts | showListenersOnTheAncestors": {
"message": "Show listeners on the ancestors"
},
+ "panels/elements/LayersWidget.ts | cssLayersTitle": {
+ "message": "CSS layers"
+ },
+ "panels/elements/LayersWidget.ts | toggleCSSLayers": {
+ "message": "Toggle CSS Layers view"
+ },
"panels/elements/MarkerDecorator.ts | domBreakpoint": {
"message": "DOM Breakpoint"
},
@@ -4250,6 +4256,9 @@
"panels/elements/StylePropertyTreeElement.ts | viewComputedValue": {
"message": "View computed value"
},
+ "panels/elements/StylesSidebarPane.ts | clickToRevealLayer": {
+ "message": "Click to reveal layer in layer tree"
+ },
"panels/elements/StylesSidebarPane.ts | constructedStylesheet": {
"message": "constructed stylesheet"
},
@@ -4289,6 +4298,9 @@
"panels/elements/StylesSidebarPane.ts | invalidString": {
"message": "{PH1}, property name: {PH2}, property value: {PH3}"
},
+ "panels/elements/StylesSidebarPane.ts | layer": {
+ "message": "Layer"
+ },
"panels/elements/StylesSidebarPane.ts | newStyleRule": {
"message": "New Style Rule"
},
diff --git a/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-XL.json b/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-XL.json
index 40d5adba5..028544258 100644
--- a/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-XL.json
+++ b/packages/devtools-frontend-lynx/front_end/core/i18n/locales/en-XL.json
@@ -4190,6 +4190,12 @@
"panels/elements/EventListenersWidget.ts | showListenersOnTheAncestors": {
"message": "Ŝh́ôẃ l̂íŝt́êńêŕŝ ón̂ t́ĥé âńĉéŝt́ôŕŝ"
},
+ "panels/elements/LayersWidget.ts | cssLayersTitle": {
+ "message": "ĈŚŜ ĺâýêŕŝ"
+ },
+ "panels/elements/LayersWidget.ts | toggleCSSLayers": {
+ "message": "T̂óĝǵl̂é ĈŚŜ Ĺâýêŕŝ v́îéŵ"
+ },
"panels/elements/MarkerDecorator.ts | domBreakpoint": {
"message": "D̂ÓM̂ B́r̂éâḱp̂óîńt̂"
},
@@ -4250,6 +4256,9 @@
"panels/elements/StylePropertyTreeElement.ts | viewComputedValue": {
"message": "V̂íêẃ ĉóm̂ṕût́êd́ v̂ál̂úê"
},
+ "panels/elements/StylesSidebarPane.ts | clickToRevealLayer": {
+ "message": "Ĉĺîćk̂ t́ô ŕêv́êál̂ ĺâýêŕ îń l̂áŷér̂ t́r̂éê"
+ },
"panels/elements/StylesSidebarPane.ts | constructedStylesheet": {
"message": "ĉón̂śt̂ŕûćt̂éd̂ śt̂ýl̂éŝh́êét̂"
},
@@ -4289,6 +4298,9 @@
"panels/elements/StylesSidebarPane.ts | invalidString": {
"message": "{PH1}, p̂ŕôṕêŕt̂ý n̂ám̂é: {PH2}, p̂ŕôṕêŕt̂ý v̂ál̂úê: {PH3}"
},
+ "panels/elements/StylesSidebarPane.ts | layer": {
+ "message": "L̂áŷér̂"
+ },
"panels/elements/StylesSidebarPane.ts | newStyleRule": {
"message": "N̂éŵ Śt̂ýl̂é R̂úl̂é"
},
diff --git a/packages/devtools-frontend-lynx/front_end/core/sdk/BUILD.gn b/packages/devtools-frontend-lynx/front_end/core/sdk/BUILD.gn
index 4ff109f26..97f01fb0b 100644
--- a/packages/devtools-frontend-lynx/front_end/core/sdk/BUILD.gn
+++ b/packages/devtools-frontend-lynx/front_end/core/sdk/BUILD.gn
@@ -17,6 +17,7 @@ devtools_module("sdk") {
"CPUThrottlingManager.ts",
"CSSContainerQuery.ts",
"CSSFontFace.ts",
+ "CSSLayer.ts",
"CSSMatchedStyles.ts",
"CSSMedia.ts",
"CSSMetadata.ts",
diff --git a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSLayer.ts b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSLayer.ts
new file mode 100644
index 000000000..2915b802a
--- /dev/null
+++ b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSLayer.ts
@@ -0,0 +1,30 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as TextUtils from '../../models/text_utils/text_utils.js';
+import type * as Protocol from '../../generated/protocol.js';
+
+import type {CSSModel} from './CSSModel.js';
+import {CSSQuery} from './CSSQuery.js';
+
+export class CSSLayer extends CSSQuery {
+ static parseLayerPayload(cssModel: CSSModel, payload: Protocol.CSS.CSSLayer[]): CSSLayer[] {
+ return payload.map(layer => new CSSLayer(cssModel, layer));
+ }
+
+ constructor(cssModel: CSSModel, payload: Protocol.CSS.CSSLayer) {
+ super(cssModel);
+ this.reinitialize(payload);
+ }
+
+ reinitialize(payload: Protocol.CSS.CSSLayer): void {
+ this.text = payload.text;
+ this.range = payload.range ? TextUtils.TextRange.TextRange.fromObject(payload.range) : null;
+ this.styleSheetId = payload.styleSheetId;
+ }
+
+ active(): boolean {
+ return true;
+ }
+}
diff --git a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSModel.ts b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSModel.ts
index 23e47cca7..c885e0a54 100644
--- a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSModel.ts
+++ b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSModel.ts
@@ -154,6 +154,10 @@ export class CSSModel extends SDKModel {
return this._sourceMapManager;
}
+ static readableLayerName(text: string): string {
+ return text || '';
+ }
+
static trimSourceURL(text: string): string {
let sourceURLIndex = text.lastIndexOf('/*# sourceURL=');
if (sourceURLIndex === -1) {
@@ -267,6 +271,11 @@ export class CSSModel extends SDKModel {
return medias ? CSSMedia.parseMediaArrayPayload(this, medias) : [];
}
+ async getRootLayer(nodeId: Protocol.DOM.NodeId): Promise {
+ const {rootLayer} = await this._agent.invoke_getLayersForNode({nodeId});
+ return rootLayer;
+ }
+
isEnabled(): boolean {
return this._isEnabled;
}
diff --git a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSQuery.ts b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSQuery.ts
index aeea6095a..6b88640a6 100644
--- a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSQuery.ts
+++ b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSQuery.ts
@@ -9,7 +9,8 @@ import type {CSSModel, Edit} from './CSSModel.js';
import {CSSLocation} from './CSSModel.js';
import type {CSSStyleSheetHeader} from './CSSStyleSheetHeader.js';
-type CSSQueryPayload = Protocol.CSS.CSSMedia|Protocol.CSS.CSSContainerQuery|Protocol.CSS.CSSSupports;
+type CSSQueryPayload =
+ Protocol.CSS.CSSMedia|Protocol.CSS.CSSContainerQuery|Protocol.CSS.CSSSupports|Protocol.CSS.CSSLayer;
export abstract class CSSQuery {
text = '';
diff --git a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSRule.ts b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSRule.ts
index 3fedfb545..fb530992f 100644
--- a/packages/devtools-frontend-lynx/front_end/core/sdk/CSSRule.ts
+++ b/packages/devtools-frontend-lynx/front_end/core/sdk/CSSRule.ts
@@ -14,6 +14,7 @@ import { reportToStatistics } from '../host/InspectorFrontendHost.js';
import { envLogger } from '../protocol_client/InspectorBackend.js';
import {CSSContainerQuery} from './CSSContainerQuery.js';
+import {CSSLayer} from './CSSLayer.js';
import {CSSMedia} from './CSSMedia.js';
import {CSSSupports} from './CSSSupports.js';
@@ -118,6 +119,7 @@ export class CSSStyleRule extends CSSRule {
media: CSSMedia[];
containerQueries: CSSContainerQuery[];
supports: CSSSupports[];
+ layers: CSSLayer[];
wasUsed: boolean;
constructor(cssModel: CSSModel, payload: Protocol.CSS.CSSRule, wasUsed?: boolean) {
// TODO(crbug.com/1011811): Replace with spread operator or better types once Closure is gone.
@@ -128,6 +130,7 @@ export class CSSStyleRule extends CSSRule {
CSSContainerQuery.parseContainerQueriesPayload(cssModel, payload.containerQueries) :
[];
this.supports = payload.supports ? CSSSupports.parseSupportsPayload(cssModel, payload.supports) : [];
+ this.layers = payload.layers ? CSSLayer.parseLayerPayload(cssModel, payload.layers) : [];
this.wasUsed = wasUsed || false;
}
diff --git a/packages/devtools-frontend-lynx/front_end/core/sdk/sdk.ts b/packages/devtools-frontend-lynx/front_end/core/sdk/sdk.ts
index e164fd07a..dc3bda62d 100644
--- a/packages/devtools-frontend-lynx/front_end/core/sdk/sdk.ts
+++ b/packages/devtools-frontend-lynx/front_end/core/sdk/sdk.ts
@@ -23,6 +23,7 @@ import * as CPUProfilerModel from './CPUProfilerModel.js';
import * as CPUThrottlingManager from './CPUThrottlingManager.js';
import * as CSSContainerQuery from './CSSContainerQuery.js';
import * as CSSFontFace from './CSSFontFace.js';
+import * as CSSLayer from './CSSLayer.js';
import * as CSSMatchedStyles from './CSSMatchedStyles.js';
import * as CSSMedia from './CSSMedia.js';
import * as CSSMetadata from './CSSMetadata.js';
@@ -89,6 +90,7 @@ export {
CPUThrottlingManager,
CSSContainerQuery,
CSSFontFace,
+ CSSLayer,
CSSMatchedStyles,
CSSMedia,
CSSMetadata,
diff --git a/packages/devtools-frontend-lynx/front_end/generated/InspectorBackendCommands.js b/packages/devtools-frontend-lynx/front_end/generated/InspectorBackendCommands.js
index dfeb9b0d3..0fd408efe 100644
--- a/packages/devtools-frontend-lynx/front_end/generated/InspectorBackendCommands.js
+++ b/packages/devtools-frontend-lynx/front_end/generated/InspectorBackendCommands.js
@@ -479,6 +479,8 @@ export function registerCommands(inspectorBackend) {
'CSS.getPlatformFontsForNode', [{'name': 'nodeId', 'type': 'number', 'optional': false}], ['fonts']);
inspectorBackend.registerCommand(
'CSS.getStyleSheetText', [{'name': 'styleSheetId', 'type': 'string', 'optional': false}], ['text']);
+ inspectorBackend.registerCommand(
+ 'CSS.getLayersForNode', [{'name': 'nodeId', 'type': 'number', 'optional': false}], ['rootLayer']);
inspectorBackend.registerCommand(
'CSS.trackComputedStyleUpdates', [{'name': 'propertiesToTrack', 'type': 'object', 'optional': false}], []);
inspectorBackend.registerCommand('CSS.takeComputedStyleUpdates', [], ['nodeIds']);
diff --git a/packages/devtools-frontend-lynx/front_end/generated/protocol-mapping.d.ts b/packages/devtools-frontend-lynx/front_end/generated/protocol-mapping.d.ts
index 9cae1014c..8809a8709 100644
--- a/packages/devtools-frontend-lynx/front_end/generated/protocol-mapping.d.ts
+++ b/packages/devtools-frontend-lynx/front_end/generated/protocol-mapping.d.ts
@@ -976,6 +976,11 @@ export namespace ProtocolMapping {
*/
'CSS.getStyleSheetText':
{paramsType: [Protocol.CSS.GetStyleSheetTextRequest]; returnType: Protocol.CSS.GetStyleSheetTextResponse;};
+ /**
+ * Returns all layers parsed by the rendering engine for the tree scope of a node.
+ */
+ 'CSS.getLayersForNode':
+ {paramsType: [Protocol.CSS.GetLayersForNodeRequest]; returnType: Protocol.CSS.GetLayersForNodeResponse;};
/**
* Starts tracking the given computed styles for updates. The specified array of properties
* replaces the one previously specified. Pass empty array to disable tracking.
diff --git a/packages/devtools-frontend-lynx/front_end/generated/protocol-proxy-api.d.ts b/packages/devtools-frontend-lynx/front_end/generated/protocol-proxy-api.d.ts
index 10e28ea0c..b181b9371 100644
--- a/packages/devtools-frontend-lynx/front_end/generated/protocol-proxy-api.d.ts
+++ b/packages/devtools-frontend-lynx/front_end/generated/protocol-proxy-api.d.ts
@@ -605,6 +605,12 @@ declare namespace ProtocolProxyApi {
invoke_getStyleSheetText(params: Protocol.CSS.GetStyleSheetTextRequest):
Promise;
+ /**
+ * Returns all layers parsed by the rendering engine for the tree scope of a node.
+ */
+ invoke_getLayersForNode(params: Protocol.CSS.GetLayersForNodeRequest):
+ Promise;
+
/**
* Starts tracking the given computed styles for updates. The specified array of properties
* replaces the one previously specified. Pass empty array to disable tracking.
diff --git a/packages/devtools-frontend-lynx/front_end/generated/protocol.d.ts b/packages/devtools-frontend-lynx/front_end/generated/protocol.d.ts
index 7a428fa01..1e18e840f 100644
--- a/packages/devtools-frontend-lynx/front_end/generated/protocol.d.ts
+++ b/packages/devtools-frontend-lynx/front_end/generated/protocol.d.ts
@@ -1879,6 +1879,11 @@ declare namespace Protocol {
* The array enumerates @supports at-rules starting with the innermost one, going outwards.
*/
supports?: CSSSupports[];
+ /**
+ * Cascade layer array. Contains the layer hierarchy that this rule belongs to starting
+ * with the innermost layer and going outwards.
+ */
+ layers?: CSSLayer[];
}
/**
@@ -2140,6 +2145,42 @@ declare namespace Protocol {
styleSheetId?: StyleSheetId;
}
+ /**
+ * CSS Layer at-rule descriptor.
+ */
+ export interface CSSLayer {
+ /**
+ * Layer name.
+ */
+ text: string;
+ /**
+ * The associated rule header range in the enclosing stylesheet (if available).
+ */
+ range?: SourceRange;
+ /**
+ * Identifier of the stylesheet containing this object (if exists).
+ */
+ styleSheetId?: StyleSheetId;
+ }
+
+ /**
+ * CSS Layer data.
+ */
+ export interface CSSLayerData {
+ /**
+ * Layer name.
+ */
+ name: string;
+ /**
+ * Direct sub-layers.
+ */
+ subLayers?: CSSLayerData[];
+ /**
+ * Layer order. A higher number has higher priority in the cascade order.
+ */
+ order: number;
+ }
+
/**
* Information about amount of glyphs that were rendered with given font.
*/
@@ -2450,6 +2491,14 @@ declare namespace Protocol {
text: string;
}
+ export interface GetLayersForNodeRequest {
+ nodeId: DOM.NodeId;
+ }
+
+ export interface GetLayersForNodeResponse extends ProtocolResponseWithError {
+ rootLayer: CSSLayerData;
+ }
+
export interface TrackComputedStyleUpdatesRequest {
propertiesToTrack: CSSComputedStyleProperty[];
}
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/BUILD.gn b/packages/devtools-frontend-lynx/front_end/panels/elements/BUILD.gn
index 13ff0286d..6cc28b3ec 100644
--- a/packages/devtools-frontend-lynx/front_end/panels/elements/BUILD.gn
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/BUILD.gn
@@ -4,9 +4,14 @@
import("../../../scripts/build/ninja/devtools_entrypoint.gni")
import("../../../scripts/build/ninja/devtools_module.gni")
+import("../../../scripts/build/ninja/generate_css.gni")
import("../../../third_party/typescript/typescript.gni")
import("../visibility.gni")
+generate_css("css_files") {
+ sources = [ "layersWidget.css" ]
+}
+
devtools_module("elements") {
sources = [
"AccessibilityTreeUtils.ts",
@@ -26,6 +31,7 @@ devtools_module("elements") {
"EventListenersWidget.ts",
"ImagePreviewPopover.ts",
"InspectElementModeController.ts",
+ "LayersWidget.ts",
"LayoutSidebarPane.ts",
"MarkerDecorator.ts",
"MetricsSidebarPane.ts",
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/LayersWidget.ts b/packages/devtools-frontend-lynx/front_end/panels/elements/LayersWidget.ts
new file mode 100644
index 000000000..87cafe304
--- /dev/null
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/LayersWidget.ts
@@ -0,0 +1,163 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as i18n from '../../core/i18n/i18n.js';
+import * as SDK from '../../core/sdk/sdk.js';
+import type * as Protocol from '../../generated/protocol.js';
+import * as TreeOutline from '../../ui/components/tree_outline/tree_outline.js';
+import * as IconButton from '../../ui/components/icon_button/icon_button.js';
+import * as UI from '../../ui/legacy/legacy.js';
+
+import {ElementsPanel} from './ElementsPanel.js';
+import layersWidgetStyles from './layersWidget.css.js';
+
+const UIStrings = {
+ /**
+ * @description Title of a section in the Elements panel that shows the CSS layers
+ * present in the context of the currently selected node.
+ */
+ cssLayersTitle: 'CSS layers',
+ /**
+ * @description Tooltip for a button that opens the CSS layers present in the current document.
+ */
+ toggleCSSLayers: 'Toggle CSS Layers view',
+};
+const str_ = i18n.i18n.registerUIStrings('panels/elements/LayersWidget.ts', UIStrings);
+const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
+
+let layersWidgetInstance: LayersWidget;
+
+export class LayersWidget extends UI.Widget.Widget {
+ private cssModel?: SDK.CSSModel.CSSModel|null;
+ private layerTreeComponent = new TreeOutline.TreeOutline.TreeOutline();
+
+ constructor() {
+ super(true);
+
+ this.contentElement.className = 'styles-layers-pane';
+ UI.UIUtils.createTextChild(this.contentElement.createChild('div'), i18nString(UIStrings.cssLayersTitle));
+ this.contentElement.appendChild(this.layerTreeComponent);
+
+ UI.Context.Context.instance().addFlavorChangeListener(SDK.DOMModel.DOMNode, this.update, this);
+ }
+
+ private updateModel(cssModel: SDK.CSSModel.CSSModel|null): void {
+ if (this.cssModel === cssModel) {
+ return;
+ }
+ if (this.cssModel) {
+ this.cssModel.removeEventListener(SDK.CSSModel.Events.StyleSheetChanged, this.update, this);
+ }
+ this.cssModel = cssModel;
+ if (this.cssModel) {
+ this.cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetChanged, this.update, this);
+ }
+ }
+
+ async wasShown(): Promise {
+ super.wasShown();
+ this.registerCSSFiles([layersWidgetStyles]);
+ return this.update();
+ }
+
+ private async update(): Promise {
+ if (!this.isShowing()) {
+ return;
+ }
+
+ let node = UI.Context.Context.instance().flavor(SDK.DOMModel.DOMNode);
+ if (node) {
+ node = node.enclosingElementOrSelf();
+ }
+ if (!node) {
+ return;
+ }
+
+ this.updateModel(node.domModel().cssModel());
+ if (!this.cssModel) {
+ return;
+ }
+
+ const makeTreeNode = (parentId: string) =>
+ (layer: Protocol.CSS.CSSLayerData): TreeOutline.TreeOutlineUtils.TreeNode => {
+ const name = SDK.CSSModel.CSSModel.readableLayerName(layer.name);
+ const id = parentId ? `${parentId}.${name}` : name;
+ const treeNodeData = `${layer.order}: ${name}`;
+ const subLayers = layer.subLayers;
+ if (!subLayers) {
+ return {treeNodeData, id};
+ }
+ return {
+ treeNodeData,
+ id,
+ children: (): Promise[]> =>
+ Promise.resolve(subLayers.sort((a, b) => a.order - b.order).map(makeTreeNode(id))),
+ };
+ };
+
+ const rootLayer = await this.cssModel.getRootLayer(node.id);
+ this.layerTreeComponent.data = {
+ defaultRenderer: TreeOutline.TreeOutline.defaultRenderer,
+ tree: [makeTreeNode('')(rootLayer)],
+ };
+
+ await this.layerTreeComponent.expandRecursively(5);
+ }
+
+ async revealLayer(layerName: string): Promise {
+ if (!this.isShowing()) {
+ ElementsPanel.instance().showToolbarPane(this, ButtonProvider.instance().item());
+ }
+ await this.update();
+ await this.layerTreeComponent.expandToAndSelectTreeNode(
+ {treeNodeData: layerName, id: `implicit outer layer.${layerName}`});
+ }
+
+ static instance(opts: {forceNew: boolean|null}|undefined = {forceNew: null}): LayersWidget {
+ const {forceNew} = opts;
+ if (!layersWidgetInstance || forceNew) {
+ layersWidgetInstance = new LayersWidget();
+ }
+ return layersWidgetInstance;
+ }
+}
+
+let buttonProviderInstance: ButtonProvider;
+
+export class ButtonProvider implements UI.Toolbar.Provider {
+ private readonly button: UI.Toolbar.ToolbarToggle;
+
+ private constructor() {
+ const layersIcon = new IconButton.Icon.Icon();
+ layersIcon.data = {
+ iconName: 'ic_layers_16x16',
+ color: 'var(--color-text-secondary)',
+ width: '13px',
+ height: '13px',
+ };
+ this.button = new UI.Toolbar.ToolbarToggle(i18nString(UIStrings.toggleCSSLayers), '');
+ layersIcon.classList.add('toolbar-icon');
+ this.button.element.appendChild(layersIcon);
+ this.button.setVisible(false);
+ this.button.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this.clicked, this);
+ this.button.element.classList.add('monospace');
+ }
+
+ static instance(opts: {forceNew: boolean|null} = {forceNew: null}): ButtonProvider {
+ const {forceNew} = opts;
+ if (!buttonProviderInstance || forceNew) {
+ buttonProviderInstance = new ButtonProvider();
+ }
+ return buttonProviderInstance;
+ }
+
+ private clicked(): void {
+ const view = LayersWidget.instance();
+ ElementsPanel.instance().showToolbarPane(!view.isShowing() ? view : null, this.button);
+ }
+
+ item(): UI.Toolbar.ToolbarToggle {
+ return this.button;
+ }
+}
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/StylesSidebarPane.ts b/packages/devtools-frontend-lynx/front_end/panels/elements/StylesSidebarPane.ts
index 77885bae6..20a6b2ec9 100644
--- a/packages/devtools-frontend-lynx/front_end/panels/elements/StylesSidebarPane.ts
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/StylesSidebarPane.ts
@@ -58,6 +58,7 @@ import {linkifyDeferredNodeReference} from './DOMLinkifier.js';
import {ElementsPanel} from './ElementsPanel.js';
import {ElementsSidebarPane} from './ElementsSidebarPane.js';
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
+import * as LayersWidget from './LayersWidget.js';
import {StyleEditorWidget} from './StyleEditorWidget.js';
import {StylePropertyHighlighter} from './StylePropertyHighlighter.js';
@@ -163,6 +164,14 @@ const UIStrings = {
*@description Tooltip text that appears when hovering over the largeicon add button in the Styles Sidebar Pane of the Elements panel
*/
newStyleRule: 'New Style Rule',
+ /**
+ *@description Text displayed on layer separators in the Styles sidebar pane
+ */
+ layer: 'Layer',
+ /**
+ *@description Tooltip for a link that reveals a CSS layer in the layer tree
+ */
+ clickToRevealLayer: 'Click to reveal layer in layer tree',
};
const str_ = i18n.i18n.registerUIStrings('panels/elements/StylesSidebarPane.ts', UIStrings);
@@ -748,9 +757,9 @@ export class StylesSidebarPane extends ElementsSidebarPane {
const nodeLoc = node!._nodeLoc;
if (nodeLoc) {
- this._sectionBlocks.unshift(SectionBlock.createOriginalNodeLocBlock(nodeLoc.sourceURL ? nodeLoc.sourceURL : "", nodeLoc.sourceLineNumber, nodeLoc.sourceColumnNumber));
+ this._sectionBlocks.unshift(SectionBlock.createOriginalNodeLocBlock(nodeLoc.sourceURL ? nodeLoc.sourceURL : '', nodeLoc.sourceLineNumber, nodeLoc.sourceColumnNumber));
}
-
+
// Style sections maybe re-created when flexbox editor is activated.
// With the following code we re-bind the flexbox editor to the new
// section with the same index as the previous section had.
@@ -828,14 +837,56 @@ export class StylesSidebarPane extends ElementsSidebarPane {
const blocks = [new SectionBlock(null)];
let lastParentNode: SDK.DOMModel.DOMNode|null = null;
+ let lastLayerKey: string|null = null;
+ let sawLayers = false;
+ const anonymousLayerIds = new WeakMap();
+ let nextAnonymousLayerId = 0;
+
+ const addLayerSeparator = (style: SDK.CSSStyleDeclaration.CSSStyleDeclaration): void => {
+ const parentRule = style.parentRule;
+ if (!(parentRule instanceof SDK.CSSRule.CSSStyleRule)) {
+ return;
+ }
+
+ const layers = parentRule.layers;
+ const layerKey = layers.length ?
+ layers.map(layer => {
+ if (layer.text) {
+ return `named:${layer.text}`;
+ }
+ const range = layer.range;
+ if (range) {
+ return `anonymous:${layer.styleSheetId || ''}:${range.startLine}:${range.startColumn}:${range.endLine}:${
+ range.endColumn}`;
+ }
+ let anonymousLayerId = anonymousLayerIds.get(layer);
+ if (anonymousLayerId === undefined) {
+ anonymousLayerId = nextAnonymousLayerId++;
+ anonymousLayerIds.set(layer, anonymousLayerId);
+ }
+ return `anonymous-object:${anonymousLayerId}`;
+ }).join('/') :
+ parentRule.origin === Protocol.CSS.StyleSheetOrigin.UserAgent ? 'user-agent' : 'implicit';
+ if ((layers.length || lastLayerKey !== null) && lastLayerKey !== layerKey) {
+ blocks.push(SectionBlock.createLayerBlock(parentRule));
+ sawLayers = true;
+ lastLayerKey = layerKey;
+ }
+ };
+
+ LayersWidget.ButtonProvider.instance().item().setVisible(false);
+
for (const style of matchedStyles.nodeStyles()) {
const parentNode = matchedStyles.isInherited(style) ? matchedStyles.nodeForStyle(style) : null;
if (parentNode && parentNode !== lastParentNode) {
lastParentNode = parentNode;
const block = await SectionBlock._createInheritedNodeBlock(lastParentNode);
blocks.push(block);
+ lastLayerKey = null;
}
+ addLayerSeparator(style);
+
const lastBlock = blocks[blocks.length - 1];
if (lastBlock) {
this._idleCallbackManager.schedule(() => {
@@ -852,14 +903,17 @@ export class StylesSidebarPane extends ElementsSidebarPane {
}
pseudoTypes = pseudoTypes.concat([...keys].sort());
for (const pseudoType of pseudoTypes) {
- const block = SectionBlock.createPseudoTypeBlock(pseudoType);
+ blocks.push(SectionBlock.createPseudoTypeBlock(pseudoType));
+ lastLayerKey = null;
+
for (const style of matchedStyles.pseudoStyles(pseudoType)) {
+ addLayerSeparator(style);
+ const lastBlock = blocks[blocks.length - 1];
this._idleCallbackManager.schedule(() => {
const section = new StylePropertiesSection(this, matchedStyles, style);
- block.sections.push(section);
+ lastBlock.sections.push(section);
});
}
- blocks.push(block);
}
for (const keyframesRule of matchedStyles.keyframes()) {
@@ -872,6 +926,12 @@ export class StylesSidebarPane extends ElementsSidebarPane {
blocks.push(block);
}
+ if (sawLayers) {
+ LayersWidget.ButtonProvider.instance().item().setVisible(true);
+ } else if (LayersWidget.LayersWidget.instance().isShowing()) {
+ ElementsPanel.instance().showToolbarPane(null, LayersWidget.ButtonProvider.instance().item());
+ }
+
await this._idleCallbackManager.awaitDone();
return blocks;
@@ -1109,6 +1169,29 @@ export class SectionBlock {
return new SectionBlock(separatorElement);
}
+ static createLayerBlock(rule: SDK.CSSRule.CSSStyleRule): SectionBlock {
+ const separatorElement = document.createElement('div');
+ separatorElement.className = 'sidebar-separator layer-separator';
+ UI.UIUtils.createTextChild(separatorElement.createChild('div'), i18nString(UIStrings.layer));
+
+ const layers = rule.layers;
+ if (!layers.length) {
+ const name = rule.origin === Protocol.CSS.StyleSheetOrigin.UserAgent ?
+ '\xa0user\xa0agent\xa0stylesheet' :
+ '\xa0implicit\xa0outer\xa0layer';
+ UI.UIUtils.createTextChild(separatorElement.createChild('div'), name);
+ return new SectionBlock(separatorElement);
+ }
+
+ const layerLink = separatorElement.createChild('button') as HTMLButtonElement;
+ layerLink.className = 'link';
+ layerLink.title = i18nString(UIStrings.clickToRevealLayer);
+ const name = layers.map(layer => SDK.CSSModel.CSSModel.readableLayerName(layer.text)).join('.');
+ layerLink.textContent = name;
+ layerLink.onclick = (): Promise => LayersWidget.LayersWidget.instance().revealLayer(name);
+ return new SectionBlock(separatorElement);
+ }
+
static createOriginalNodeLocBlock(filename: string, line: number, col: number): SectionBlock {
const element = document.createElement('div');
element.className = 'styles-section';
@@ -1134,7 +1217,7 @@ export class SectionBlock {
const contextMenu = new UI.ContextMenu.ContextMenu(event);
contextMenu.clipboardSection().appendItem('copy', () => {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(fileNameElement.textContent);
- })
+ });
contextMenu.show();
});
fileElement.appendChild(fileSubtitleElement);
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/elements-meta.ts b/packages/devtools-frontend-lynx/front_end/panels/elements/elements-meta.ts
index fbca5dceb..d2d954a0c 100644
--- a/packages/devtools-frontend-lynx/front_end/panels/elements/elements-meta.ts
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/elements-meta.ts
@@ -505,7 +505,7 @@ Common.Revealer.registerRevealer({
UI.Toolbar.registerToolbarItem({
async loadItem() {
const Elements = await loadElementsModule();
- return Elements.ElementStatePaneWidget.ButtonProvider.instance();
+ return Elements.LayersWidget.ButtonProvider.instance();
},
order: 1,
location: UI.Toolbar.ToolbarItemLocation.STYLES_SIDEBARPANE_TOOLBAR,
@@ -518,7 +518,7 @@ UI.Toolbar.registerToolbarItem({
UI.Toolbar.registerToolbarItem({
async loadItem() {
const Elements = await loadElementsModule();
- return Elements.ClassesPaneWidget.ButtonProvider.instance();
+ return Elements.ElementStatePaneWidget.ButtonProvider.instance();
},
order: 2,
location: UI.Toolbar.ToolbarItemLocation.STYLES_SIDEBARPANE_TOOLBAR,
@@ -528,6 +528,19 @@ UI.Toolbar.registerToolbarItem({
actionId: undefined,
});
+UI.Toolbar.registerToolbarItem({
+ async loadItem() {
+ const Elements = await loadElementsModule();
+ return Elements.ClassesPaneWidget.ButtonProvider.instance();
+ },
+ order: 3,
+ location: UI.Toolbar.ToolbarItemLocation.STYLES_SIDEBARPANE_TOOLBAR,
+ showLabel: undefined,
+ condition: undefined,
+ separator: undefined,
+ actionId: undefined,
+});
+
UI.Toolbar.registerToolbarItem({
async loadItem() {
const Elements = await loadElementsModule();
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/elements.ts b/packages/devtools-frontend-lynx/front_end/panels/elements/elements.ts
index a8798129b..ee966330c 100644
--- a/packages/devtools-frontend-lynx/front_end/panels/elements/elements.ts
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/elements.ts
@@ -39,6 +39,7 @@ import * as ElementsTreeElementHighlighter from './ElementsTreeElementHighlighte
import * as ElementsTreeOutline from './ElementsTreeOutline.js';
import * as EventListenersWidget from './EventListenersWidget.js';
import * as InspectElementModeController from './InspectElementModeController.js';
+import * as LayersWidget from './LayersWidget.js';
import * as LayoutSidebarPane from './LayoutSidebarPane.js';
import * as MarkerDecorator from './MarkerDecorator.js';
import * as MetricsSidebarPane from './MetricsSidebarPane.js';
@@ -65,6 +66,7 @@ export {
ElementsTreeOutline,
EventListenersWidget,
InspectElementModeController,
+ LayersWidget,
LayoutSidebarPane,
MarkerDecorator,
MetricsSidebarPane,
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/layersWidget.css b/packages/devtools-frontend-lynx/front_end/panels/elements/layersWidget.css
new file mode 100644
index 000000000..929608326
--- /dev/null
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/layersWidget.css
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2022 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.styles-layers-pane {
+ overflow: hidden;
+ padding-left: 2px;
+ background-color: var(--color-background-elevation-1);
+ border-bottom: 1px solid var(--color-details-hairline);
+ margin-top: 0;
+ padding-bottom: 2px;
+}
+
+.styles-layers-pane > div {
+ font-weight: bold;
+ margin: 8px 4px 6px;
+}
+
+.styles-layers-pane > table {
+ width: 100%;
+ border-spacing: 0;
+}
+
+.styles-layers-pane td {
+ padding: 0;
+}
diff --git a/packages/devtools-frontend-lynx/front_end/panels/elements/stylesSidebarPane.css b/packages/devtools-frontend-lynx/front_end/panels/elements/stylesSidebarPane.css
index acc056f87..579293384 100644
--- a/packages/devtools-frontend-lynx/front_end/panels/elements/stylesSidebarPane.css
+++ b/packages/devtools-frontend-lynx/front_end/panels/elements/stylesSidebarPane.css
@@ -217,6 +217,10 @@
margin-left: 2px;
}
+.sidebar-separator.layer-separator {
+ display: flex;
+}
+
.sidebar-pane-section-toolbar {
position: absolute;
right: 0;
diff --git a/packages/devtools-frontend-lynx/test/e2e/elements/style-pane-properties_test.ts b/packages/devtools-frontend-lynx/test/e2e/elements/style-pane-properties_test.ts
index b07a169b8..296b36862 100644
--- a/packages/devtools-frontend-lynx/test/e2e/elements/style-pane-properties_test.ts
+++ b/packages/devtools-frontend-lynx/test/e2e/elements/style-pane-properties_test.ts
@@ -5,7 +5,7 @@
import {assert} from 'chai';
import type * as puppeteer from 'puppeteer';
-import {$$, click, getBrowserAndPages, goToResource, timeout, waitFor} from '../../shared/helper.js';
+import {$$, click, getBrowserAndPages, goToResource, timeout, waitFor, waitForFunction} from '../../shared/helper.js';
import {describe, it} from '../../shared/mocha-extensions.js';
import {editQueryRuleText, getComputedStylesForDomNode, getDisplayedCSSPropertyNames, getDisplayedStyleRules, getStyleRule, getStyleSectionSubtitles, waitForContentOfSelectedElementsNode, waitForElementsStyleSection, waitForPropertyToHighlight, waitForStyleRule} from '../helpers/elements-helpers.js';
@@ -17,6 +17,7 @@ const SECOND_PROPERTY_NAME_SELECTOR = '.tree-outline li:nth-of-type(2) > .webkit
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';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const deletePropertyByBackspace = async (selector: string, root?: puppeteer.ElementHandle) => {
@@ -348,4 +349,39 @@ describe('The Styles pane', async () => {
const supportsQueryText = await supportsQuery.evaluate(node => (node as HTMLElement).innerText as string);
assert.deepEqual(supportsQueryText, '@supports (width: 10px)', 'incorrectly displayed @supports rule');
});
+
+ it('can display @layer separators', async () => {
+ const {frontend} = getBrowserAndPages();
+ await goToResourceAndWaitForStyleSection('elements/css-layers.html');
+
+ await frontend.keyboard.press('ArrowDown');
+ await waitForContentOfSelectedElementsNode('\u200B
\u200B');
+
+ const layerSeparators = await waitForFunction(async () => {
+ const layers = await $$(LAYER_SEPARATOR_SELECTOR);
+ return layers.length === 6 ? layers : undefined;
+ });
+ const layerText = await Promise.all(layerSeparators.map(element => element.evaluate(node => node.textContent)));
+ assert.deepEqual(layerText, [
+ 'Layer',
+ 'Layerimportant',
+ 'Layeroverrule',
+ 'Layeroverrule.',
+ 'Layerbase',
+ 'Layer\xa0user\xa0agent\xa0stylesheet',
+ ]);
+ });
+
+ it('can open the layer tree from an @layer separator', async () => {
+ const {frontend} = getBrowserAndPages();
+ await goToResourceAndWaitForStyleSection('elements/css-layers.html');
+
+ await frontend.keyboard.press('ArrowDown');
+ await waitForContentOfSelectedElementsNode('\u200B
\u200B');
+
+ const overruleButton = await waitFor('overrule[role="button"]', undefined, undefined, 'aria');
+ await click(overruleButton);
+
+ await waitFor('[data-node-key="2: overrule"]');
+ });
});
diff --git a/packages/devtools-frontend-lynx/test/e2e/resources/elements/BUILD.gn b/packages/devtools-frontend-lynx/test/e2e/resources/elements/BUILD.gn
index 5aa152478..85cca22ab 100644
--- a/packages/devtools-frontend-lynx/test/e2e/resources/elements/BUILD.gn
+++ b/packages/devtools-frontend-lynx/test/e2e/resources/elements/BUILD.gn
@@ -12,6 +12,7 @@ copy_to_gen("elements") {
"css-container-queries.html",
"css-grid-ua-shadow.html",
"css-grid.html",
+ "css-layers.html",
"css-module.css",
"css-supports.html",
"css-variables.html",
diff --git a/packages/devtools-frontend-lynx/test/e2e/resources/elements/css-layers.html b/packages/devtools-frontend-lynx/test/e2e/resources/elements/css-layers.html
new file mode 100644
index 000000000..844d2a998
--- /dev/null
+++ b/packages/devtools-frontend-lynx/test/e2e/resources/elements/css-layers.html
@@ -0,0 +1,37 @@
+
+
+
diff --git a/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.json b/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.json
index 2e1f4ffaf..196f890f1 100644
--- a/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.json
+++ b/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.json
@@ -2825,6 +2825,16 @@
"items": {
"$ref": "CSSSupports"
}
+ },
+ {
+ "name": "layers",
+ "description": "Cascade layer array. Contains the layer hierarchy that this rule belongs to starting\nwith the innermost layer and going outwards.",
+ "experimental": true,
+ "optional": true,
+ "type": "array",
+ "items": {
+ "$ref": "CSSLayer"
+ }
}
]
},
@@ -3174,6 +3184,58 @@
}
]
},
+ {
+ "id": "CSSLayer",
+ "description": "CSS Layer at-rule descriptor.",
+ "experimental": true,
+ "type": "object",
+ "properties": [
+ {
+ "name": "text",
+ "description": "Layer name.",
+ "type": "string"
+ },
+ {
+ "name": "range",
+ "description": "The associated rule header range in the enclosing stylesheet (if\navailable).",
+ "optional": true,
+ "$ref": "SourceRange"
+ },
+ {
+ "name": "styleSheetId",
+ "description": "Identifier of the stylesheet containing this object (if exists).",
+ "optional": true,
+ "$ref": "StyleSheetId"
+ }
+ ]
+ },
+ {
+ "id": "CSSLayerData",
+ "description": "CSS Layer data.",
+ "experimental": true,
+ "type": "object",
+ "properties": [
+ {
+ "name": "name",
+ "description": "Layer name.",
+ "type": "string"
+ },
+ {
+ "name": "subLayers",
+ "description": "Direct sub-layers.",
+ "optional": true,
+ "type": "array",
+ "items": {
+ "$ref": "CSSLayerData"
+ }
+ },
+ {
+ "name": "order",
+ "description": "Layer order. The order determines the order of the layer in the cascade order.\nA higher number has higher priority in the cascade order.",
+ "type": "number"
+ }
+ ]
+ },
{
"id": "PlatformFontUsage",
"description": "Information about amount of glyphs that were rendered with given font.",
@@ -3636,6 +3698,23 @@
}
]
},
+ {
+ "name": "getLayersForNode",
+ "description": "Returns all layers parsed by the rendering engine for the tree scope of a node.\nGiven a DOM element identified by nodeId, getLayersForNode returns the root\nlayer for the nearest ancestor document or shadow root. The layer root contains\nthe full layer tree for the tree scope and their ordering.",
+ "experimental": true,
+ "parameters": [
+ {
+ "name": "nodeId",
+ "$ref": "DOM.NodeId"
+ }
+ ],
+ "returns": [
+ {
+ "name": "rootLayer",
+ "$ref": "CSSLayerData"
+ }
+ ]
+ },
{
"name": "trackComputedStyleUpdates",
"description": "Starts tracking the given computed styles for updates. The specified array of properties\nreplaces the one previously specified. Pass empty array to disable tracking.\nUse takeComputedStyleUpdates to retrieve the list of nodes that had properties modified.\nThe changes to computed style properties are only tracked for nodes pushed to the front-end\nby the DOM agent. If no changes to the tracked properties occur after the node has been pushed\nto the front-end, no updates will be issued for the node.",
diff --git a/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 710596a7e..bfcc2243b 100644
--- a/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/packages/devtools-frontend-lynx/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -1337,6 +1337,9 @@ experimental domain CSS
# @supports CSS at-rule array.
# The array enumerates @supports at-rules starting with the innermost one, going outwards.
experimental optional array of CSSSupports supports
+ # Cascade layer array. Contains the layer hierarchy that this rule belongs to starting
+ # with the innermost layer and going outwards.
+ experimental optional array of CSSLayer layers
# CSS coverage information.
type RuleUsage extends object
@@ -1484,6 +1487,28 @@ experimental domain CSS
# Identifier of the stylesheet containing this object (if exists).
optional StyleSheetId styleSheetId
+ # CSS Layer at-rule descriptor.
+ experimental type CSSLayer extends object
+ properties
+ # Layer name.
+ string text
+ # The associated rule header range in the enclosing stylesheet (if
+ # available).
+ optional SourceRange range
+ # Identifier of the stylesheet containing this object (if exists).
+ optional StyleSheetId styleSheetId
+
+ # CSS Layer data.
+ experimental type CSSLayerData extends object
+ properties
+ # Layer name.
+ string name
+ # Direct sub-layers.
+ optional array of CSSLayerData subLayers
+ # Layer order. The order determines the order of the layer in the cascade order.
+ # A higher number has higher priority in the cascade order.
+ number order
+
# Information about amount of glyphs that were rendered with given font.
type PlatformFontUsage extends object
properties
@@ -1685,6 +1710,16 @@ experimental domain CSS
# The stylesheet text.
string text
+ # Returns all layers parsed by the rendering engine for the tree scope of a node.
+ # Given a DOM element identified by nodeId, getLayersForNode returns the root
+ # layer for the nearest ancestor document or shadow root. The layer root contains
+ # the full layer tree for the tree scope and their ordering.
+ experimental command getLayersForNode
+ parameters
+ DOM.NodeId nodeId
+ returns
+ CSSLayerData rootLayer
+
# Starts tracking the given computed styles for updates. The specified array of properties
# replaces the one previously specified. Pass empty array to disable tracking.
# Use takeComputedStyleUpdates to retrieve the list of nodes that had properties modified.