blob: fd4da7a20bece7c97cde039dfed74eb644a792c7 [file] [log] [blame]
// 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 SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import {renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {createTarget} from '../../testing/EnvironmentHelpers.js';
import {describeWithMockConnection} from '../../testing/MockConnection.js';
import * as Elements from './elements.js';
describeWithMockConnection('AdoptedStyleSheetTreeElement highlighting', () => {
let domModel: SDK.DOMModel.DOMModel;
let treeOutline: Elements.ElementsTreeOutline.ElementsTreeOutline;
let containerNode: SDK.DOMModel.DOMNode;
let shadowRootNode: SDK.DOMModel.DOMNode;
let shadowRootTreeElement: Elements.ElementsTreeElement.ElementsTreeElement;
const sheetId = 'sheet-id' as Protocol.DOM.StyleSheetId;
beforeEach(async () => {
const target = createTarget();
domModel = target.model(SDK.DOMModel.DOMModel)!;
const containerPayload = {
nodeId: 1 as Protocol.DOM.NodeId,
backendNodeId: 2 as Protocol.DOM.BackendNodeId,
nodeType: Node.ELEMENT_NODE,
nodeName: 'DIV',
localName: 'div',
nodeValue: '',
attributes: [],
childNodeCount: 0,
shadowRoots: [{
nodeId: 3 as Protocol.DOM.NodeId,
backendNodeId: 4 as Protocol.DOM.BackendNodeId,
parentId: 1 as Protocol.DOM.NodeId,
nodeType: Node.DOCUMENT_FRAGMENT_NODE,
shadowRootType: Protocol.DOM.ShadowRootType.Open,
adoptedStyleSheets: [sheetId],
nodeName: 'SHADOW-ROOT',
localName: '#shadow-root',
nodeValue: '',
}],
};
containerNode = SDK.DOMModel.DOMNode.create(domModel, null, false, containerPayload);
shadowRootNode = containerNode.shadowRoots()![0];
treeOutline = new Elements.ElementsTreeOutline.ElementsTreeOutline();
treeOutline.wireToDOMModel(domModel);
const containerTreeElement = new Elements.ElementsTreeElement.ElementsTreeElement(containerNode);
shadowRootTreeElement = new Elements.ElementsTreeElement.ElementsTreeElement(shadowRootNode);
containerTreeElement.appendChild(shadowRootTreeElement);
treeOutline.setVisible(true);
renderElementIntoDOM(treeOutline.element);
containerTreeElement.expand();
});
afterEach(() => {
treeOutline.removeChildren();
treeOutline.setVisible(false);
});
it('edits an adopted style sheet', async () => {
const adoptedSheet = shadowRootNode.adoptedStyleSheetsForNode[0];
const initialCSS = '/* comment */';
let resolveWaitForText: (value: unknown) => void;
const waitForText = new Promise(resolve => {
resolveWaitForText = resolve;
})
.then(() => {})
.then(() => {});
const getStyleSheetTextStub = sinon.stub(adoptedSheet.cssModel, 'getStyleSheetText');
getStyleSheetTextStub.onFirstCall().resolves(initialCSS);
getStyleSheetTextStub.onSecondCall().callsFake(() => new Promise(resolve => {
resolve(initialCSS);
resolveWaitForText(null);
}));
adoptedSheet.cssModel.styleSheetAdded({
styleSheetId: sheetId,
frameId: '' as Protocol.Page.FrameId,
sourceURL: '',
title: '',
origin: Protocol.CSS.StyleSheetOrigin.Regular,
disabled: false,
isInline: false,
isMutable: true,
isConstructed: true,
startLine: 0,
startColumn: 0,
endLine: 0,
endColumn: initialCSS.length,
length: initialCSS.length,
loadingFailed: false,
});
const adoptedStyleSheetSetTreeElement =
new Elements.AdoptedStyleSheetTreeElement.AdoptedStyleSheetSetTreeElement([adoptedSheet]);
shadowRootTreeElement.appendChild(adoptedStyleSheetSetTreeElement);
await shadowRootTreeElement.onpopulate();
shadowRootTreeElement.expand();
assert.strictEqual(shadowRootTreeElement.childCount(), 1);
assert.strictEqual(shadowRootTreeElement.children()[0], adoptedStyleSheetSetTreeElement);
await adoptedStyleSheetSetTreeElement.onpopulate();
adoptedStyleSheetSetTreeElement.expand();
assert.strictEqual(adoptedStyleSheetSetTreeElement.childCount(), 1);
const adoptedStyleSheetTreeElement = adoptedStyleSheetSetTreeElement.children()[0] as
Elements.AdoptedStyleSheetTreeElement.AdoptedStyleSheetTreeElement;
await adoptedStyleSheetTreeElement.onpopulate();
adoptedStyleSheetTreeElement.expand();
assert.strictEqual(adoptedStyleSheetTreeElement.childCount(), 1);
const adoptedStyleSheetContentsTreeElement = adoptedStyleSheetTreeElement.children()[0] as
Elements.AdoptedStyleSheetTreeElement.AdoptedStyleSheetContentsTreeElement;
const setTextStub = sinon.stub(adoptedSheet.cssModel, 'setStyleSheetText').resolves(null);
await adoptedStyleSheetContentsTreeElement.onpopulate();
const textElementDOM =
adoptedStyleSheetContentsTreeElement.listItemElement.querySelector('.webkit-html-text-node') as HTMLElement;
assert.exists(textElementDOM);
// Start editing by calling ondblclick
const event = new MouseEvent('dblclick', {bubbles: true, cancelable: true});
Object.defineProperty(event, 'target', {value: textElementDOM});
assert.isFalse(adoptedStyleSheetContentsTreeElement.ondblclick(event));
await waitForText;
sinon.assert.calledTwice(getStyleSheetTextStub);
assert.isTrue(adoptedStyleSheetContentsTreeElement.isEditing());
assert.strictEqual(textElementDOM.textContent, initialCSS);
// The inplace editor is now active on textElementDOM.
const newCSS = '.foo { color: red; }';
textElementDOM.textContent = newCSS;
// The commit is triggered by blur or enter.
textElementDOM.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
assert.isFalse(adoptedStyleSheetContentsTreeElement.isEditing());
sinon.assert.calledOnce(setTextStub);
sinon.assert.calledWith(setTextStub, sheetId, newCSS, false);
});
});