UNPKG

chrome-devtools-frontend

Version:
149 lines (137 loc) 5.56 kB
// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Host from '../../../core/host/host.js'; import * as SDK from '../../../core/sdk/sdk.js'; import type * as Protocol from '../../../generated/protocol.js'; import type {ComputedStyleAiWidget, FunctionCallHandlerResult, FunctionHandlerOptions} from '../agents/AiAgent.js'; import {DOMNodeContext} from '../contexts/DOMNodeContext.js'; import {debugLog} from '../debug.js'; import { type BaseToolCapability, type OriginLockCapability, type TargetCapability, type Tool, type ToolArgs, ToolName, } from './Tool.js'; export interface GetStylesArgs extends ToolArgs { elements: number[]; styleProperties: string[]; explanation: string; } export class GetStylesTool implements Tool<GetStylesArgs, unknown, BaseToolCapability&TargetCapability&OriginLockCapability> { readonly name = ToolName.GET_STYLES; readonly description = `Get computed and source styles for one or multiple elements on the inspected page for multiple elements at once by uid. **CRITICAL** An element uid is a number, not a selector. **CRITICAL** Use selectors to refer to elements in the text output. Do not use uids. **CRITICAL** Always provide the explanation argument to explain what and why you query. **CRITICAL** You MUST provide a specific list of CSS property names. Do not use generic values like "all" or "*".`; readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetStylesArgs> = { type: Host.AidaClient.ParametersTypes.OBJECT, description: '', nullable: false, properties: { explanation: { type: Host.AidaClient.ParametersTypes.STRING, description: 'Explain why you want to get styles', nullable: false, }, elements: { type: Host.AidaClient.ParametersTypes.ARRAY, description: 'A list of element uids to get data for. These are numbers, not selectors.', items: {type: Host.AidaClient.ParametersTypes.INTEGER, description: 'An element uid.'}, nullable: false, }, styleProperties: { type: Host.AidaClient.ParametersTypes.ARRAY, description: 'One or more specific CSS style property names to fetch. Generic values like "all" or "*" are not supported.', nullable: false, items: { type: Host.AidaClient.ParametersTypes.STRING, description: 'A CSS style property name to retrieve. For example, \'background-color\'.' } }, }, required: ['explanation', 'elements', 'styleProperties'] }; displayInfoFromArgs(params: GetStylesArgs): { title: string, thought: string, action: string, } { return { title: 'Reading computed and source styles', thought: params.explanation, action: `getStyles(${JSON.stringify(params.elements)}, ${JSON.stringify(params.styleProperties)})`, }; } async handler( params: GetStylesArgs, context: BaseToolCapability&TargetCapability&OriginLockCapability, _options?: FunctionHandlerOptions, ): Promise<FunctionCallHandlerResult<unknown>> { const widgets: ComputedStyleAiWidget[] = []; const result: Record<string, {computed: Record<string, string|undefined>, authored: Record<string, string|undefined>}> = {}; const target = context.getTarget(); if (!target) { return {error: 'Error: Could not find the inspected page.'}; } const establishedOrigin = context.getEstablishedOrigin(); if (!establishedOrigin) { return {error: 'Error: Origin lock is not established.'}; } for (const uid of params.elements) { result[uid] = {computed: {}, authored: {}}; debugLog(`Action to execute: uid=${uid}`); const node = new SDK.DOMModel.DeferredDOMNode(target, uid as Protocol.DOM.BackendNodeId); const resolved = await node.resolvePromise(); if (!resolved) { return {error: 'Error: Could not find the element with uid=' + uid}; } const newContext = new DOMNodeContext(resolved); if (establishedOrigin !== newContext.getOrigin()) { return {error: 'Error: Node does not belong to the current origin.'}; } const styles = await resolved.domModel().cssModel().getComputedStyle(resolved.id); if (!styles) { return {error: 'Error: Could not get computed styles.'}; } const matchedStyles = await resolved.domModel().cssModel().getMatchedStyles(resolved.id); if (!matchedStyles) { return {error: 'Error: Could not get authored styles.'}; } widgets.push({ name: 'COMPUTED_STYLES', data: { computedStyles: styles, backendNodeId: node.backendNodeId(), matchedCascade: matchedStyles, properties: params.styleProperties, } }); for (const prop of params.styleProperties) { result[uid].computed[prop] = styles.get(prop); } for (const style of matchedStyles.nodeStyles()) { for (const property of style.allProperties()) { if (!params.styleProperties.includes(property.name)) { continue; } const state = matchedStyles.propertyState(property); if (state === SDK.CSSMatchedStyles.PropertyState.ACTIVE) { result[uid].authored[property.name] = property.value; } } } } return { result: JSON.stringify(result, null, 2), widgets, }; } }