Skip to content

Commit d2738fa

Browse files
Connor ClarkDevtools-frontend LUCI CQ
authored andcommitted
[RPP] Show progress bar when downloading trace
Preparing the trace for download can take awhile. This CL shows the status dialog as the trace is being prepared for download, including a progress bar to track the compression stage. Bug: 457771800 Bypass-Check-License: I didn't add a new license comment. Change-Id: Ie6a28b8331a6a44555120558a478266d737e83ff Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7268421 Auto-Submit: Connor Clark <cjamcl@chromium.org> Reviewed-by: Paul Irish <paulirish@chromium.org> Commit-Queue: Connor Clark <cjamcl@chromium.org>
1 parent a93ac54 commit d2738fa

4 files changed

Lines changed: 75 additions & 5 deletions

File tree

front_end/core/common/Gzip.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,22 @@ export function decompressStream(stream: ReadableStream): ReadableStream {
6868
const ds = new DecompressionStream('gzip');
6969
return stream.pipeThrough(ds);
7070
}
71+
7172
export function compressStream(stream: ReadableStream): ReadableStream {
7273
const cs = new CompressionStream('gzip');
7374
return stream.pipeThrough(cs);
7475
}
76+
77+
export function createMonitoredStream(stream: ReadableStream, onProgress: (bytesRead: number) => void): ReadableStream {
78+
let bytesRead = 0;
79+
80+
const progressTransformer = new TransformStream({
81+
transform(chunk, controller) {
82+
bytesRead += chunk.byteLength;
83+
onProgress(bytesRead);
84+
controller.enqueue(chunk);
85+
}
86+
});
87+
88+
return stream.pipeThrough(progressTransformer);
89+
}

front_end/panels/timeline/StatusDialog.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,14 +145,15 @@ export class StatusDialog extends UI.Widget.VBox {
145145
}
146146

147147
remove(): void {
148-
(this.element.parentNode as HTMLElement)?.classList.remove('tinted');
148+
(this.element.parentNode as HTMLElement)?.classList.remove('opaque', 'tinted');
149149
this.stopTimer();
150150
this.element.remove();
151151
}
152152

153-
showPane(parent: Element): void {
153+
showPane(parent: Element, mode: 'tinted'|'opaque' = 'opaque'): void {
154154
this.show(parent);
155-
parent.classList.add('tinted');
155+
parent.classList.toggle('tinted', mode === 'tinted');
156+
parent.classList.toggle('opaque', mode === 'opaque');
156157
}
157158

158159
enableAndFocusButton(): void {

front_end/panels/timeline/TimelinePanel.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,18 @@ const UIStrings = {
232232
* @description Text in Timeline Panel of the Performance panel
233233
*/
234234
initializingTracing: 'Initializing tracing…',
235+
/**
236+
* @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
237+
*/
238+
preparingTraceForDownload: 'Preparing…',
239+
/**
240+
* @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
241+
*/
242+
compressingTraceForDownload: 'Compressing…',
243+
/**
244+
* @description Text in Timeline Panel of the Performance panel. Shown to the user after they request to download the trace.
245+
*/
246+
encodingTraceForDownload: 'Encoding…',
235247
/**
236248
* @description Tooltip description for a checkbox that toggles the visibility of data added by extensions of this panel (Performance).
237249
*/
@@ -1455,6 +1467,9 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
14551467
}
14561468

14571469
this.#showExportTraceErrorDialog(error);
1470+
} finally {
1471+
this.statusDialog?.remove();
1472+
this.statusDialog = null;
14581473
}
14591474
}
14601475

