Skip to content

Commit a14b1f3

Browse files
Connor ClarkDevtools-frontend LUCI CQ
authored andcommitted
Correctly handle source maps when hydrating
btoa/base64 is not valid for utf-8. Use encodeURIComponent instead. Bug: 337909145 Change-Id: Ic46a00e0ae8de5c25fd62cf2a8a685e578685f74 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6518811 Commit-Queue: Connor Clark <cjamcl@chromium.org> Reviewed-by: Paul Irish <paulirish@chromium.org> Auto-Submit: Connor Clark <cjamcl@chromium.org>
1 parent 0baa9d1 commit a14b1f3

File tree

5 files changed

+45
-28
lines changed

5 files changed

+45
-28
lines changed

front_end/core/sdk/EnhancedTracesParser.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('EnhancedTracesParser', () => {
162162
url: 'http://localhost:8080/index.html',
163163
hasSourceURL: false,
164164
sourceURL: undefined,
165-
sourceMapURL: undefined,
165+
sourceMapURL: 'http://localhost:8080/source.map.json',
166166
pid: 8050,
167167
auxData: {
168168
frameId: '21D58E83A5C17916277166140F6A464B' as Protocol.Page.FrameId,
@@ -184,7 +184,7 @@ describe('EnhancedTracesParser', () => {
184184
url: 'http://localhost:8080/index.html',
185185
hasSourceURL: false,
186186
sourceURL: undefined,
187-
sourceMapURL: undefined,
187+
sourceMapURL: 'http://localhost:8080/source.map.json',
188188
pid: 8050,
189189
auxData: {
190190
frameId: '21D58E83A5C17916277166140F6A464B' as Protocol.Page.FrameId,
@@ -206,7 +206,7 @@ describe('EnhancedTracesParser', () => {
206206
url: 'http://localhost:8080/index.html',
207207
hasSourceURL: false,
208208
sourceURL: undefined,
209-
sourceMapURL: undefined,
209+
sourceMapURL: 'http://localhost:8080/source.map.json',
210210
pid: 8052,
211211
};
212212

@@ -259,7 +259,7 @@ describe('EnhancedTracesParser', () => {
259259
assert.lengthOf(scripts, 6);
260260
for (const script of scripts) {
261261
if (script.scriptId === '1' && script.isolate === '12345') {
262-
assert.deepEqual(script, {...script1, sourceMapURL: undefined});
262+
assert.deepEqual(script, script1);
263263
} else if (script.scriptId === '2' && script.isolate === '12345') {
264264
assert.deepEqual(script, script2);
265265
} else if (script.scriptId === '1' && script.isolate === '6789') {
@@ -288,7 +288,7 @@ describe('EnhancedTracesParser', () => {
288288
assert.lengthOf(scripts, 4);
289289
for (const script of scripts) {
290290
if (script.scriptId === '1') {
291-
assert.deepEqual(script, {...script1, sourceMapURL: undefined});
291+
assert.deepEqual(script, script1);
292292
} else if (script.scriptId === '2') {
293293
assert.deepEqual(script, script2);
294294
} else if (script.scriptId === '3') {

front_end/core/sdk/EnhancedTracesParser.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,15 @@ interface TraceEventScriptRundown extends RehydratingTraceBase {
4343
isolate: string,
4444
executionContextId: Protocol.Runtime.ExecutionContextId,
4545
scriptId: Protocol.Runtime.ScriptId,
46-
startLine: number,
47-
startColumn: number,
48-
endLine: number,
49-
endColumn: number,
46+
// These don't actually get set in v8.
5047
url: string,
5148
hash: string,
5249
isModule: boolean,
5350
hasSourceUrl: boolean,
51+
startLine?: number,
52+
startColumn?: number,
53+
endLine?: number,
54+
endColumn?: number,
5455
sourceUrl?: string,
5556
sourceMapUrl?: string,
5657
},
@@ -186,10 +187,10 @@ export class EnhancedTracesParser {
186187
scriptId: data.scriptId,
187188
isolate: data.isolate,
188189
executionContextId: data.executionContextId,
189-
startLine: data.startLine,
190-
startColumn: data.startColumn,
191-
endLine: data.endLine,
192-
endColumn: data.endColumn,
190+
startLine: data.startLine ?? 0,
191+
startColumn: data.startColumn ?? 0,
192+
endLine: data.endLine ?? 0,
193+
endColumn: data.endColumn ?? 0,
193194
hash: data.hash,
194195
isModule: data.isModule,
195196
url: data.url,
@@ -274,28 +275,27 @@ export class EnhancedTracesParser {
274275
// Encoded as a data url so that the debugger model makes no network request.
275276
// NOTE: consider passing directly as object and hacking `parsedScriptSource` in DebuggerModel.ts to handle
276277
// this fake event. Would avoid a lot of wasteful (de)serialization. Maybe add SDK.Script.hydratedSourceMap.
277-
script.sourceMapURL = this.getEncodedSourceMapUrl(script);
278+
this.resolveSourceMap(script);
278279
}
279280

280281
return this.groupContextsAndScriptsUnderTarget(this.#targets, this.#executionContexts, this.#scripts);
281282
}
282283

283-
private getEncodedSourceMapUrl(script: RehydratingScript): string|undefined {
284+
private resolveSourceMap(script: RehydratingScript): void {
284285
if (script.sourceMapURL?.startsWith('data:')) {
285-
return script.sourceMapURL;
286+
return;
286287
}
287288

288289
const sourceMap = this.getSourceMapFromMetadata(script);
289290
if (!sourceMap) {
290291
return;
291292
}
292293

293-
try {
294-
return `data:text/plain;base64,${btoa(JSON.stringify(sourceMap))}`;
295-
} catch {
296-
// TODO(cjamcl): getting InvalidCharacterError (try loading dupe-js.json.gz).
297-
return;
298-
}
294+
// Note: this encoding + re-parsing overhead cost ~10ms per 1MB of JSON on my
295+
// Mac M1 Pro.
296+
// See https://crrev.com/c/6490409/comments/f294c12a_69781e24
297+
const payload = encodeURIComponent(JSON.stringify(sourceMap));
298+
script.sourceMapURL = `data:application/json;charset=utf-8,${payload}`;
299299
}
300300

301301
private getSourceMapFromMetadata(script: RehydratingScript): SourceMapV3|undefined {

front_end/panels/recorder/RecorderController.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ export class RecorderController extends LitElement {
597597
await UI.InspectorView.InspectorView.instance().showPanel(event.data?.targetPanel as string);
598598
switch (event.data?.targetPanel) {
599599
case Components.RecordingView.TargetPanel.PERFORMANCE_PANEL:
600+
// Note: this is not passing any metadata to the Performance panel.
600601
Timeline.TimelinePanel.TimelinePanel.instance().loadFromEvents(events as Trace.Types.Events.Event[]);
601602
break;
602603
}

front_end/panels/timeline/TimelineLoader.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ export class TimelineLoader implements Common.StringOutputStream.OutputStream {
7878
static loadFromEvents(events: Trace.Types.Events.Event[], client: Client): TimelineLoader {
7979
const loader = new TimelineLoader(client);
8080
window.setTimeout(async () => {
81-
void loader.addEvents(events);
81+
void loader.addEvents(events, null);
82+
});
83+
return loader;
84+
}
85+
86+
static loadFromTraceFile(traceFile: Trace.Types.File.TraceFile, client: Client): TimelineLoader {
87+
const loader = new TimelineLoader(client);
88+
window.setTimeout(async () => {
89+
void loader.addEvents(traceFile.traceEvents, traceFile.metadata);
8290
});
8391
return loader;
8492
}
@@ -92,7 +100,7 @@ export class TimelineLoader implements Common.StringOutputStream.OutputStream {
92100
profile, Trace.Types.Events.ThreadID(1));
93101

94102
window.setTimeout(async () => {
95-
void loader.addEvents(contents.traceEvents);
103+
void loader.addEvents(contents.traceEvents, null);
96104
});
97105
} catch (e) {
98106
console.error(e.stack);
@@ -166,7 +174,9 @@ export class TimelineLoader implements Common.StringOutputStream.OutputStream {
166174
}
167175
}
168176

169-
async addEvents(events: readonly Trace.Types.Events.Event[]): Promise<void> {
177+
async addEvents(events: readonly Trace.Types.Events.Event[], metadata: Trace.Types.File.MetaData|null):
178+
Promise<void> {
179+
this.#metadata = metadata;
170180
this.client?.loadingStarted();
171181
/**
172182
* See the `eventsPerChunk` comment in `models/trace/types/Configuration.ts`.

front_end/panels/timeline/TimelinePanel.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,14 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
793793
this.loader = TimelineLoader.loadFromEvents(events, this);
794794
}
795795

796+
loadFromTraceFile(traceFile: Trace.Types.File.TraceFile): void {
797+
if (this.state !== State.IDLE) {
798+
return;
799+
}
800+
this.prepareToLoadTimeline();
801+
this.loader = TimelineLoader.loadFromTraceFile(traceFile, this);
802+
}
803+
796804
getFlameChart(): TimelineFlameChartView {
797805
return this.flameChart;
798806
}
@@ -2979,10 +2987,8 @@ export class LoadTimelineHandler implements Common.QueryParamHandler.QueryParamH
29792987

29802988
export class TraceRevealer implements Common.Revealer.Revealer<SDK.TraceObject.TraceObject> {
29812989
async reveal(trace: SDK.TraceObject.TraceObject): Promise<void> {
2982-
// TODO(cjamcl): This needs to be given a TraceFile, so that metadata is loaded too. Important
2983-
// for source maps (which otherwise won't be saved on export).
29842990
await UI.ViewManager.ViewManager.instance().showView('timeline');
2985-
TimelinePanel.instance().loadFromEvents(trace.traceEvents);
2991+
TimelinePanel.instance().loadFromTraceFile(trace);
29862992
}
29872993
}
29882994

0 commit comments

Comments
 (0)