Skip to content

Commit 6f794b3

Browse files
danilsomsikovDevtools-frontend LUCI CQ
authored andcommitted
Decouple CoverageListView from URLCoverageInfo
This change introduces a new `CoverageListItem` interface in `CoverageListView.ts`. The `CoverageListView` and its `GridNode` now operate on this interface, rather than directly on `CoverageModel.URLCoverageInfo`. The `CoverageView` is updated to convert `URLCoverageInfo` instances to `CoverageListItem` objects before passing them to the list view, improving separation of concerns. Bug: 414630818 Change-Id: Ie7dec46759aab4faa3060b185df00b104b17b8e9 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6983464 Reviewed-by: Alex Rudenko <[email protected]> Auto-Submit: Danil Somsikov <[email protected]> Commit-Queue: Danil Somsikov <[email protected]>
1 parent 9a03869 commit 6f794b3

File tree

4 files changed

+135
-101
lines changed

4 files changed

+135
-101
lines changed

front_end/panels/coverage/CoverageListView.test.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// found in the LICENSE file.
44

55
import * as Platform from '../../core/platform/platform.js';
6-
import type * as TextUtils from '../../models/text_utils/text_utils.js';
76
import {assertScreenshot} from '../../testing/DOMHelpers.js';
87
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
98
import {
@@ -14,26 +13,36 @@ import * as Coverage from './coverage.js';
1413

1514
const {urlString} = Platform.DevToolsPath;
1615

17-
function makeCoverageInfo(url: string, type: Coverage.CoverageModel.CoverageType, size: number, unusedSize: number):
18-
Coverage.CoverageModel.URLCoverageInfo {
19-
const result = new Coverage.CoverageModel.URLCoverageInfo(urlString`${url}`);
20-
result.ensureEntry({contentURL: () => url} as TextUtils.ContentProvider.ContentProvider, size, 0, 0, type);
21-
result.addToSizes(size - unusedSize, 0);
22-
return result;
16+
function makeItem(
17+
url: Platform.DevToolsPath.UrlString, type: Coverage.CoverageModel.CoverageType, size: number,
18+
unusedSize: number): Coverage.CoverageListView.CoverageListItem {
19+
const usedSize = size - unusedSize;
20+
return {
21+
url,
22+
type,
23+
size,
24+
usedSize,
25+
unusedSize,
26+
usedPercentage: size > 0 ? usedSize / size : 0,
27+
unusedPercentage: size > 0 ? unusedSize / size : 0,
28+
sources: [],
29+
isContentScript: false,
30+
};
2331
}
2432

2533
describeWithEnvironment('CoverageListView', () => {
2634
it('basic rendering', async () => {
2735
const view = new Coverage.CoverageListView.CoverageListView(_ => true);
2836
renderWidgetInVbox(view);
2937
await view.update([
30-
makeCoverageInfo('https://example.com/index.html', Coverage.CoverageModel.CoverageType.JAVA_SCRIPT, 100, 10),
31-
makeCoverageInfo(
32-
'https://example.com/index.html?query=foo', Coverage.CoverageModel.CoverageType.JAVA_SCRIPT_PER_FUNCTION, 100,
33-
0),
34-
makeCoverageInfo('https://example.com/index.html?query=bar', Coverage.CoverageModel.CoverageType.CSS, 100, 50),
35-
makeCoverageInfo(
36-
'https://example.com/index.html?query=bar', Coverage.CoverageModel.CoverageType.JAVA_SCRIPT, 100, 50),
38+
makeItem(urlString`https://example.com/index.html`, Coverage.CoverageModel.CoverageType.JAVA_SCRIPT, 100, 10),
39+
makeItem(
40+
urlString`https://example.com/index.html?query=foo`,
41+
Coverage.CoverageModel.CoverageType.JAVA_SCRIPT_PER_FUNCTION, 100, 0),
42+
makeItem(urlString`https://example.com/index.html?query=baz`, Coverage.CoverageModel.CoverageType.CSS, 100, 50),
43+
makeItem(
44+
urlString`https://example.com/index.html?query=bar`, Coverage.CoverageModel.CoverageType.JAVA_SCRIPT, 100,
45+
50),
3746
]);
3847

3948
await assertScreenshot('coverage/basic.png');

front_end/panels/coverage/CoverageListView.ts

Lines changed: 77 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@ import * as UI from '../../ui/legacy/legacy.js';
1414
import {Directives, html, nothing, render} from '../../ui/lit/lit.js';
1515

1616
import coverageListViewStyles from './coverageListView.css.js';
17-
import {
18-
CoverageType,
19-
SourceURLCoverageInfo,
20-
type URLCoverageInfo,
21-
} from './CoverageModel.js';
17+
import {CoverageType} from './CoverageModel.js';
18+
19+
export interface CoverageListItem {
20+
url: Platform.DevToolsPath.UrlString;
21+
type: CoverageType;
22+
size: number;
23+
usedSize: number;
24+
unusedSize: number;
25+
usedPercentage: number;
26+
unusedPercentage: number;
27+
sources: CoverageListItem[];
28+
isContentScript: boolean;
29+
generatedUrl?: Platform.DevToolsPath.UrlString;
30+
}
2231

2332
const UIStrings = {
2433
/**
@@ -131,15 +140,15 @@ export function coverageTypeToString(type: CoverageType): string {
131140
}
132141

133142
export class CoverageListView extends UI.Widget.VBox {
134-
private readonly nodeForCoverageInfo: Map<URLCoverageInfo, GridNode>;
135-
private readonly isVisibleFilter: (arg0: URLCoverageInfo) => boolean;
143+
private readonly nodeForUrl: Map<Platform.DevToolsPath.UrlString, GridNode>;
144+
private readonly isVisibleFilter: (arg0: CoverageListItem) => boolean;
136145
private highlightRegExp: RegExp|null;
137146
private dataGrid: DataGrid.SortableDataGrid.SortableDataGrid<GridNode>;
138147

139-
constructor(isVisibleFilter: (arg0: URLCoverageInfo) => boolean) {
148+
constructor(isVisibleFilter: (arg0: CoverageListItem) => boolean) {
140149
super({useShadowDom: true});
141150
this.registerRequiredCSS(coverageListViewStyles);
142-
this.nodeForCoverageInfo = new Map();
151+
this.nodeForUrl = new Map();
143152
this.isVisibleFilter = isVisibleFilter;
144153
this.highlightRegExp = null;
145154

@@ -200,27 +209,27 @@ export class CoverageListView extends UI.Widget.VBox {
200209
this.setDefaultFocusedChild(dataGridWidget);
201210
}
202211

203-
update(coverageInfo: URLCoverageInfo[] = []): void {
212+
update(coverageInfo: CoverageListItem[] = []): void {
204213
let hadUpdates = false;
205-
const maxSize = coverageInfo.reduce((acc, entry) => Math.max(acc, entry.size()), 0);
214+
const maxSize = coverageInfo.reduce((acc, entry) => Math.max(acc, entry.size), 0);
206215
const rootNode = this.dataGrid.rootNode();
207216
for (const entry of coverageInfo) {
208-
let node = this.nodeForCoverageInfo.get(entry);
217+
let node = this.nodeForUrl.get(entry.url);
209218
if (node) {
210219
if (this.isVisibleFilter(node.coverageInfo)) {
211-
hadUpdates = node.refreshIfNeeded(maxSize) || hadUpdates;
212-
if (entry.sourcesURLCoverageInfo.size > 0) {
213-
this.updateSourceNodes(entry.sourcesURLCoverageInfo, maxSize, node);
220+
hadUpdates = node.refreshIfNeeded(maxSize, entry) || hadUpdates;
221+
if (entry.sources.length > 0) {
222+
this.updateSourceNodes(entry.sources, maxSize, node);
214223
}
215224
}
216225
continue;
217226
}
218227
node = new GridNode(entry, maxSize);
219-
this.nodeForCoverageInfo.set(entry, node);
228+
this.nodeForUrl.set(entry.url, node);
220229
if (this.isVisibleFilter(node.coverageInfo)) {
221230
rootNode.appendChild(node);
222-
if (entry.sourcesURLCoverageInfo.size > 0) {
223-
void this.createSourceNodes(entry.sourcesURLCoverageInfo, maxSize, node);
231+
if (entry.sources.length > 0) {
232+
void this.createSourceNodes(entry.sources, maxSize, node);
224233
}
225234
hadUpdates = true;
226235
}
@@ -230,43 +239,39 @@ export class CoverageListView extends UI.Widget.VBox {
230239
}
231240
}
232241

233-
updateSourceNodes(
234-
sourcesURLCoverageInfo: Map<Platform.DevToolsPath.UrlString, SourceURLCoverageInfo>, maxSize: number,
235-
node: GridNode): void {
242+
updateSourceNodes(sources: CoverageListItem[], maxSize: number, node: GridNode): void {
236243
let shouldCreateSourceNodes = false;
237-
for (const coverageInfo of sourcesURLCoverageInfo.values()) {
238-
const sourceNode = this.nodeForCoverageInfo.get(coverageInfo);
244+
for (const coverageInfo of sources) {
245+
const sourceNode = this.nodeForUrl.get(coverageInfo.url);
239246
if (sourceNode) {
240-
sourceNode.refreshIfNeeded(maxSize);
247+
sourceNode.refreshIfNeeded(maxSize, coverageInfo);
241248
} else {
242249
shouldCreateSourceNodes = true;
243250
break;
244251
}
245252
}
246253
if (shouldCreateSourceNodes) {
247-
void this.createSourceNodes(sourcesURLCoverageInfo, maxSize, node);
254+
void this.createSourceNodes(sources, maxSize, node);
248255
}
249256
}
250257

251-
async createSourceNodes(
252-
sourcesURLCoverageInfo: Map<Platform.DevToolsPath.UrlString, SourceURLCoverageInfo>, maxSize: number,
253-
node: GridNode): Promise<void> {
254-
for (const coverageInfo of sourcesURLCoverageInfo.values()) {
258+
async createSourceNodes(sources: CoverageListItem[], maxSize: number, node: GridNode): Promise<void> {
259+
for (const coverageInfo of sources) {
255260
const sourceNode = new GridNode(coverageInfo, maxSize);
256261
node.appendChild(sourceNode);
257-
this.nodeForCoverageInfo.set(coverageInfo, sourceNode);
262+
this.nodeForUrl.set(coverageInfo.url, sourceNode);
258263
}
259264
}
260265

261266
reset(): void {
262-
this.nodeForCoverageInfo.clear();
267+
this.nodeForUrl.clear();
263268
this.dataGrid.rootNode().removeChildren();
264269
}
265270

266271
updateFilterAndHighlight(highlightRegExp: RegExp|null): void {
267272
this.highlightRegExp = highlightRegExp;
268273
let hadTreeUpdates = false;
269-
for (const node of this.nodeForCoverageInfo.values()) {
274+
for (const node of this.nodeForUrl.values()) {
270275
const shouldBeVisible = this.isVisibleFilter(node.coverageInfo);
271276
const isVisible = Boolean(node.parent);
272277
if (shouldBeVisible) {
@@ -288,20 +293,18 @@ export class CoverageListView extends UI.Widget.VBox {
288293
}
289294

290295
private appendNodeByType(node: GridNode): void {
291-
if (node.coverageInfo instanceof SourceURLCoverageInfo) {
292-
const parentNode = this.nodeForCoverageInfo.get(node.coverageInfo.generatedURLCoverageInfo);
296+
if (node.coverageInfo.generatedUrl) {
297+
const parentNode = this.nodeForUrl.get(node.coverageInfo.generatedUrl);
293298
parentNode?.appendChild(node);
294299
} else {
295300
this.dataGrid.rootNode().appendChild(node);
296301
}
297302
}
298303

299304
selectByUrl(url: string): void {
300-
for (const [info, node] of this.nodeForCoverageInfo.entries()) {
301-
if (info.url() === url) {
302-
node.revealAndSelect();
303-
break;
304-
}
305+
const node = this.nodeForUrl.get(url as Platform.DevToolsPath.UrlString);
306+
if (node) {
307+
node.revealAndSelect();
305308
}
306309
}
307310

@@ -315,7 +318,7 @@ export class CoverageListView extends UI.Widget.VBox {
315318
return;
316319
}
317320
const coverageInfo = (node as GridNode).coverageInfo;
318-
const sourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(coverageInfo.url());
321+
const sourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(coverageInfo.url);
319322
if (!sourceCode) {
320323
return;
321324
}
@@ -325,7 +328,6 @@ export class CoverageListView extends UI.Widget.VBox {
325328
}
326329
void Common.Revealer.reveal(sourceCode);
327330
}
328-
329331
}
330332

331333
let percentageFormatter: Intl.NumberFormat|null = null;
@@ -350,27 +352,28 @@ function getBytesFormatter(): Intl.NumberFormat {
350352
}
351353

352354
export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<GridNode> {
353-
coverageInfo: URLCoverageInfo;
355+
coverageInfo: CoverageListItem;
354356
private lastUsedSize!: number|undefined;
355357
private url: Platform.DevToolsPath.UrlString;
356358
private maxSize: number;
357359
private highlightRegExp: RegExp|null;
358360

359-
constructor(coverageInfo: URLCoverageInfo, maxSize: number) {
361+
constructor(coverageInfo: CoverageListItem, maxSize: number) {
360362
super();
361363
this.coverageInfo = coverageInfo;
362-
this.url = coverageInfo.url();
364+
this.url = coverageInfo.url;
363365
this.maxSize = maxSize;
364366
this.highlightRegExp = null;
365-
this.#updateData();
367+
this.#updateData(coverageInfo);
366368
}
367369

368-
#updateData(): void {
370+
#updateData(coverageInfo: CoverageListItem): void {
369371
this.data['url'] = this.url;
370-
this.data['type'] = coverageTypeToString(this.coverageInfo.type());
371-
this.data['size'] = this.coverageInfo.size();
372-
this.data['unused-size'] = this.coverageInfo.unusedSize();
373-
this.data['bars'] = this.coverageInfo.unusedSize();
372+
this.data['type'] = coverageTypeToString(coverageInfo.type);
373+
this.data['size'] = coverageInfo.size;
374+
this.data['unused-size'] = coverageInfo.unusedSize;
375+
this.data['bars'] = coverageInfo.unusedSize;
376+
this.coverageInfo = coverageInfo;
374377
}
375378

376379
setHighlight(highlightRegExp: RegExp|null): void {
@@ -381,14 +384,14 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
381384
this.refresh();
382385
}
383386

384-
refreshIfNeeded(maxSize: number): boolean {
385-
if (this.lastUsedSize === this.coverageInfo.usedSize() && maxSize === this.maxSize) {
387+
refreshIfNeeded(maxSize: number, coverageInfo: CoverageListItem): boolean {
388+
if (this.lastUsedSize === coverageInfo.usedSize && maxSize === this.maxSize) {
386389
return false;
387390
}
388-
this.lastUsedSize = this.coverageInfo.usedSize();
391+
this.lastUsedSize = coverageInfo.usedSize;
389392
this.maxSize = maxSize;
390393
this.refresh();
391-
this.#updateData();
394+
this.#updateData(coverageInfo);
392395
return true;
393396
}
394397

@@ -421,26 +424,26 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
421424
case 'type': {
422425
UI.Tooltip.Tooltip.install(
423426
cell,
424-
info.type() & CoverageType.JAVA_SCRIPT_PER_FUNCTION ? i18nString(UIStrings.jsCoverageWithPerFunction) :
425-
info.type() & CoverageType.JAVA_SCRIPT ? i18nString(UIStrings.jsCoverageWithPerBlock) :
426-
'');
427-
render(coverageTypeToString(this.coverageInfo.type()), cell);
427+
info.type & CoverageType.JAVA_SCRIPT_PER_FUNCTION ? i18nString(UIStrings.jsCoverageWithPerFunction) :
428+
info.type & CoverageType.JAVA_SCRIPT ? i18nString(UIStrings.jsCoverageWithPerBlock) :
429+
'');
430+
render(coverageTypeToString(this.coverageInfo.type), cell);
428431
break;
429432
}
430433
case 'size': {
431-
this.setCellAccessibleName(i18nString(UIStrings.sBytes, {n: info.size() || 0}), cell, columnId);
432-
render(html`<span>${formatBytes(info.size())}</span>`, cell);
434+
this.setCellAccessibleName(i18nString(UIStrings.sBytes, {n: info.size || 0}), cell, columnId);
435+
render(html`<span>${formatBytes(info.size)}</span>`, cell);
433436
break;
434437
}
435438
case 'unused-size': {
436439
this.setCellAccessibleName(
437-
i18nString(UIStrings.sBytesS, {n: info.unusedSize(), percentage: formatPercent(info.unusedPercentage())}),
438-
cell, columnId);
440+
i18nString(UIStrings.sBytesS, {n: info.unusedSize, percentage: formatPercent(info.unusedPercentage)}), cell,
441+
columnId);
439442
// clang-format off
440443
render(html`
441-
<span>${formatBytes(info.unusedSize())}</span>
444+
<span>${formatBytes(info.unusedSize)}</span>
442445
<span class="percent-value">
443-
${formatPercent(info.unusedPercentage())}
446+
${formatPercent(info.unusedPercentage)}
444447
</span>`, cell);
445448
// clang-format on
446449
break;
@@ -449,27 +452,27 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
449452
this.setCellAccessibleName(
450453
i18nString(
451454
UIStrings.sOfFileUnusedSOfFileUsed,
452-
{PH1: formatPercent(info.unusedPercentage()), PH2: formatPercent(info.usedPercentage())}),
455+
{PH1: formatPercent(info.unusedPercentage), PH2: formatPercent(info.usedPercentage)}),
453456
cell, columnId);
454457
// clang-format off
455458
render(html`
456459
<div class="bar-container">
457-
${info.unusedSize() > 0 ? html`
460+
${info.unusedSize > 0 ? html`
458461
<div class="bar bar-unused-size"
459462
title=${
460-
info.type() & CoverageType.JAVA_SCRIPT_PER_FUNCTION ? i18nString(UIStrings.sBytesSBelongToFunctionsThatHave, {PH1: info.unusedSize(), PH2: formatPercent(info.unusedPercentage())}) :
461-
info.type() & CoverageType.JAVA_SCRIPT ? i18nString(UIStrings.sBytesSBelongToBlocksOf, {PH1: info.unusedSize(), PH2: formatPercent(info.unusedPercentage())}) :
463+
info.type & CoverageType.JAVA_SCRIPT_PER_FUNCTION ? i18nString(UIStrings.sBytesSBelongToFunctionsThatHave, {PH1: info.unusedSize, PH2: formatPercent(info.unusedPercentage)}) :
464+
info.type & CoverageType.JAVA_SCRIPT ? i18nString(UIStrings.sBytesSBelongToBlocksOf, {PH1: info.unusedSize, PH2: formatPercent(info.unusedPercentage)}) :
462465
''}
463-
style=${styleMap({width: ((info.unusedSize() / this.maxSize) * 100 || 0) + '%'})}>
466+
style=${styleMap({width: ((info.unusedSize / this.maxSize) * 100 || 0) + '%'})}>
464467
</div>` : nothing}
465-
${info.usedSize() > 0 ? html`
468+
${info.usedSize > 0 ? html`
466469
<div class="bar bar-used-size"
467470
title=${
468-
info.type() & CoverageType.JAVA_SCRIPT_PER_FUNCTION ? i18nString(UIStrings.sBytesSBelongToFunctionsThatHaveExecuted, {PH1: info.usedSize(), PH2: formatPercent(info.usedPercentage())}) :
469-
info.type() & CoverageType.JAVA_SCRIPT ? i18nString(UIStrings.sBytesSBelongToBlocksOfJavascript, {PH1: info.usedSize(), PH2: formatPercent(info.usedPercentage())}) :
471+
info.type & CoverageType.JAVA_SCRIPT_PER_FUNCTION ? i18nString(UIStrings.sBytesSBelongToFunctionsThatHaveExecuted, {PH1: info.usedSize, PH2: formatPercent(info.usedPercentage)}) :
472+
info.type & CoverageType.JAVA_SCRIPT ? i18nString(UIStrings.sBytesSBelongToBlocksOfJavascript, {PH1: info.usedSize, PH2: formatPercent(info.usedPercentage)}) :
470473
''}
471-
{ PH1: info.usedSize(), PH2: formatPercent(info.usedPercentage()) })}
472-
style=${styleMap({width:((info.usedSize() / this.maxSize) * 100 || 0) + '%'})}>
474+
{ PH1: info.usedSize, PH2: formatPercent(info.usedPercentage) })}
475+
style=${styleMap({width:((info.usedSize / this.maxSize) * 100 || 0) + '%'})}>
473476
</div>` : nothing}
474477
</div>`, cell);
475478
// clang-format on
@@ -489,5 +492,4 @@ export class GridNode extends DataGrid.SortableDataGrid.SortableDataGridNode<Gri
489492
const range = new TextUtils.TextRange.SourceRange(matches.index, matches[0].length);
490493
UI.UIUtils.highlightRangesWithStyleClass(element, [range], 'filter-highlight');
491494
}
492-
493495
}

0 commit comments

Comments
 (0)