chrome-devtools-frontend 1.0.1650677 → 1.0.1652307
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/front_end/core/sdk/CSSMatchedStyles.ts +20 -4
- package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +94 -0
- package/front_end/generated/InspectorBackendCommands.ts +6 -3
- package/front_end/generated/SupportedCSSProperties.js +370 -6
- package/front_end/generated/protocol-mapping.d.ts +7 -0
- package/front_end/generated/protocol-proxy-api.d.ts +5 -0
- package/front_end/generated/protocol.ts +66 -7
- package/front_end/models/ai_assistance/AiAgent2.ts +3 -3
- package/front_end/models/ai_assistance/README.md +1 -1
- package/front_end/models/ai_assistance/tools/README.md +1 -1
- package/front_end/models/ai_assistance/tools/Tool.ts +1 -1
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +6 -6
- package/front_end/models/heap_snapshot/HeapSnapshotModel.ts +14 -0
- package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +4 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
- package/front_end/panels/elements/CSSSpecificityBreakdown.ts +106 -0
- package/front_end/panels/elements/StylePropertiesSection.ts +40 -13
- package/front_end/panels/elements/StylePropertyTreeElement.ts +6 -5
- package/front_end/panels/elements/components/CSSQuery.ts +119 -4
- package/front_end/panels/elements/elements.ts +3 -0
- package/front_end/panels/elements/stylesSidebarPane.css +34 -0
- package/front_end/panels/layer_viewer/PaintProfilerView.ts +0 -3
- package/front_end/panels/sources/ScopeChainSidebarPane.ts +0 -12
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/package.json +1 -1
|
@@ -1444,6 +1444,7 @@ export namespace Audits {
|
|
|
1444
1444
|
JwksHttpNotFound = 'JwksHttpNotFound',
|
|
1445
1445
|
JwksInvalidResponse = 'JwksInvalidResponse',
|
|
1446
1446
|
TokenVerificationSdJwtUnsupportedHeaderAlg = 'TokenVerificationSdJwtUnsupportedHeaderAlg',
|
|
1447
|
+
TokenVerificationSdJwtInvalidTyp = 'TokenVerificationSdJwtInvalidTyp',
|
|
1447
1448
|
TokenVerificationSdJwtMissingIss = 'TokenVerificationSdJwtMissingIss',
|
|
1448
1449
|
TokenVerificationSdJwtMissingIat = 'TokenVerificationSdJwtMissingIat',
|
|
1449
1450
|
TokenVerificationSdJwtMissingCnf = 'TokenVerificationSdJwtMissingCnf',
|
|
@@ -4803,6 +4804,7 @@ export namespace DOM {
|
|
|
4803
4804
|
FileSelectorButton = 'file-selector-button',
|
|
4804
4805
|
DetailsContent = 'details-content',
|
|
4805
4806
|
Picker = 'picker',
|
|
4807
|
+
SelectListbox = 'select-listbox',
|
|
4806
4808
|
PermissionIcon = 'permission-icon',
|
|
4807
4809
|
OverscrollAreaParent = 'overscroll-area-parent',
|
|
4808
4810
|
Skeleton = 'skeleton',
|
|
@@ -13982,6 +13984,58 @@ export namespace Overlay {
|
|
|
13982
13984
|
outlineColor?: DOM.RGBA;
|
|
13983
13985
|
}
|
|
13984
13986
|
|
|
13987
|
+
/**
|
|
13988
|
+
* Supported display cutout shapes.
|
|
13989
|
+
*/
|
|
13990
|
+
export const enum DisplayCutoutShape {
|
|
13991
|
+
Pill = 'pill',
|
|
13992
|
+
Notch = 'notch',
|
|
13993
|
+
Circle = 'circle',
|
|
13994
|
+
Rectangle = 'rectangle',
|
|
13995
|
+
}
|
|
13996
|
+
|
|
13997
|
+
/**
|
|
13998
|
+
* Configuration for a display cutout.
|
|
13999
|
+
*/
|
|
14000
|
+
export interface DisplayCutoutConfig {
|
|
14001
|
+
/**
|
|
14002
|
+
* A rectangle representing the cutout bounds.
|
|
14003
|
+
*/
|
|
14004
|
+
rect: DOM.Rect;
|
|
14005
|
+
/**
|
|
14006
|
+
* Shape used to draw the cutout.
|
|
14007
|
+
*/
|
|
14008
|
+
shape: DisplayCutoutShape;
|
|
14009
|
+
/**
|
|
14010
|
+
* Border radius for rounded cutout shapes.
|
|
14011
|
+
*/
|
|
14012
|
+
borderRadius?: integer;
|
|
14013
|
+
/**
|
|
14014
|
+
* Upper shoulder radius for notch cutout shapes.
|
|
14015
|
+
*/
|
|
14016
|
+
upperRadius?: integer;
|
|
14017
|
+
/**
|
|
14018
|
+
* Lower transition radius for notch cutout shapes.
|
|
14019
|
+
*/
|
|
14020
|
+
lowerRadius?: integer;
|
|
14021
|
+
/**
|
|
14022
|
+
* Center x coordinate for circle cutout shapes.
|
|
14023
|
+
*/
|
|
14024
|
+
cx?: integer;
|
|
14025
|
+
/**
|
|
14026
|
+
* Center y coordinate for circle cutout shapes.
|
|
14027
|
+
*/
|
|
14028
|
+
cy?: integer;
|
|
14029
|
+
/**
|
|
14030
|
+
* Radius for circle cutout shapes.
|
|
14031
|
+
*/
|
|
14032
|
+
radius?: integer;
|
|
14033
|
+
/**
|
|
14034
|
+
* The cutout fill color (default: black).
|
|
14035
|
+
*/
|
|
14036
|
+
contentColor?: DOM.RGBA;
|
|
14037
|
+
}
|
|
14038
|
+
|
|
13985
14039
|
/**
|
|
13986
14040
|
* Configuration for Window Controls Overlay
|
|
13987
14041
|
*/
|
|
@@ -14344,6 +14398,13 @@ export namespace Overlay {
|
|
|
14344
14398
|
hingeConfig?: HingeConfig;
|
|
14345
14399
|
}
|
|
14346
14400
|
|
|
14401
|
+
export interface SetShowDisplayCutoutRequest {
|
|
14402
|
+
/**
|
|
14403
|
+
* display cutout data, null means hide display cutout
|
|
14404
|
+
*/
|
|
14405
|
+
displayCutoutConfig?: DisplayCutoutConfig;
|
|
14406
|
+
}
|
|
14407
|
+
|
|
14347
14408
|
export interface SetShowIsolatedElementsRequest {
|
|
14348
14409
|
/**
|
|
14349
14410
|
* An array of node identifiers and descriptors for the highlight appearance.
|
|
@@ -19431,13 +19492,11 @@ export namespace Target {
|
|
|
19431
19492
|
*/
|
|
19432
19493
|
hidden?: boolean;
|
|
19433
19494
|
/**
|
|
19434
|
-
* If specified,
|
|
19435
|
-
*
|
|
19436
|
-
*
|
|
19437
|
-
*
|
|
19438
|
-
*
|
|
19439
|
-
* and background=false with focus=undefined will result in the window being focused.
|
|
19440
|
-
* Using background: true and focus: true is not supported and will result in an error.
|
|
19495
|
+
* If specified, determines whether the new target should be focused.
|
|
19496
|
+
* By default, the focus behavior depends on the `background` parameter:
|
|
19497
|
+
* - If `background` is false (default) and `focus` is omitted, the new target is focused and the browser window is brought to the foreground.
|
|
19498
|
+
* - If `background` is false and `focus` is false, the target is opened but the browser window's focus remains unchanged (e.g., if the window was in the background, it stays there).
|
|
19499
|
+
* - If `background` is true, setting `focus` to true is not supported and will result in an error.
|
|
19441
19500
|
*/
|
|
19442
19501
|
focus?: boolean;
|
|
19443
19502
|
}
|
|
@@ -22,7 +22,7 @@ import {debugLog} from './debug.js';
|
|
|
22
22
|
import {ExtensionScope} from './ExtensionScope.js';
|
|
23
23
|
import type {Skill, SkillName} from './skills/Skill.js';
|
|
24
24
|
import {SKILLS} from './skills/SkillRegistry.js';
|
|
25
|
-
import type {
|
|
25
|
+
import type {AllToolsCapabilities, Tool, ToolArgs} from './tools/Tool.js';
|
|
26
26
|
import {ToolRegistry} from './tools/ToolRegistry.js';
|
|
27
27
|
|
|
28
28
|
const SKILL_DISPLAY_NAMES: Record<SkillName, string> = {
|
|
@@ -214,7 +214,7 @@ User query: ${enhancedQuery}`;
|
|
|
214
214
|
* Declares a tool to be available to the agent model, verifying first that
|
|
215
215
|
* it hasn't already been declared to prevent duplicate declaration errors.
|
|
216
216
|
*/
|
|
217
|
-
#declareTool(tool: Tool<ToolArgs, unknown,
|
|
217
|
+
#declareTool(tool: Tool<ToolArgs, unknown, AllToolsCapabilities>): void {
|
|
218
218
|
if (this.#declaredTools.has(tool.name)) {
|
|
219
219
|
debugLog(`AiAgent2: Tool ${tool.name} is already declared`);
|
|
220
220
|
return;
|
|
@@ -225,7 +225,7 @@ User query: ${enhancedQuery}`;
|
|
|
225
225
|
parameters: tool.parameters,
|
|
226
226
|
displayInfoFromArgs: tool.displayInfoFromArgs,
|
|
227
227
|
handler: (args, options) => {
|
|
228
|
-
const context:
|
|
228
|
+
const context: AllToolsCapabilities = {
|
|
229
229
|
conversationContext: this.context ?? null,
|
|
230
230
|
changeManager: this.#changes,
|
|
231
231
|
createExtensionScope: this.#createExtensionScope.bind(this),
|
|
@@ -46,7 +46,7 @@ To support skills requiring execution of code or fetching page state (like compu
|
|
|
46
46
|
|
|
47
47
|
- **BaseTool**: A non-generic base interface capturing tool metadata (`name`, `description`, `parameters`). This acts as the type-erased representation for generic registry storage and fallback string lookups.
|
|
48
48
|
- **Tool**: A generic interface parameterized by `<Args, ReturnType, ContextType>` that binds parameter argument types and handler execution to strict contracts. `ContextType` defaults to `BaseToolCapability`, ensuring that each tool explicitly requests only the dependencies it requires.
|
|
49
|
-
- **Capability Contexts**: Instead of passing a monolithic grab-bag context to all tools, dependencies are broken into narrow capability interfaces (e.g. `PageExecutionCapability`, `StyleMutationCapability`, `TargetCapability`, `OriginLockCapability`). Tools declare their required dependencies by intersecting these interfaces on their generic `ContextType` definition. The caller/Agent fulfills the complete capability context (`
|
|
49
|
+
- **Capability Contexts**: Instead of passing a monolithic grab-bag context to all tools, dependencies are broken into narrow capability interfaces (e.g. `PageExecutionCapability`, `StyleMutationCapability`, `TargetCapability`, `OriginLockCapability`). Tools declare their required dependencies by intersecting these interfaces on their generic `ContextType` definition. The caller/Agent fulfills the complete capability context (`AllToolsCapabilities`), guaranteeing 100% compile-time type safety for dependencies without runtime checks.
|
|
50
50
|
- **ToolRegistry**: A static registry (`ToolRegistry`) storing instantiated tools. It uses TypeScript function overloading and generic lookups (`static get<K extends keyof typeof TOOLS>(name: K): typeof TOOLS[K]`) to return the precise class type of each tool, preventing type-erasure and escape-hatches (such as `any` or `as unknown` type assertions) at integration points like `AiAgent2.ts`. See the [Tools README](tools/README.md) for authoring instructions.
|
|
51
51
|
|
|
52
52
|
## Performance specific documentation
|
|
@@ -21,7 +21,7 @@ Instead of passing a monolithic "grab-bag" context object to all tool handlers,
|
|
|
21
21
|
|
|
22
22
|
### Unified Context
|
|
23
23
|
|
|
24
|
-
The Agent (e.g., `AiAgent2`) builds the complete dependency union (`
|
|
24
|
+
The Agent (e.g., `AiAgent2`) builds the complete dependency union (`AllToolsCapabilities`) and fulfills all capabilities. When the handler is invoked, TypeScript validates that the provided context matches the intersection of capabilities requested by that tool.
|
|
25
25
|
|
|
26
26
|
## Authoring a New Tool
|
|
27
27
|
|
|
@@ -84,7 +84,7 @@ export interface LighthouseCapability {
|
|
|
84
84
|
* Unified context interface providing all capabilities available in the project.
|
|
85
85
|
* Used by the agent to pass a complete context to any tool type-safely.
|
|
86
86
|
*/
|
|
87
|
-
export type
|
|
87
|
+
export type AllToolsCapabilities = BaseToolCapability&PageExecutionCapability&StyleMutationCapability&TargetCapability&
|
|
88
88
|
OriginLockCapability&LighthouseCapability;
|
|
89
89
|
|
|
90
90
|
/**
|
|
@@ -8,7 +8,7 @@ import {GetNetworkRequestDetailsTool} from './GetNetworkRequestDetails.js';
|
|
|
8
8
|
import {GetStylesTool} from './GetStyles.js';
|
|
9
9
|
import {ListNetworkRequestsTool} from './ListNetworkRequests.js';
|
|
10
10
|
import {ResolveLighthousePathTool} from './ResolveLighthousePath.js';
|
|
11
|
-
import {type
|
|
11
|
+
import {type AllToolsCapabilities, type Tool, type ToolArgs, ToolName} from './Tool.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Plain object registry containing concrete instantiated tools.
|
|
@@ -45,16 +45,16 @@ export class ToolRegistry {
|
|
|
45
45
|
* @param name The string name of the tool to retrieve, used when the tool name is only known at runtime.
|
|
46
46
|
* @returns The generic Tool interface, or undefined if not found.
|
|
47
47
|
*/
|
|
48
|
-
static get(name: string): Tool<ToolArgs, unknown,
|
|
49
|
-
static get(name: string): Tool<ToolArgs, unknown,
|
|
48
|
+
static get(name: string): Tool<ToolArgs, unknown, AllToolsCapabilities>|undefined;
|
|
49
|
+
static get(name: string): Tool<ToolArgs, unknown, AllToolsCapabilities>|undefined {
|
|
50
50
|
// We use a double assertion (`as unknown as Tool<...>`) here. TypeScript's variance
|
|
51
51
|
// rules prevent direct casting from specific concrete tools (which have narrowed,
|
|
52
|
-
// capability-specific contexts) to the generic `Tool` signature that uses `
|
|
52
|
+
// capability-specific contexts) to the generic `Tool` signature that uses `AllToolsCapabilities`.
|
|
53
53
|
// This cast is runtime-safe because any capability requested by a specific tool is
|
|
54
|
-
// guaranteed to be satisfied by `
|
|
54
|
+
// guaranteed to be satisfied by `AllToolsCapabilities`, and the handler will only access
|
|
55
55
|
// the capabilities it expects.
|
|
56
56
|
return Object.prototype.hasOwnProperty.call(TOOLS, name) ?
|
|
57
|
-
TOOLS[name as keyof typeof TOOLS] as unknown as Tool<ToolArgs, unknown,
|
|
57
|
+
TOOLS[name as keyof typeof TOOLS] as unknown as Tool<ToolArgs, unknown, AllToolsCapabilities>:
|
|
58
58
|
undefined;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -303,3 +303,17 @@ export interface DominatorNode {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
export type DominatorChain = DominatorNode[];
|
|
306
|
+
|
|
307
|
+
export interface DuplicateStringGroup {
|
|
308
|
+
value: string;
|
|
309
|
+
count: number;
|
|
310
|
+
totalSelfSize: number;
|
|
311
|
+
totalRetainedSize: number;
|
|
312
|
+
nodes: Array<{
|
|
313
|
+
id: number,
|
|
314
|
+
selfSize: number,
|
|
315
|
+
retainedSize: number,
|
|
316
|
+
distance: number,
|
|
317
|
+
}>;
|
|
318
|
+
truncated?: boolean;
|
|
319
|
+
}
|
|
@@ -272,6 +272,10 @@ export class HeapSnapshotProxy extends HeapSnapshotProxyObject {
|
|
|
272
272
|
return this.callMethodPromise('aggregatesWithFilter', filter);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
getDuplicateStrings(): Promise<HeapSnapshotModel.DuplicateStringGroup[]> {
|
|
276
|
+
return this.callMethodPromise('getDuplicateStrings');
|
|
277
|
+
}
|
|
278
|
+
|
|
275
279
|
aggregatesForDiff(interfaceDefinitions: string): Promise<Record<string, HeapSnapshotModel.AggregateForDiff>> {
|
|
276
280
|
return this.callMethodPromise('aggregatesForDiff', interfaceDefinitions);
|
|
277
281
|
}
|
|
@@ -8402,6 +8402,10 @@ export const NativeFunctions = [
|
|
|
8402
8402
|
name: "getPathData",
|
|
8403
8403
|
signatures: [["?settings"]]
|
|
8404
8404
|
},
|
|
8405
|
+
{
|
|
8406
|
+
name: "setPathData",
|
|
8407
|
+
signatures: [["pathData"]]
|
|
8408
|
+
},
|
|
8405
8409
|
{
|
|
8406
8410
|
name: "ByteLengthQueuingStrategy",
|
|
8407
8411
|
signatures: [["init"]]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as i18n from '../../core/i18n/i18n.js';
|
|
6
|
+
import type * as Protocol from '../../generated/protocol.js';
|
|
7
|
+
|
|
8
|
+
const UIStrings = {
|
|
9
|
+
/**
|
|
10
|
+
* @description Summary line in a tooltip explaining a CSS selector specificity.
|
|
11
|
+
* @example {1} PH1
|
|
12
|
+
* @example {2} PH2
|
|
13
|
+
* @example {3} PH3
|
|
14
|
+
*/
|
|
15
|
+
specificity: 'Specificity: ({PH1},{PH2},{PH3})',
|
|
16
|
+
/**
|
|
17
|
+
* @description Tooltip line listing the selector parts contributing to the ID-like specificity bucket.
|
|
18
|
+
* @example {#main} PH1
|
|
19
|
+
*/
|
|
20
|
+
idLikeSpecificity: '(a) ID-like: {PH1}',
|
|
21
|
+
/**
|
|
22
|
+
* @description Tooltip line listing the selector parts contributing to the class-like specificity bucket.
|
|
23
|
+
* @example {.active, :hover} PH1
|
|
24
|
+
*/
|
|
25
|
+
classLikeSpecificity: '(b) Class-like: {PH1}',
|
|
26
|
+
/**
|
|
27
|
+
* @description Tooltip line listing the selector parts contributing to the type-like specificity bucket.
|
|
28
|
+
* @example {div} PH1
|
|
29
|
+
*/
|
|
30
|
+
typeLikeSpecificity: '(c) Type-like: {PH1}',
|
|
31
|
+
} as const;
|
|
32
|
+
const str_ = i18n.i18n.registerUIStrings('panels/elements/CSSSpecificityBreakdown.ts', UIStrings);
|
|
33
|
+
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
34
|
+
|
|
35
|
+
export interface SpecificityBreakdown {
|
|
36
|
+
ids: string[];
|
|
37
|
+
classes: string[];
|
|
38
|
+
types: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function formatContribution(text: string, contribution: number): string {
|
|
42
|
+
return contribution > 1 ? `${text} x${contribution}` : text;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatComponentList(components: string[]): string {
|
|
46
|
+
const formatter = new Intl.ListFormat(i18n.DevToolsLocale.DevToolsLocale.instance().locale, {
|
|
47
|
+
style: 'long',
|
|
48
|
+
type: 'conjunction',
|
|
49
|
+
});
|
|
50
|
+
return formatter.format(components);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getSpecificityBreakdown(specificity: Protocol.CSS.Specificity): SpecificityBreakdown {
|
|
54
|
+
const ids: string[] = [];
|
|
55
|
+
const classes: string[] = [];
|
|
56
|
+
const types: string[] = [];
|
|
57
|
+
|
|
58
|
+
for (const component of specificity.components ?? []) {
|
|
59
|
+
if (component.a > 0) {
|
|
60
|
+
ids.push(formatContribution(component.text, component.a));
|
|
61
|
+
}
|
|
62
|
+
if (component.b > 0) {
|
|
63
|
+
classes.push(formatContribution(component.text, component.b));
|
|
64
|
+
}
|
|
65
|
+
if (component.c > 0) {
|
|
66
|
+
types.push(formatContribution(component.text, component.c));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {ids, classes, types};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Formats the specificity breakdown into a human-readable multi-line string
|
|
75
|
+
* suitable for displaying in a tooltip.
|
|
76
|
+
*
|
|
77
|
+
* Example output for selector "div#main .active:hover":
|
|
78
|
+
* Specificity: (1,2,1)
|
|
79
|
+
* (a) ID-like: #main
|
|
80
|
+
* (b) Class-like: .active, :hover
|
|
81
|
+
* (c) Type-like: div
|
|
82
|
+
*/
|
|
83
|
+
export function formatSpecificitySummary(specificity: Protocol.CSS.Specificity): string {
|
|
84
|
+
return i18nString(UIStrings.specificity, {PH1: specificity.a, PH2: specificity.b, PH3: specificity.c});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getSpecificityBreakdownLines(specificity: Protocol.CSS.Specificity): string[] {
|
|
88
|
+
const breakdown = getSpecificityBreakdown(specificity);
|
|
89
|
+
const lines: string[] = [];
|
|
90
|
+
|
|
91
|
+
if (breakdown.ids.length > 0) {
|
|
92
|
+
lines.push(i18nString(UIStrings.idLikeSpecificity, {PH1: formatComponentList(breakdown.ids)}));
|
|
93
|
+
}
|
|
94
|
+
if (breakdown.classes.length > 0) {
|
|
95
|
+
lines.push(i18nString(UIStrings.classLikeSpecificity, {PH1: formatComponentList(breakdown.classes)}));
|
|
96
|
+
}
|
|
97
|
+
if (breakdown.types.length > 0) {
|
|
98
|
+
lines.push(i18nString(UIStrings.typeLikeSpecificity, {PH1: formatComponentList(breakdown.types)}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return lines;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function formatSpecificityTooltip(specificity: Protocol.CSS.Specificity): string {
|
|
105
|
+
return [formatSpecificitySummary(specificity), ...getSpecificityBreakdownLines(specificity)].join('\n');
|
|
106
|
+
}
|
|
@@ -54,9 +54,15 @@ import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
|
54
54
|
import * as PanelsCommon from '../common/common.js';
|
|
55
55
|
|
|
56
56
|
import * as ElementsComponents from './components/components.js';
|
|
57
|
+
import {formatSpecificitySummary, getSpecificityBreakdownLines} from './CSSSpecificityBreakdown.js';
|
|
57
58
|
import {ElementsPanel} from './ElementsPanel.js';
|
|
58
59
|
import stylePropertiesTreeOutlineStyles from './stylePropertiesTreeOutline.css.js';
|
|
59
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
type Context,
|
|
62
|
+
GhostStylePropertyTreeElement,
|
|
63
|
+
handleVarDefinitionActivate,
|
|
64
|
+
StylePropertyTreeElement,
|
|
65
|
+
} from './StylePropertyTreeElement.js';
|
|
60
66
|
import type {StylesContainer} from './StylesContainer.js';
|
|
61
67
|
|
|
62
68
|
const UIStrings = {
|
|
@@ -110,11 +116,6 @@ const UIStrings = {
|
|
|
110
116
|
* @description Text that is announced by the screen reader when the user focuses on an input field for editing the name of a CSS selector in the Styles panel
|
|
111
117
|
*/
|
|
112
118
|
cssSelector: '`CSS` selector',
|
|
113
|
-
/**
|
|
114
|
-
* @description Text displayed in tooltip that shows specificity information.
|
|
115
|
-
* @example {(0,0,1)} PH1
|
|
116
|
-
*/
|
|
117
|
-
specificity: 'Specificity: {PH1}',
|
|
118
119
|
/**
|
|
119
120
|
* @description Accessibility label for the button that expands a collapsed CSS rule in the Styles pane.
|
|
120
121
|
*/
|
|
@@ -898,7 +899,7 @@ export class StylePropertiesSection {
|
|
|
898
899
|
ancestorRuleElement = this.createMediaElement(rule.media[mediaIndex++]);
|
|
899
900
|
break;
|
|
900
901
|
case Protocol.CSS.CSSRuleType.ContainerRule:
|
|
901
|
-
ancestorRuleElement = this.createContainerQueryElement(rule.containerQueries[containerIndex++]);
|
|
902
|
+
ancestorRuleElement = this.createContainerQueryElement(rule.containerQueries[containerIndex++], rule.style);
|
|
902
903
|
break;
|
|
903
904
|
case Protocol.CSS.CSSRuleType.ScopeRule:
|
|
904
905
|
ancestorRuleElement = this.createScopeElement(rule.scopes[scopeIndex++]);
|
|
@@ -1019,7 +1020,8 @@ export class StylePropertiesSection {
|
|
|
1019
1020
|
return mediaQueryElement;
|
|
1020
1021
|
}
|
|
1021
1022
|
|
|
1022
|
-
protected createContainerQueryElement(containerQuery: SDK.CSSContainerQuery.CSSContainerQuery
|
|
1023
|
+
protected createContainerQueryElement(containerQuery: SDK.CSSContainerQuery.CSSContainerQuery,
|
|
1024
|
+
style?: SDK.CSSStyleDeclaration.CSSStyleDeclaration):
|
|
1023
1025
|
ElementsComponents.CSSQuery.CSSQuery|undefined {
|
|
1024
1026
|
let onQueryTextClick;
|
|
1025
1027
|
if (containerQuery.styleSheetId) {
|
|
@@ -1032,10 +1034,22 @@ export class StylePropertiesSection {
|
|
|
1032
1034
|
queryName: containerQuery.textIsConditionText ? undefined : containerQuery.name,
|
|
1033
1035
|
queryText: containerQuery.text,
|
|
1034
1036
|
onQueryTextClick,
|
|
1037
|
+
onLinkActivate: async(resolvedVariable: string|SDK.CSSMatchedStyles.CSSValueSource): Promise<void> => {
|
|
1038
|
+
handleVarDefinitionActivate(resolvedVariable, this.stylesContainer);
|
|
1039
|
+
},
|
|
1040
|
+
getPopoverContents: (variableName: string, variableValue: string|null):
|
|
1041
|
+
ElementsComponents.CSSVariableValueView.CSSVariableValueView => {
|
|
1042
|
+
return this.stylesContainer.getVariablePopoverContents(this.matchedStyles, variableName, variableValue);
|
|
1043
|
+
},
|
|
1035
1044
|
jslogContext: 'container-query',
|
|
1036
1045
|
};
|
|
1037
|
-
if (
|
|
1038
|
-
|
|
1046
|
+
if (style) {
|
|
1047
|
+
const tooltipPrefix = `container-${this.sectionTooltipIdPrefix}-${this.nestingLevel}`;
|
|
1048
|
+
void containerQuery.getContainerForNode(this.matchedStyles.node().id).then(container => {
|
|
1049
|
+
if (container) {
|
|
1050
|
+
containerQueryElement.parseStyleQueries(this.matchedStyles, style, container.containerNode, tooltipPrefix);
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1039
1053
|
void this.addContainerForContainerQuery(containerQuery);
|
|
1040
1054
|
}
|
|
1041
1055
|
return containerQueryElement;
|
|
@@ -1434,14 +1448,27 @@ export class StylePropertiesSection {
|
|
|
1434
1448
|
elementToSelectorIndex.set(span, i);
|
|
1435
1449
|
span.textContent = selectors[i].text;
|
|
1436
1450
|
if (specificityTooltipId && selector.specificity) {
|
|
1437
|
-
span.setAttribute('aria-
|
|
1438
|
-
const PH1 = `(${selector.specificity.a},${selector.specificity.b},${selector.specificity.c})`;
|
|
1451
|
+
span.setAttribute('aria-details', specificityTooltipId);
|
|
1439
1452
|
const tooltip = this.#specificityTooltips.appendChild(new Tooltips.Tooltip.Tooltip({
|
|
1440
1453
|
id: specificityTooltipId,
|
|
1441
1454
|
anchor: span,
|
|
1455
|
+
variant: 'rich',
|
|
1442
1456
|
jslogContext: 'elements.css-selector-specificity',
|
|
1443
1457
|
}));
|
|
1444
|
-
tooltip.
|
|
1458
|
+
tooltip.hoverDelay = 500;
|
|
1459
|
+
|
|
1460
|
+
const specificitySummary = formatSpecificitySummary(selector.specificity);
|
|
1461
|
+
const breakdownLines = getSpecificityBreakdownLines(selector.specificity);
|
|
1462
|
+
const tooltipContent = breakdownLines.length > 0 ? html`
|
|
1463
|
+
<details class="selector-specificity-tooltip-disclosure">
|
|
1464
|
+
<summary>${specificitySummary}</summary>
|
|
1465
|
+
<ul class="selector-specificity-tooltip-list">
|
|
1466
|
+
${breakdownLines.map(line => html`<li>${line}</li>`)}
|
|
1467
|
+
</ul>
|
|
1468
|
+
</details>` :
|
|
1469
|
+
html`
|
|
1470
|
+
<div class="selector-specificity-tooltip-summary">${specificitySummary}</div>`;
|
|
1471
|
+
render(tooltipContent, tooltip, {host: this});
|
|
1445
1472
|
}
|
|
1446
1473
|
}
|
|
1447
1474
|
}
|
|
@@ -316,8 +316,8 @@ export class CSSWideKeywordRenderer extends rendererBase(SDK.CSSPropertyParserMa
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
function handleVarDefinitionActivate(variable: string|SDK.CSSMatchedStyles.CSSValueSource,
|
|
320
|
-
|
|
319
|
+
export function handleVarDefinitionActivate(variable: string|SDK.CSSMatchedStyles.CSSValueSource,
|
|
320
|
+
stylesContainer: StylesContainer): void {
|
|
321
321
|
Host.userMetrics.actionTaken(Host.UserMetrics.Action.CustomPropertyLinkClicked);
|
|
322
322
|
Host.userMetrics.swatchActivated(Host.UserMetrics.SwatchType.VAR_LINK);
|
|
323
323
|
|
|
@@ -448,16 +448,17 @@ export class VariableNameRenderer extends rendererBase(SDK.CSSPropertyParserMatc
|
|
|
448
448
|
this.#matchedStyles = matchedStyles;
|
|
449
449
|
}
|
|
450
450
|
|
|
451
|
-
override render(match: SDK.CSSPropertyParserMatchers.VariableNameMatch,
|
|
451
|
+
override render(match: SDK.CSSPropertyParserMatchers.VariableNameMatch, _context: RenderingContext): Node[] {
|
|
452
|
+
const varSwatch = document.createElement('span');
|
|
452
453
|
if (this.#treeElement?.property.ownerStyle.parentRule instanceof SDK.CSSRule.CSSFunctionRule) {
|
|
453
|
-
|
|
454
|
+
render(html`${match.text}`, varSwatch);
|
|
455
|
+
return [varSwatch];
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
const {declaration, value: variableValue} = match.resolveVariable() ?? {};
|
|
457
459
|
const isDefined = variableValue !== undefined;
|
|
458
460
|
const onLinkActivate = (name: string): void =>
|
|
459
461
|
handleVarDefinitionActivate(declaration ?? name, this.#stylesContainer);
|
|
460
|
-
const varSwatch = document.createElement('span');
|
|
461
462
|
|
|
462
463
|
const tooltipContents =
|
|
463
464
|
this.#stylesContainer.getVariablePopoverContents(this.#matchedStyles, match.text, variableValue ?? null);
|
|
@@ -3,11 +3,17 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
/* eslint-disable @devtools/no-lit-render-outside-of-view */
|
|
5
5
|
|
|
6
|
+
import '../../../ui/components/tooltips/tooltips.js';
|
|
7
|
+
import '../../../ui/legacy/components/inline_editor/inline_editor.js';
|
|
8
|
+
|
|
9
|
+
import * as SDK from '../../../core/sdk/sdk.js';
|
|
10
|
+
import type * as InlineEditor from '../../../ui/legacy/components/inline_editor/inline_editor.js';
|
|
6
11
|
import * as UI from '../../../ui/legacy/legacy.js';
|
|
7
12
|
import * as Lit from '../../../ui/lit/lit.js';
|
|
8
13
|
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
|
9
14
|
|
|
10
15
|
import cssQueryStyles from './cssQuery.css.js';
|
|
16
|
+
import type {CSSVariableValueView} from './CSSVariableValueView.js';
|
|
11
17
|
|
|
12
18
|
const {render, html} = Lit;
|
|
13
19
|
|
|
@@ -16,36 +22,145 @@ export interface CSSQueryData {
|
|
|
16
22
|
queryName?: string;
|
|
17
23
|
queryText: string;
|
|
18
24
|
onQueryTextClick?: (event: Event) => void;
|
|
25
|
+
onLinkActivate?: (resolvedVariable: string|SDK.CSSMatchedStyles.CSSValueSource) => void;
|
|
26
|
+
getPopoverContents?: (variableName: string, variableValue: string|null) => CSSVariableValueView;
|
|
19
27
|
jslogContext: string;
|
|
20
28
|
}
|
|
21
29
|
|
|
30
|
+
interface TextSection {
|
|
31
|
+
text: string;
|
|
32
|
+
isVariable: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
export class CSSQuery extends HTMLElement {
|
|
23
36
|
readonly #shadow = this.attachShadow({mode: 'open'});
|
|
24
37
|
#queryPrefix = '';
|
|
25
38
|
#queryName?: string;
|
|
26
|
-
#queryText =
|
|
39
|
+
#queryText: TextSection[] = [];
|
|
27
40
|
#onQueryTextClick?: (event: Event) => void;
|
|
41
|
+
#onLinkActivate?: (resolvedVariable: string|SDK.CSSMatchedStyles.CSSValueSource) => void;
|
|
42
|
+
#getPopoverContents?: (variableName: string, variableValue: string|null) => CSSVariableValueView;
|
|
28
43
|
#jslogContext?: string;
|
|
44
|
+
#matchedStyles?: SDK.CSSMatchedStyles.CSSMatchedStyles;
|
|
45
|
+
#style?: SDK.CSSStyleDeclaration.CSSStyleDeclaration;
|
|
46
|
+
#containerNode?: SDK.DOMModel.DOMNode;
|
|
47
|
+
#tooltipPrefix = '';
|
|
29
48
|
|
|
30
49
|
set data(data: CSSQueryData) {
|
|
31
50
|
this.#queryPrefix = data.queryPrefix;
|
|
32
51
|
this.#queryName = data.queryName;
|
|
33
|
-
this.#queryText = data.queryText;
|
|
52
|
+
this.#queryText = [{text: data.queryText, isVariable: false}];
|
|
34
53
|
this.#onQueryTextClick = data.onQueryTextClick;
|
|
54
|
+
this.#onLinkActivate = data.onLinkActivate;
|
|
55
|
+
this.#getPopoverContents = data.getPopoverContents;
|
|
35
56
|
this.#jslogContext = data.jslogContext;
|
|
36
57
|
this.#render();
|
|
37
58
|
}
|
|
38
59
|
|
|
60
|
+
parseStyleQueries(
|
|
61
|
+
matchedStyles: SDK.CSSMatchedStyles.CSSMatchedStyles,
|
|
62
|
+
style: SDK.CSSStyleDeclaration.CSSStyleDeclaration,
|
|
63
|
+
containerNode: SDK.DOMModel.DOMNode,
|
|
64
|
+
tooltipPrefix: string,
|
|
65
|
+
): void {
|
|
66
|
+
this.#matchedStyles = matchedStyles;
|
|
67
|
+
this.#style = style;
|
|
68
|
+
this.#containerNode = containerNode;
|
|
69
|
+
this.#tooltipPrefix = tooltipPrefix;
|
|
70
|
+
|
|
71
|
+
const queryText = this.#queryText[0]?.text;
|
|
72
|
+
if (!queryText) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const prefix = 'if(';
|
|
77
|
+
const suffix = ': 1)';
|
|
78
|
+
const ast = SDK.CSSPropertyParser.tokenizeDeclaration('--query', prefix + queryText + suffix);
|
|
79
|
+
if (!ast) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const matcher = new SDK.CSSPropertyParserMatchers.VariableNameMatcher(matchedStyles, style);
|
|
84
|
+
const matchedResult = SDK.CSSPropertyParser.BottomUpTreeMatching.walk(ast, [matcher]);
|
|
85
|
+
const matchedNodes = SDK.CSSPropertyParser.TreeSearch.findAll(ast, node => {
|
|
86
|
+
return matchedResult.getMatch(node) instanceof SDK.CSSPropertyParserMatchers.VariableNameMatch;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
matchedNodes.sort((a, b) => a.from - b.from);
|
|
90
|
+
|
|
91
|
+
const sections: TextSection[] = [];
|
|
92
|
+
const valueOffset = ast.rule.indexOf(ast.propertyValue) + prefix.length;
|
|
93
|
+
let lastOffset = 0;
|
|
94
|
+
|
|
95
|
+
for (const node of matchedNodes) {
|
|
96
|
+
const start = node.from - valueOffset;
|
|
97
|
+
const end = node.to - valueOffset;
|
|
98
|
+
if (start > lastOffset) {
|
|
99
|
+
sections.push({
|
|
100
|
+
text: queryText.substring(lastOffset, start),
|
|
101
|
+
isVariable: false,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
sections.push({
|
|
105
|
+
text: queryText.substring(start, end),
|
|
106
|
+
isVariable: true,
|
|
107
|
+
});
|
|
108
|
+
lastOffset = end;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (lastOffset < queryText.length) {
|
|
112
|
+
sections.push({
|
|
113
|
+
text: queryText.substring(lastOffset),
|
|
114
|
+
isVariable: false,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.#queryText = sections;
|
|
119
|
+
this.#render();
|
|
120
|
+
}
|
|
121
|
+
|
|
39
122
|
#render(): void {
|
|
40
123
|
const queryClasses = Lit.Directives.classMap({
|
|
41
124
|
query: true,
|
|
42
125
|
editable: Boolean(this.#onQueryTextClick),
|
|
43
126
|
});
|
|
44
127
|
|
|
45
|
-
// Disabled until https://crbug.com/1079231 is fixed.
|
|
46
128
|
// clang-format off
|
|
47
129
|
const queryText = html`
|
|
48
|
-
<span class="query-text" @click=${this.#onQueryTextClick}>${
|
|
130
|
+
<span class="query-text" @click=${this.#onQueryTextClick}>${
|
|
131
|
+
this.#queryText.map((section, index) => {
|
|
132
|
+
if (section.isVariable && this.#matchedStyles && this.#style && this.#onLinkActivate) {
|
|
133
|
+
const variableName = section.text;
|
|
134
|
+
const variable = this.#matchedStyles.computeCSSVariable(this.#style, variableName, this.#containerNode);
|
|
135
|
+
const isDefined = variable !== null && variable.value !== undefined;
|
|
136
|
+
const onLinkActivate = (): void => {
|
|
137
|
+
if (this.#onLinkActivate) {
|
|
138
|
+
this.#onLinkActivate(variable ? variable.declaration : variableName);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const tooltipContents = this.#getPopoverContents?.(variableName, variable?.value ?? null) ?? null;
|
|
142
|
+
const tooltipId = `${this.#tooltipPrefix}-${index}-${variableName}`;
|
|
143
|
+
const tooltip = {tooltipId};
|
|
144
|
+
return html`
|
|
145
|
+
<devtools-link-swatch class="css-var-link" .data=${{
|
|
146
|
+
tooltip,
|
|
147
|
+
text: variableName,
|
|
148
|
+
isDefined,
|
|
149
|
+
onLinkActivate,
|
|
150
|
+
} as InlineEditor.LinkSwatch.LinkSwatchRenderData}>
|
|
151
|
+
</devtools-link-swatch>
|
|
152
|
+
<devtools-tooltip
|
|
153
|
+
id=${tooltipId}
|
|
154
|
+
variant="rich"
|
|
155
|
+
jslogContext="elements.css-var"
|
|
156
|
+
>
|
|
157
|
+
${tooltipContents}
|
|
158
|
+
</devtools-tooltip>
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
return html`${section.text}`;
|
|
162
|
+
})
|
|
163
|
+
}</span>
|
|
49
164
|
`;
|
|
50
165
|
|
|
51
166
|
render(html`
|