UNPKG

chrome-devtools-frontend

Version:
326 lines (307 loc) • 11.3 kB
// Copyright (c) 2020 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_underscored_properties */ import * as DataGrid from '../data_grid/data_grid.js'; import * as Host from '../host/host.js'; import * as i18n from '../i18n/i18n.js'; import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars import * as TextUtils from '../text_utils/text_utils.js'; import * as UI from '../ui/ui.js'; export const UIStrings = { /** *@description Text for the status of something */ status: 'Status', /** *@description Text for web URLs */ url: 'URL', /** *@description Text for the initiator of something */ initiator: 'Initiator', /** *@description Text in Coverage List View of the Coverage tab */ totalBytes: 'Total Bytes', /** *@description Text for errors */ error: 'Error', /** *@description Title for the developer resources tab */ developerResources: 'Developer Resources', /** *@description Text for a context menu entry */ copyUrl: 'Copy URL', /** *@description Text for a context menu entry */ copyInitiatorUrl: 'Copy initiator URL', /** *@description Text for the status column of a list view */ pending: 'pending', /** *@description Text for the status column of a list view */ success: 'success', /** *@description Text for the status column of a list view */ failure: 'failure', /** *@description Accessible text for a file size of 1 byte */ Byte: '1 byte', /** *@description Accessible text for the value in bytes in memory allocation or coverage view. *@example {12345} PH1 */ sBytes: '{PH1} bytes', }; const str_ = i18n.i18n.registerUIStrings('developer_resources/DeveloperResourcesListView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export class DeveloperResourcesListView extends UI.Widget.VBox { _nodeForItem: Map<SDK.PageResourceLoader.PageResource, GridNode>; _isVisibleFilter: (arg0: SDK.PageResourceLoader.PageResource) => boolean; _highlightRegExp: RegExp|null; _dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<GridNode>; constructor(isVisibleFilter: (arg0: SDK.PageResourceLoader.PageResource) => boolean) { super(true); this._nodeForItem = new Map(); this._isVisibleFilter = isVisibleFilter; this._highlightRegExp = null; this.registerRequiredCSS('developer_resources/developerResourcesListView.css', {enableLegacyPatching: false}); const columns = [ {id: 'status', title: i18nString(UIStrings.status), width: '60px', fixedWidth: true, sortable: true}, {id: 'url', title: i18nString(UIStrings.url), width: '250px', fixedWidth: false, sortable: true}, {id: 'initiator', title: i18nString(UIStrings.initiator), width: '80px', fixedWidth: false, sortable: true}, { id: 'size', title: i18nString(UIStrings.totalBytes), width: '80px', fixedWidth: true, sortable: true, align: DataGrid.DataGrid.Align.Right, }, { id: 'errorMessage', title: i18nString(UIStrings.error), width: '200px', fixedWidth: false, sortable: true, }, ] as DataGrid.DataGrid.ColumnDescriptor[]; this._dataGrid = new DataGrid.SortableDataGrid.SortableDataGrid({ displayName: i18nString(UIStrings.developerResources), columns, editCallback: undefined, refreshCallback: undefined, deleteCallback: undefined, }); this._dataGrid.setResizeMethod(DataGrid.DataGrid.ResizeMethod.Last); this._dataGrid.element.classList.add('flex-auto'); this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._sortingChanged, this); this._dataGrid.setRowContextMenuCallback(this._populateContextMenu.bind(this)); const dataGridWidget = this._dataGrid.asWidget(); dataGridWidget.show(this.contentElement); this.setDefaultFocusedChild(dataGridWidget); } _populateContextMenu( contextMenu: UI.ContextMenu.ContextMenu, gridNode: DataGrid.DataGrid.DataGridNode< DataGrid.ViewportDataGrid.ViewportDataGridNode<DataGrid.SortableDataGrid.SortableDataGridNode<GridNode>>>): void { const item = (gridNode as GridNode).item; contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyUrl), () => { Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(item.url); }); if (item.initiator.initiatorUrl) { contextMenu.clipboardSection().appendItem(i18nString(UIStrings.copyInitiatorUrl), () => { Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(item.initiator.initiatorUrl); }); } } update(items: Iterable<SDK.PageResourceLoader.PageResource>): void { let hadUpdates = false; const rootNode = this._dataGrid.rootNode(); for (const item of items) { let node = this._nodeForItem.get(item); if (node) { if (this._isVisibleFilter(node.item)) { hadUpdates = node._refreshIfNeeded() || hadUpdates; } continue; } node = new GridNode(item); this._nodeForItem.set(item, node); if (this._isVisibleFilter(node.item)) { rootNode.appendChild(node); hadUpdates = true; } } if (hadUpdates) { this._sortingChanged(); } } reset(): void { this._nodeForItem.clear(); this._dataGrid.rootNode().removeChildren(); } updateFilterAndHighlight(highlightRegExp: RegExp|null): void { this._highlightRegExp = highlightRegExp; let hadTreeUpdates = false; for (const node of this._nodeForItem.values()) { const shouldBeVisible = this._isVisibleFilter(node.item); const isVisible = Boolean(node.parent); if (shouldBeVisible) { node._setHighlight(this._highlightRegExp); } if (shouldBeVisible === isVisible) { continue; } hadTreeUpdates = true; if (!shouldBeVisible) { node.remove(); } else { this._dataGrid.rootNode().appendChild(node); } } if (hadTreeUpdates) { this._sortingChanged(); } } _sortingChanged(): void { const columnId = this._dataGrid.sortColumnId(); if (!columnId) { return; } const sortFunction = GridNode.sortFunctionForColumn(columnId) as ( (arg0: DataGrid.SortableDataGrid.SortableDataGridNode<GridNode>, arg1: DataGrid.SortableDataGrid.SortableDataGridNode<GridNode>) => number) | null; if (sortFunction) { this._dataGrid.sortNodes(sortFunction, !this._dataGrid.isSortOrderAscending()); } } } class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<GridNode> { item: SDK.PageResourceLoader.PageResource; _highlightRegExp: RegExp|null; constructor(item: SDK.PageResourceLoader.PageResource) { super(); this.item = item; this._highlightRegExp = null; } _setHighlight(highlightRegExp: RegExp|null): void { if (this._highlightRegExp === highlightRegExp) { return; } this._highlightRegExp = highlightRegExp; this.refresh(); } _refreshIfNeeded(): boolean { this.refresh(); return true; } createCell(columnId: string): HTMLElement { const cell = this.createTD(columnId) as HTMLElement; switch (columnId) { case 'url': { UI.Tooltip.Tooltip.install(cell, this.item.url); const outer = cell.createChild('div', 'url-outer'); const prefix = outer.createChild('div', 'url-prefix'); const suffix = outer.createChild('div', 'url-suffix'); const splitURL = /^(.*)(\/[^/]*)$/.exec(this.item.url); prefix.textContent = splitURL ? splitURL[1] : this.item.url; suffix.textContent = splitURL ? splitURL[2] : ''; if (this._highlightRegExp) { this._highlight(outer, this.item.url); } this.setCellAccessibleName(this.item.url, cell, columnId); break; } case 'initiator': { const url = this.item.initiator.initiatorUrl || ''; cell.textContent = url; UI.Tooltip.Tooltip.install(cell, url); this.setCellAccessibleName(url, cell, columnId); cell.onmouseenter = (): void => { const frame = SDK.FrameManager.FrameManager.instance().getFrame(this.item.initiator.frameId || ''); if (frame) { frame.highlight(); } }; cell.onmouseleave = (): void => SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight(); break; } case 'status': { if (this.item.success === null) { cell.textContent = i18nString(UIStrings.pending); } else { cell.textContent = this.item.success ? i18nString(UIStrings.success) : i18nString(UIStrings.failure); } break; } case 'size': { const size = this.item.size; if (size !== null) { const sizeSpan = cell.createChild('span'); sizeSpan.textContent = Number.withThousandsSeparator(size); const sizeAccessibleName = (size === 1) ? i18nString(UIStrings.Byte) : i18nString(UIStrings.sBytes, {PH1: size}); this.setCellAccessibleName(sizeAccessibleName, cell, columnId); } break; } case 'errorMessage': { cell.classList.add('error-message'); if (this.item.errorMessage) { cell.textContent = this.item.errorMessage; if (this._highlightRegExp) { this._highlight(cell, this.item.errorMessage); } } break; } } return cell; } _highlight(element: Element, textContent: string): void { if (!this._highlightRegExp) { return; } const matches = this._highlightRegExp.exec(textContent); if (!matches || !matches.length) { return; } const range = new TextUtils.TextRange.SourceRange(matches.index, matches[0].length); UI.UIUtils.highlightRangesWithStyleClass(element, [range], 'filter-highlight'); } static sortFunctionForColumn(columnId: string): ((arg0: GridNode, arg1: GridNode) => number)|null { const nullToNegative = (x: boolean|number|null): number => x === null ? -1 : Number(x); switch (columnId) { case 'url': return (a: GridNode, b: GridNode): number => a.item.url.localeCompare(b.item.url); case 'status': return (a: GridNode, b: GridNode): number => { return nullToNegative(a.item.success) - nullToNegative(b.item.success); }; case 'size': return (a: GridNode, b: GridNode): number => nullToNegative(a.item.size) - nullToNegative(b.item.size); case 'initiator': return (a: GridNode, b: GridNode): number => (a.item.initiator.initiatorUrl || '').localeCompare(b.item.initiator.initiatorUrl || ''); case 'errorMessage': return (a: GridNode, b: GridNode): number => (a.item.errorMessage || '').localeCompare(b.item.errorMessage || ''); default: console.assert(false, 'Unknown sort field: ' + columnId); return null; } } }