| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /* eslint-disable @devtools/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 UI from '../../ui/legacy/legacy.js'; |
| import * as LayerViewer from '../layer_viewer/layer_viewer.js'; |
| |
| import {LayerPaintProfilerView} from './LayerPaintProfilerView.js'; |
| import {Events, LayerTreeModel} from './LayerTreeModel.js'; |
| |
| const UIStrings = { |
| /** |
| * @description Title of the details tab in the Layers panel. |
| */ |
| details: 'Details', |
| /** |
| * @description Title of the profiler tab in the Layers panel. |
| */ |
| profiler: 'Profiler', |
| } as const; |
| const str_ = i18n.i18n.registerUIStrings('panels/layers/LayersPanel.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| let layersPanelInstance: LayersPanel; |
| |
| export class LayersPanel extends UI.Panel.PanelWithSidebar implements SDK.TargetManager.Observer { |
| private model: LayerTreeModel|null; |
| private readonly layerViewHost: LayerViewer.LayerViewHost.LayerViewHost; |
| private readonly layerTreeOutline: LayerViewer.LayerTreeOutline.LayerTreeOutline; |
| private readonly rightSplitWidget: UI.SplitWidget.SplitWidget; |
| readonly layers3DView: LayerViewer.Layers3DView.Layers3DView; |
| private tabbedPane: UI.TabbedPane.TabbedPane; |
| private readonly layerDetailsView: LayerViewer.LayerDetailsView.LayerDetailsView; |
| private readonly paintProfilerView: LayerPaintProfilerView; |
| private readonly updateThrottler: Common.Throttler.Throttler; |
| private layerBeingProfiled?: SDK.LayerTreeBase.Layer|null; |
| constructor() { |
| super('layers', 225); |
| this.model = null; |
| |
| SDK.TargetManager.TargetManager.instance().observeTargets(this, {scoped: true}); |
| this.layerViewHost = new LayerViewer.LayerViewHost.LayerViewHost(); |
| this.layerTreeOutline = new LayerViewer.LayerTreeOutline.LayerTreeOutline(this.layerViewHost); |
| this.layerTreeOutline.addEventListener( |
| LayerViewer.LayerTreeOutline.Events.PAINT_PROFILER_REQUESTED, this.onPaintProfileRequested, this); |
| this.layerTreeOutline.show(this.panelSidebarElement()); |
| this.setDefaultFocusedElement(this.layerTreeOutline.element); |
| |
| this.rightSplitWidget = new UI.SplitWidget.SplitWidget(false, true, 'layer-details-split-view-state'); |
| this.splitWidget().setMainWidget(this.rightSplitWidget); |
| this.splitWidget().hideSidebar(); |
| |
| this.layers3DView = new LayerViewer.Layers3DView.Layers3DView(this.layerViewHost); |
| this.rightSplitWidget.setMainWidget(this.layers3DView); |
| this.rightSplitWidget.hideSidebar(); |
| this.layers3DView.addEventListener( |
| LayerViewer.Layers3DView.Events.PAINT_PROFILER_REQUESTED, this.onPaintProfileRequested, this); |
| this.layers3DView.addEventListener(LayerViewer.Layers3DView.Events.SCALE_CHANGED, this.onScaleChanged, this); |
| |
| this.tabbedPane = new UI.TabbedPane.TabbedPane(); |
| this.rightSplitWidget.setSidebarWidget(this.tabbedPane); |
| |
| this.layerDetailsView = new LayerViewer.LayerDetailsView.LayerDetailsView(this.layerViewHost); |
| this.layerDetailsView.addEventListener( |
| LayerViewer.LayerDetailsView.Events.PAINT_PROFILER_REQUESTED, this.onPaintProfileRequested, this); |
| this.tabbedPane.appendTab(DetailsViewTabs.Details, i18nString(UIStrings.details), this.layerDetailsView); |
| |
| this.paintProfilerView = new LayerPaintProfilerView(this.showImage.bind(this)); |
| this.tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, this.onTabClosed, this); |
| this.updateThrottler = new Common.Throttler.Throttler(100); |
| } |
| |
| static instance(opts?: {forceNew: boolean}): LayersPanel { |
| if (!layersPanelInstance || opts?.forceNew) { |
| layersPanelInstance = new LayersPanel(); |
| } |
| |
| return layersPanelInstance; |
| } |
| |
| override focus(): void { |
| this.layerTreeOutline.focus(); |
| } |
| |
| override wasShown(): void { |
| super.wasShown(); |
| if (this.model) { |
| this.model.enable(); |
| } |
| } |
| |
| override willHide(): void { |
| if (this.model) { |
| void this.model.disable(); |
| } |
| super.willHide(); |
| } |
| |
| targetAdded(target: SDK.Target.Target): void { |
| if (target !== target.outermostTarget()) { |
| return; |
| } |
| this.model = target.model(LayerTreeModel); |
| if (!this.model) { |
| return; |
| } |
| this.model.addEventListener(Events.LayerTreeChanged, this.onLayerTreeUpdated, this); |
| this.model.addEventListener(Events.LayerPainted, this.onLayerPainted, this); |
| if (this.isShowing()) { |
| this.model.enable(); |
| void this.update(); |
| } |
| } |
| |
| targetRemoved(target: SDK.Target.Target): void { |
| if (!this.model || this.model.target() !== target) { |
| return; |
| } |
| this.model.removeEventListener(Events.LayerTreeChanged, this.onLayerTreeUpdated, this); |
| this.model.removeEventListener(Events.LayerPainted, this.onLayerPainted, this); |
| void this.model.disable(); |
| this.model = null; |
| } |
| |
| private onLayerTreeUpdated(): void { |
| void this.updateThrottler.schedule(this.update.bind(this)); |
| } |
| |
| update(): void { |
| if (this.model) { |
| this.splitWidget().showBoth(); |
| this.rightSplitWidget.showBoth(); |
| this.layerViewHost.setLayerTree(this.model.layerTree()); |
| const resourceModel = this.model.target().model(SDK.ResourceTreeModel.ResourceTreeModel); |
| if (resourceModel) { |
| const mainFrame = resourceModel.mainFrame; |
| if (mainFrame) { |
| const url = mainFrame.url; |
| // Add the currently visualized url as an attribute to make it accessible to e2e tests |
| this.element.setAttribute('test-current-url', url); |
| } |
| } |
| } |
| } |
| |
| private onLayerPainted({data: layer}: Common.EventTarget.EventTargetEvent<SDK.LayerTreeBase.Layer>): void { |
| if (!this.model) { |
| return; |
| } |
| const selection = this.layerViewHost.selection(); |
| if (selection && selection.layer() === layer) { |
| this.layerDetailsView.update(); |
| } |
| this.layers3DView.updateLayerSnapshot(layer); |
| } |
| |
| private onPaintProfileRequested({data: selection}: |
| Common.EventTarget.EventTargetEvent<LayerViewer.LayerViewHost.Selection>): void { |
| void this.layers3DView.snapshotForSelection(selection).then(snapshotWithRect => { |
| if (!snapshotWithRect) { |
| return; |
| } |
| this.layerBeingProfiled = selection.layer(); |
| if (!this.tabbedPane.hasTab(DetailsViewTabs.Profiler)) { |
| this.tabbedPane.appendTab( |
| DetailsViewTabs.Profiler, i18nString(UIStrings.profiler), this.paintProfilerView, undefined, true, true); |
| } |
| this.tabbedPane.selectTab(DetailsViewTabs.Profiler); |
| this.paintProfilerView.profile(snapshotWithRect.snapshot); |
| }); |
| } |
| |
| private onTabClosed(event: Common.EventTarget.EventTargetEvent<UI.TabbedPane.EventData>): void { |
| if (event.data.tabId !== DetailsViewTabs.Profiler || !this.layerBeingProfiled) { |
| return; |
| } |
| this.paintProfilerView.reset(); |
| this.layers3DView.showImageForLayer(this.layerBeingProfiled, undefined); |
| this.layerBeingProfiled = null; |
| } |
| |
| private showImage(imageURL?: string): void { |
| if (this.layerBeingProfiled) { |
| this.layers3DView.showImageForLayer(this.layerBeingProfiled, imageURL); |
| } |
| } |
| |
| private onScaleChanged(event: Common.EventTarget.EventTargetEvent<number>): void { |
| this.paintProfilerView.setScale(event.data); |
| } |
| } |
| |
| export const DetailsViewTabs = { |
| Details: 'details', |
| Profiler: 'profiler', |
| }; |