Skip to content

Commit 761baac

Browse files
Jayson ChenDevtools-frontend LUCI CQ
authored andcommitted
Rehydrated session
This CL supports the launch of the rehydrated session, powered by connection layer previously implemented in both hosted and native mode. - load the following trace file: https://drive.google.com/file/d/1XuKTJnxpM7HuYnxFhNlYQjAOgbSw9YTX/view?usp=drive_link Bug: 337909145 Change-Id: Iabaff01007241e3ad05aa464d3de5b572814f734 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5837910 Reviewed-by: Paul Irish <[email protected]> Commit-Queue: Paul Irish <[email protected]> Reviewed-by: Robert Paveza <[email protected]>
1 parent 3c51433 commit 761baac

File tree

5 files changed

+93
-11
lines changed

5 files changed

+93
-11
lines changed

front_end/core/sdk/Connections.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import type * as Platform from '../platform/platform.js';
88
import * as ProtocolClient from '../protocol_client/protocol_client.js';
99
import * as Root from '../root/root.js';
1010

11+
import {RehydratingConnection} from './RehydratingConnection.js';
12+
1113
export class MainConnection implements ProtocolClient.InspectorBackend.Connection {
1214
onMessage: ((arg0: (Object|string)) => void)|null;
1315
#onDisconnect: ((arg0: string) => void)|null;
@@ -271,6 +273,9 @@ export async function initMainConnection(
271273
}
272274

273275
function createMainConnection(websocketConnectionLost: () => void): ProtocolClient.InspectorBackend.Connection {
276+
if (Root.Runtime.getPathName().includes('rehydrated_devtools_app')) {
277+
return new RehydratingConnection();
278+
}
274279
const wsParam = Root.Runtime.Runtime.queryParam('ws');
275280
const wssParam = Root.Runtime.Runtime.queryParam('wss');
276281
if (wsParam || wssParam) {

front_end/core/sdk/EnhancedTracesParser.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
interface RehydratingTraceBase {
1313
cat: string;
1414
pid: number;
15+
args: {data: object};
1516
}
1617

1718
interface TraceEventTargetRundown extends RehydratingTraceBase {
@@ -208,7 +209,8 @@ export class EnhancedTracesParser {
208209
}
209210

210211
private isTraceEvent(event: unknown): event is RehydratingTraceBase {
211-
return 'cat' in (event as RehydratingTraceBase) && 'pid' in (event as RehydratingTraceBase);
212+
return 'cat' in (event as RehydratingTraceBase) && 'pid' in (event as RehydratingTraceBase) &&
213+
'args' in (event as RehydratingTraceBase) && 'data' in (event as RehydratingTraceBase).args;
212214
}
213215

214216
private isTargetRundownEvent(event: unknown): event is TraceEventTargetRundown {

front_end/core/sdk/RehydratingConnection.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,31 @@
2323
*
2424
*/
2525

26+
import * as Common from '../../core/common/common.js';
2627
import type * as Protocol from '../../generated/protocol.js';
2728
import * as i18n from '../i18n/i18n.js';
29+
import {UserVisibleError} from '../platform/platform.js';
2830
import type * as ProtocolClient from '../protocol_client/protocol_client.js';
2931

3032
import * as EnhancedTraces from './EnhancedTracesParser.js';
3133
import type {
3234
ProtocolMessage, RehydratingExecutionContext, RehydratingScript, RehydratingTarget, ServerMessage} from
3335
'./RehydratingObject.js';
36+
import {TraceObject} from './TraceObject.js';
3437

3538
const UIStrings = {
3639
/**
3740
* @description Text that appears when no source text is available for the given script
3841
*/
3942
noSourceText: 'No source text available',
43+
/**
44+
* @description Text to indicate rehydrating connection cannot find host window
45+
*/
46+
noHostWindow: 'Can not find host window',
47+
/**
48+
* @description Text to indicate that there is an error loading the log
49+
*/
50+
errorLoadingLog: 'Error loading log',
4051
};
4152
const str_ = i18n.i18n.registerUIStrings('core/sdk/RehydratingConnection.ts', UIStrings);
4253
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -57,16 +68,40 @@ export class RehydratingConnection implements ProtocolClient.InspectorBackend.Co
5768
onMessage: ((arg0: Object) => void)|null = null;
5869
traceEvents: unknown[] = [];
5970
sessions: Map<number, RehydratingSessionBase> = new Map();
60-
private static rehydratingConnectionInstance: RehydratingConnection|null = null;
71+
#rehydratingWindow: Window&typeof globalThis;
72+
#onReceiveHostWindowPayloadBound = this.#onReceiveHostWindowPayload.bind(this);
6173

62-
private constructor() {
74+
constructor() {
75+
// If we're invoking this class, we're in the rehydrating pop-up window. Rename window for clarity.
76+
this.#rehydratingWindow = window;
77+
this.#setupMessagePassing();
6378
}
6479

65-
static instance(): RehydratingConnection {
66-
if (!this.rehydratingConnectionInstance) {
67-
this.rehydratingConnectionInstance = new RehydratingConnection();
80+
#setupMessagePassing(): void {
81+
this.#rehydratingWindow.addEventListener('message', this.#onReceiveHostWindowPayloadBound);
82+
if (!this.#rehydratingWindow.opener) {
83+
throw new UserVisibleError.UserVisibleError(i18nString(UIStrings.noHostWindow));
6884
}
69-
return this.rehydratingConnectionInstance;
85+
this.#rehydratingWindow.opener.postMessage({type: 'REHYDRATING_WINDOW_READY'});
86+
}
87+
88+
/**
89+
* This is a callback for rehydrated session to receive payload from host window. Payload includes but not limited to
90+
* the trace event and all necessary data to power a rehydrated session.
91+
*/
92+
#onReceiveHostWindowPayload(event: MessageEvent): void {
93+
if (event.data.type === 'REHYDRATING_TRACE_FILE') {
94+
const {traceFile} = event.data;
95+
const reader = new FileReader();
96+
reader.onload = async(): Promise<void> => {
97+
await this.startHydration(reader.result as string);
98+
};
99+
reader.onerror = (): void => {
100+
throw new UserVisibleError.UserVisibleError(i18nString(UIStrings.errorLoadingLog));
101+
};
102+
reader.readAsText(traceFile);
103+
}
104+
this.#rehydratingWindow.removeEventListener('message', this.#onReceiveHostWindowPayloadBound);
70105
}
71106

72107
async startHydration(logPayload: string): Promise<boolean> {
@@ -109,10 +144,17 @@ export class RehydratingConnection implements ProtocolClient.InspectorBackend.Co
109144
sessionId += 1;
110145
this.sessions.set(sessionId, new RehydratingSession(sessionId, target, executionContexts, scripts, this));
111146
}
112-
this.rehydratingConnectionState = RehydratingConnectionState.REHYDRATED;
147+
await this.#onRehydrated();
113148
return true;
114149
}
115150

151+
async #onRehydrated(): Promise<void> {
152+
this.rehydratingConnectionState = RehydratingConnectionState.REHYDRATED;
153+
// Use revealer to load trace into performance panel
154+
const trace = new TraceObject(this.traceEvents);
155+
await Common.Revealer.reveal(trace);
156+
}
157+
116158
setOnMessage(onMessage: (arg0: (Object|string)) => void): void {
117159
this.onMessage = onMessage;
118160
this.rehydratingConnectionState = RehydratingConnectionState.INITIALIZED;

front_end/models/trace/types/File.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ export interface MetaData {
189189
cpuThrottling?: number;
190190
hardwareConcurrency?: number;
191191
dataOrigin?: DataOrigin;
192-
modifications?: Modifications;
193192
enhancedTraceVersion?: number;
193+
modifications?: Modifications;
194194
cruxFieldData?: CrUXManager.PageResult[];
195195
}
196196

front_end/panels/timeline/TimelinePanel.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,11 +1393,44 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
13931393
if (this.state !== State.IDLE) {
13941394
return;
13951395
}
1396-
this.prepareToLoadTimeline();
1397-
this.loader = await TimelineLoader.loadFromFile(file, this);
1396+
const maximumTraceFileLengthToDetermineEnhancedTraces = 5000;
1397+
// We are expecting to locate the enhanced traces version within the first 5000
1398+
// characters of the trace file if the given trace file is enhanced traces.
1399+
// Doing so can avoid serializing the whole trace while needing to serialize
1400+
// it again in rehydrated session for enhanced traces.
1401+
const blob = file.slice(0, maximumTraceFileLengthToDetermineEnhancedTraces);
1402+
const content = await blob.text();
1403+
if (content.includes('enhancedTraceVersion')) {
1404+
await window.scheduler?.postTask(() => {
1405+
this.#launchRehydratedSession(file);
1406+
}, {priority: 'background'});
1407+
} else {
1408+
this.loader = await TimelineLoader.loadFromFile(file, this);
1409+
this.prepareToLoadTimeline();
1410+
}
13981411
this.createFileSelector();
13991412
}
14001413

1414+
#launchRehydratedSession(file: File): void {
1415+
let rehydratingWindow: Window|null = null;
1416+
let pathToLaunch: string|null = null;
1417+
const url = new URL(window.location.href);
1418+
const pathToEntrypoint = url.pathname.slice(0, url.pathname.lastIndexOf('/'));
1419+
url.pathname = `${pathToEntrypoint}/rehydrated_devtools_app.html`;
1420+
pathToLaunch = url.toString();
1421+
1422+
// Clarifying the window the code is referring to
1423+
const hostWindow = window;
1424+
function onMessageHandler(ev: MessageEvent): void {
1425+
if (url && ev.data && ev.data.type === 'REHYDRATING_WINDOW_READY') {
1426+
rehydratingWindow?.postMessage({type: 'REHYDRATING_TRACE_FILE', traceFile: file}, url.origin);
1427+
}
1428+
hostWindow.removeEventListener('message', onMessageHandler);
1429+
}
1430+
hostWindow.addEventListener('message', onMessageHandler);
1431+
rehydratingWindow = hostWindow.open(pathToLaunch, /* target: */ undefined, 'noopener=false,popup=true');
1432+
}
1433+
14011434
async loadFromURL(url: Platform.DevToolsPath.UrlString): Promise<void> {
14021435
if (this.state !== State.IDLE) {
14031436
return;

0 commit comments

Comments
 (0)