blob: bf3071e077bb669b0e5751e5040e288d5803900e [file] [log] [blame]
// 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<SDK.CSSStyleSheetHeader.CSSStyleSheetHeader>):
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<void> {
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<SDK.CSSModel.StyleSheetChangedEvent>):
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<void> {
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<void> {
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;
}
}