chrome-devtools-frontend
Version:
Chrome DevTools UI
96 lines (79 loc) • 3.06 kB
text/typescript
// 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.
/**
* @file This file includes DOM helpers and page-evaluated serialization helpers
* used by AI assistance that require DOM types. They are isolated in a separate
* target so DOM types do not leak into the rest of models/ai_assistance.
*/
import * as Platform from '../../core/platform/platform.js';
export interface GetErrorStackOutput {
message: string;
stack?: string;
}
/* istanbul ignore next */
export function getErrorStackOnThePage(this: Error): GetErrorStackOutput {
// Using this.stack causes side effect checks to throw.
return {stack: '', message: this.message};
}
/* istanbul ignore next */
export function stringifyObjectOnThePage(this: unknown): string {
const seenBefore = new Map();
return JSON.stringify(this, function replacer(this: unknown, key: string, value: unknown) {
if (typeof value === 'object' && value !== null) {
if (seenBefore.has(value)) {
return '(cycle)';
}
seenBefore.set(value, true);
}
if (value instanceof HTMLElement) {
const idAttribute = value.id ? ` id="${value.id}"` : '';
const classAttribute = value.classList.value ? ` class="${value.classList.value}"` : '';
return `<${value.nodeName.toLowerCase()}${idAttribute}${classAttribute}>${value.hasChildNodes() ? '...' : ''}</${
value.nodeName.toLowerCase()}>`;
}
if (this instanceof CSSStyleDeclaration) {
// Do not add number keys to the output.
if (!isNaN(Number(key))) {
return undefined;
}
}
return value;
});
}
export async function sanitizeStyleChanges(selector: string,
styles: Record<string, string>): Promise<Record<string, string>> {
const cssStyleValue: string[] = [];
const changedStyles: string[] = [];
const kebabStyles = Platform.StringUtilities.toKebabCaseKeys(styles);
for (const [style, value] of Object.entries(kebabStyles)) {
// Build up the CSS style
cssStyleValue.push(`${style}: ${value};`);
// Keep track of what style changed to query later.
changedStyles.push(style);
}
if (typeof CSSStyleSheet === 'undefined' || typeof CSSStyleRule === 'undefined') {
return kebabStyles;
}
const styleSheet = new CSSStyleSheet({disabled: true});
// Build up the CSS stylesheet value.
await styleSheet.replace(`${selector} { ${cssStyleValue.join(' ')} }`);
const sanitizedStyles: Record<string, string> = {};
for (const cssRule of styleSheet.cssRules) {
if (!(cssRule instanceof CSSStyleRule)) {
continue;
}
for (const style of changedStyles) {
const value = cssRule.style.getPropertyValue(style);
if (value) {
sanitizedStyles[style] = value;
}
}
}
return sanitizedStyles;
}
export function dispatchAiAssistanceDoneEvent(): void {
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('aiassistancedone'));
}
}