chrome-devtools-frontend
Version:
Chrome DevTools UI
119 lines (104 loc) • 5.49 kB
text/typescript
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/no-imperative-dom-api */
import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as ElementsComponents from './components/components.js';
import type {ElementsTreeElement} from './ElementsTreeElement.js';
import * as ElementsTreeOutline from './ElementsTreeOutline.js';
const UIStrings = {
/**
*@description Link text content in Elements Tree Outline of the Elements panel. When clicked, it "reveals" the true location of an element.
*/
reveal: 'reveal',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/elements/TopLayerContainer.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class TopLayerContainer extends UI.TreeOutline.TreeElement {
tree: ElementsTreeOutline.ElementsTreeOutline;
document: SDK.DOMModel.DOMDocument;
currentTopLayerDOMNodes = new Set<SDK.DOMModel.DOMNode>();
topLayerUpdateThrottler: Common.Throttler.Throttler;
constructor(tree: ElementsTreeOutline.ElementsTreeOutline, document: SDK.DOMModel.DOMDocument) {
super('#top-layer');
this.tree = tree;
this.document = document;
this.topLayerUpdateThrottler = new Common.Throttler.Throttler(1);
}
async throttledUpdateTopLayerElements(): Promise<void> {
await this.topLayerUpdateThrottler.schedule(() => this.updateTopLayerElements());
}
async updateTopLayerElements(): Promise<void> {
this.removeChildren();
this.removeCurrentTopLayerElementsAdorners();
this.currentTopLayerDOMNodes = new Set();
const domModel = this.document.domModel();
const newTopLayerElementsIDs = await domModel.getTopLayerElements();
if (!newTopLayerElementsIDs || newTopLayerElementsIDs.length === 0) {
return;
}
let topLayerElementIndex = 0;
for (let i = 0; i < newTopLayerElementsIDs.length; i++) {
const topLayerDOMNode = domModel.idToDOMNode.get(newTopLayerElementsIDs[i]);
if (!topLayerDOMNode || topLayerDOMNode.ownerDocument !== this.document) {
continue;
}
if (topLayerDOMNode.nodeName() !== '::backdrop') {
const topLayerElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
domModel.target(), topLayerDOMNode.backendNodeId(), 0, topLayerDOMNode.nodeName());
const topLayerElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(topLayerElementShortcut);
this.appendChild(topLayerElementRepresentation);
this.currentTopLayerDOMNodes.add(topLayerDOMNode);
// Add the element's backdrop if previous top layer element is a backdrop.
const previousTopLayerDOMNode = (i > 0) ? domModel.idToDOMNode.get(newTopLayerElementsIDs[i - 1]) : undefined;
if (previousTopLayerDOMNode && previousTopLayerDOMNode.nodeName() === '::backdrop') {
const backdropElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
domModel.target(), previousTopLayerDOMNode.backendNodeId(), 0, previousTopLayerDOMNode.nodeName());
const backdropElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(backdropElementShortcut);
topLayerElementRepresentation.appendChild(backdropElementRepresentation);
}
// TODO(changhaohan): store not-yet-inserted DOMNodes and adorn them when inserted.
const topLayerTreeElement = this.tree.treeElementByNode.get(topLayerDOMNode);
if (topLayerTreeElement) {
this.addTopLayerAdorner(topLayerTreeElement, topLayerElementRepresentation, ++topLayerElementIndex);
}
}
}
}
private removeCurrentTopLayerElementsAdorners(): void {
for (const node of this.currentTopLayerDOMNodes) {
const topLayerTreeElement = this.tree.treeElementByNode.get(node);
topLayerTreeElement?.removeAdornersByType(ElementsComponents.AdornerManager.RegisteredAdorners.TOP_LAYER);
}
}
private addTopLayerAdorner(
element: ElementsTreeElement, topLayerElementRepresentation: ElementsTreeOutline.ShortcutTreeElement,
topLayerElementIndex: number): void {
const config = ElementsComponents.AdornerManager.getRegisteredAdorner(
ElementsComponents.AdornerManager.RegisteredAdorners.TOP_LAYER);
const adornerContent = document.createElement('span');
adornerContent.classList.add('adorner-with-icon');
const linkIcon = IconButton.Icon.create('select-element');
const adornerText = document.createElement('span');
adornerText.textContent = `top-layer (${topLayerElementIndex})`;
adornerContent.append(linkIcon);
adornerContent.append(adornerText);
const adorner = element?.adorn(config, adornerContent);
if (adorner) {
const onClick = ((() => {
topLayerElementRepresentation.revealAndSelect();
}) as EventListener);
adorner.addInteraction(onClick, {
isToggle: false,
shouldPropagateOnKeydown: false,
ariaLabelDefault: i18nString(UIStrings.reveal),
ariaLabelActive: i18nString(UIStrings.reveal),
});
adorner.addEventListener('mousedown', e => e.consume(), false);
}
}
}