// Copyright 2025 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import type * as Common from '../../core/common/common.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as TextUtils from '../../models/text_utils/text_utils.js'; import * as CodeHighlighter from '../../ui/components/code_highlighter/code_highlighter.js'; import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import {PanelUtils} from '../utils/utils.js'; import type {EditorHandles} from './ElementsTreeElement.js'; export class AdoptedStyleSheetSetTreeElement extends UI.TreeOutline.TreeElement { constructor(readonly adoptedStyleSheets: SDK.DOMModel.AdoptedStyleSheet[]) { super(''); const documentElement = this.listItemElement.createChild('span'); UI.UIUtils.createTextChild(documentElement, '#adopted-style-sheets'); for (const adoptedStyleSheet of adoptedStyleSheets) { this.appendChild(new AdoptedStyleSheetTreeElement(adoptedStyleSheet)); } } } export class AdoptedStyleSheetTreeElement extends UI.TreeOutline.TreeElement { private eventListener: Common.EventTarget.EventDescriptor|null = null; constructor(readonly adoptedStyleSheet: SDK.DOMModel.AdoptedStyleSheet) { super(''); const header = adoptedStyleSheet.cssModel.styleSheetHeaderForId(adoptedStyleSheet.id); if (header) { AdoptedStyleSheetTreeElement.createContents(header, this); } else { this.eventListener = adoptedStyleSheet.cssModel.addEventListener( SDK.CSSModel.Events.StyleSheetAdded, this.onStyleSheetAdded, this); } } onStyleSheetAdded({data: header}: Common.EventTarget.EventTargetEvent): void { if (header.id === this.adoptedStyleSheet.id) { AdoptedStyleSheetTreeElement.createContents(header, this); this.adoptedStyleSheet.cssModel.removeEventListener( SDK.CSSModel.Events.StyleSheetAdded, this.onStyleSheetAdded, this); this.eventListener = null; } } static createContents(header: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader, treeElement: UI.TreeOutline.TreeElement): void { const documentElement = treeElement.listItemElement.createChild('span'); const linkText = header.sourceURL; UI.UIUtils.createTextChild(documentElement, '#adopted-style-sheet' + (linkText ? ' (' : '')); if (linkText) { documentElement.appendChild(Components.Linkifier.Linkifier.linkifyURL(linkText, { text: linkText, preventClick: true, showColumnNumber: false, inlineFrameIndex: 0, })); UI.UIUtils.createTextChild(documentElement, ')'); } treeElement.appendChild(new AdoptedStyleSheetContentsTreeElement(header)); } highlight(): void { PanelUtils.highlightElement(this.listItemElement); } } export class AdoptedStyleSheetContentsTreeElement extends UI.TreeOutline.TreeElement { private editing: EditorHandles|null = null; constructor(private readonly styleSheetHeader: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader) { super(''); } override onbind(): void { this.styleSheetHeader.cssModel().addEventListener( SDK.CSSModel.Events.StyleSheetChanged, this.onStyleSheetChanged, this); void this.onpopulate(); } override onunbind(): void { if (this.editing) { this.editing.cancel(); } this.styleSheetHeader.cssModel().removeEventListener( SDK.CSSModel.Events.StyleSheetChanged, this.onStyleSheetChanged, this); } override async onpopulate(): Promise { const data = await this.styleSheetHeader.requestContentData(); if (!TextUtils.ContentData.ContentData.isError(data) && data.isTextContent) { this.listItemElement.removeChildren(); const newNode = this.listItemElement.createChild('span', 'webkit-html-text-node webkit-html-css-node'); newNode.setAttribute('jslog', `${VisualLogging.value('css-text-node').track({change: true, dblclick: true})}`); const text = data.text; newNode.textContent = text.replace(/^[\n\r]+|\s+$/g, ''); void CodeHighlighter.CodeHighlighter.highlightNode(newNode, 'text/css'); } } onStyleSheetChanged({data: {styleSheetId}}: Common.EventTarget.EventTargetEvent): void { if (styleSheetId === this.styleSheetHeader.id) { void this.onpopulate(); } } override ondblclick(event: Event): boolean { if (this.editing) { return false; } void this.startEditing(event.target as Element); return false; } override onenter(): boolean { if (this.editing) { return false; } const target = this.listItemElement.querySelector('.webkit-html-text-node'); if (target) { void this.startEditing(target); return true; } return false; } private async startEditing(target: Element): Promise { if (this.editing || UI.UIUtils.isBeingEdited(target)) { return; } const textNode = target.enclosingNodeOrSelfWithClass('webkit-html-text-node'); if (!textNode) { return; } const data = await this.styleSheetHeader.requestContentData(); textNode.textContent = (TextUtils.ContentData.ContentData.isError(data) || !data.isTextContent) ? '' : data.text; const config = new UI.InplaceEditor.Config(this.editingCommitted.bind(this), () => this.editingCancelled(), undefined); const editorHandles = UI.InplaceEditor.InplaceEditor.startEditing(textNode, config); if (!editorHandles) { return; } this.editing = { commit: editorHandles.commit, cancel: editorHandles.cancel, editor: undefined, resize: () => {}, }; const componentSelection = this.listItemElement.getComponentSelection(); componentSelection?.selectAllChildren(textNode); } private async editingCommitted(element: Element, newText: string, oldText: string|null): Promise { this.editing = null; if (newText !== oldText) { await this.styleSheetHeader.cssModel().setStyleSheetText(this.styleSheetHeader.id, newText, false); } this.editingCancelled(); } private editingCancelled(): void { this.editing = null; void this.onpopulate(); } isEditing(): boolean { return this.editing !== null; } }