@@ -1464,6 +1479,24 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
14641479
addModifications: boolean,
14651480
shouldCompress: boolean,
14661481
}): Promise<void> {
1482+
this.statusDialog = new StatusDialog(
1483+
{
1484+
hideStopButton: true,
1485+
showProgress: true,
1486+
},
1487+
async () => {
1488+
this.statusDialog?.remove();
1489+
this.statusDialog = null;
1490+
});
1491+
this.statusDialog.showPane(this.statusPaneContainer, 'tinted');
1492+
this.statusDialog.updateStatus(i18nString(UIStrings.preparingTraceForDownload));
1493+
this.statusDialog.updateProgressBar(i18nString(UIStrings.preparingTraceForDownload), 0);
1494+
this.statusDialog.requestUpdate();
1495+
await this.statusDialog.updateComplete;
1496+
// Not sure why the above isn't sufficient.
1497+
await new Promise(resolve => requestAnimationFrame(resolve));
1498+
await new Promise(resolve => requestAnimationFrame(resolve));
1499+
14671500
// Base the filename on the trace's time of recording
14681501
const isoDate =
14691502
Platform.DateUtilities.toISO8601Compact(metadata.startTime ? new Date(metadata.startTime) : new Date());
@@ -1499,8 +1532,16 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
14991532
let blob = new Blob(blobParts, {type: 'application/json'});
15001533

15011534
if (config.shouldCompress) {
1535+
this.statusDialog.updateStatus(i18nString(UIStrings.compressingTraceForDownload));
1536+
this.statusDialog.updateProgressBar(i18nString(UIStrings.compressingTraceForDownload), 0);
1537+
15021538
fileName = `${fileName}.gz` as Platform.DevToolsPath.RawPathString;
1503-
const gzStream = Common.Gzip.compressStream(blob.stream());
1539+
const inputSize = blob.size;
1540+
const monitoredStream = Common.Gzip.createMonitoredStream(blob.stream(), bytesRead => {
1541+
this.statusDialog?.updateProgressBar(
1542+
i18nString(UIStrings.compressingTraceForDownload), bytesRead / inputSize * 100);
1543+
});
1544+
const gzStream = Common.Gzip.compressStream(monitoredStream);
15041545
blob = await new Response(gzStream, {
15051546
headers: {'Content-Type': 'application/gzip'},
15061547
}).blob();
@@ -1515,9 +1556,12 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
15151556
try {
15161557
// The maximum string length in v8 is `2 ** 29 - 23`, aka 538 MB.
15171558
// If the gzipped&base64-encoded trace is larger than that, this'll throw a RangeError.
1559+
this.statusDialog.updateStatus(i18nString(UIStrings.encodingTraceForDownload));
1560+
this.statusDialog.updateProgressBar(i18nString(UIStrings.encodingTraceForDownload), 100);
15181561
bytesAsB64 = await Common.Base64.encode(blob);
15191562
} catch {
15201563
}
1564+
15211565
if (bytesAsB64?.length) {
15221566
const contentData = new TextUtils.ContentData.ContentData(bytesAsB64, /* isBase64=*/ true, blob.type);
15231567
await Workspace.FileManager.FileManager.instance().save(fileName, contentData, /* forceSaveAs=*/ true);
@@ -1531,6 +1575,9 @@ export class TimelinePanel extends Common.ObjectWrapper.eventMixin<EventTypes, t
15311575
a.click();
15321576
URL.revokeObjectURL(url);
15331577
}
1578+
1579+
this.statusDialog.remove();
1580+
this.statusDialog = null;
15341581
}
15351582

15361583
async handleSaveToFileAction(): Promise<void> {

front_end/panels/timeline/timelinePanel.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,18 @@
9090
pointer-events: none;
9191
}
9292

93-
.timeline.panel .status-pane-container.tinted {
93+
.timeline.panel .status-pane-container.opaque {
9494
background-color: var(--sys-color-cdt-base-container);
9595
pointer-events: auto;
9696
}
9797

98+
.timeline.panel .status-pane-container.tinted {
99+
/* stylelint-disable-next-line plugin/use_theme_colors */
100+
background-color: #0005;
101+
background-blend-mode: multiply;
102+
pointer-events: auto;
103+
}
104+
98105
.timeline-landing-page.legacy > div > p {
99106
flex: none;
100107
white-space: pre-line;

0 commit comments

Comments
 (0)