Skip to content

Commit 52ea9e7

Browse files
Jayson ChenDevtools-frontend LUCI CQ
authored andcommitted
Connection Layer for rehydrated devtools
This patch creates a connection layer for rehydrated devtools and supply the relevant CDP support to empower rehydrated devtools to function just like a live profiling session. Bug: 337909145 Change-Id: I6a0448e8cc9dbf10623520dc18fff6fc3eb76591 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5763794 Commit-Queue: Jayson Chen <[email protected]> Reviewed-by: Paul Irish <[email protected]> Reviewed-by: Robert Paveza <[email protected]>
1 parent 4ff8050 commit 52ea9e7

File tree

7 files changed

+537
-11
lines changed

7 files changed

+537
-11
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ grd_files_debug_sources = [
848848
"front_end/core/sdk/PerformanceMetricsModel.js",
849849
"front_end/core/sdk/PreloadingModel.js",
850850
"front_end/core/sdk/ProfileTreeModel.js",
851+
"front_end/core/sdk/RehydratingConnection.js",
851852
"front_end/core/sdk/RehydratingObject.js",
852853
"front_end/core/sdk/RemoteObject.js",
853854
"front_end/core/sdk/Resource.js",

front_end/core/sdk/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ devtools_module("sdk") {
6363
"PerformanceMetricsModel.ts",
6464
"PreloadingModel.ts",
6565
"ProfileTreeModel.ts",
66+
"RehydratingConnection.ts",
6667
"RehydratingObject.ts",
6768
"RemoteObject.ts",
6869
"Resource.ts",
@@ -158,6 +159,7 @@ ts_library("unittests") {
158159
"OverlayPersistentHighlighter.test.ts",
159160
"PageResourceLoader.test.ts",
160161
"PreloadingModel.test.ts",
162+
"RehydratingConnection.test.ts",
161163
"RemoteObject.test.ts",
162164
"ResourceTreeModel.test.ts",
163165
"RuntimeModel.test.ts",

front_end/core/sdk/EnhancedTracesParser.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe('EnhancedTracesParser', () => {
133133
});
134134

135135
it('captures targets from target rundown events', async function() {
136-
const data = enhancedTracesParser.data().data;
136+
const data = enhancedTracesParser.data();
137137
const targets: RehydratingTarget[] = [];
138138
for (const target of data.keys()) {
139139
targets.push(target);
@@ -147,7 +147,7 @@ describe('EnhancedTracesParser', () => {
147147
});
148148

149149
it('captures execution context info', async function() {
150-
const data = enhancedTracesParser.data().data;
150+
const data = enhancedTracesParser.data();
151151
let executionContexts: RehydratingExecutionContext[] = [];
152152
for (const target of data.keys()) {
153153
const contextsAndScripts = data.get(target);
@@ -170,7 +170,7 @@ describe('EnhancedTracesParser', () => {
170170
});
171171

172172
it('captures script info and source text', async function() {
173-
const data = enhancedTracesParser.data().data;
173+
const data = enhancedTracesParser.data();
174174
let scripts: RehydratingScript[] = [];
175175
for (const target of data.keys()) {
176176
const contextsAndScripts = data.get(target);
@@ -193,7 +193,7 @@ describe('EnhancedTracesParser', () => {
193193
});
194194

195195
it('grouped contexts and scripts under the right target', async function() {
196-
const data = enhancedTracesParser.data().data;
196+
const data = enhancedTracesParser.data();
197197
for (const target of data.keys()) {
198198
const contextsAndScripts = data.get(target);
199199
if (contextsAndScripts) {

front_end/core/sdk/EnhancedTracesParser.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import type * as Protocol from '../../generated/protocol.js';
66
import {UserVisibleError} from '../platform/platform.js';
77

88
import type {
9-
EnhancedTracesData, RehydratingExecutionContext, RehydratingScript, RehydratingTarget} from './RehydratingObject.js';
9+
HydratingDataPerTarget, RehydratingExecutionContext, RehydratingScript, RehydratingTarget} from
10+
'./RehydratingObject.js';
1011

1112
interface RehydratingTraceBase {
1213
cat: string;
@@ -156,7 +157,7 @@ export class EnhancedTracesParser {
156157
}
157158
}
158159

159-
data(): EnhancedTracesData {
160+
data(): HydratingDataPerTarget {
160161
// Put back execution context id
161162
const v8ContextToExecutionContextId: Map<string, Protocol.Runtime.ExecutionContextId> =
162163
new Map<string, Protocol.Runtime.ExecutionContextId>();
@@ -199,9 +200,7 @@ export class EnhancedTracesParser {
199200
for (const target of this.#targets) {
200201
data.set(target, this.groupContextsAndScriptsUnderTarget(target, this.#executionContexts, this.#scripts));
201202
}
202-
return {
203-
data,
204-
};
203+
return data;
205204
}
206205

207206
private getScriptIsolateId(isolate: string, scriptId: Protocol.Runtime.ScriptId): string {
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright (c) 2024 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import type * as Protocol from '../../generated/protocol.js';
6+
7+
import * as Rehydrating from './RehydratingConnection.js';
8+
import type {
9+
RehydratingExecutionContext, RehydratingScript, RehydratingTarget, ServerMessage} from './RehydratingObject.js';
10+
11+
const mockTarget1: RehydratingTarget = {
12+
targetId: 'ABCDE' as Protocol.Page.FrameId,
13+
type: 'page',
14+
isolate: '12345',
15+
url: 'example.com',
16+
pid: 12345,
17+
};
18+
19+
const mockExecutionContext1: RehydratingExecutionContext = {
20+
id: 1 as Protocol.Runtime.ExecutionContextId,
21+
origin: 'example.com',
22+
v8Context: 'example context 1',
23+
auxData: {
24+
frameId: 'ABCDE' as Protocol.Page.FrameId,
25+
isDefault: true,
26+
type: 'type',
27+
},
28+
isolate: '12345',
29+
};
30+
31+
const mockExecutionContext2: RehydratingExecutionContext = {
32+
id: 2 as Protocol.Runtime.ExecutionContextId,
33+
origin: 'example.com',
34+
v8Context: 'example context 2',
35+
auxData: {
36+
frameId: 'ABCDE' as Protocol.Page.FrameId,
37+
isDefault: true,
38+
type: 'type',
39+
},
40+
isolate: '12345',
41+
};
42+
43+
const mockScript1: RehydratingScript = {
44+
scriptId: '1' as Protocol.Runtime.ScriptId,
45+
isolate: '12345',
46+
executionContextId: 1 as Protocol.Runtime.ExecutionContextId,
47+
startLine: 0,
48+
startColumn: 0,
49+
endLine: 1,
50+
endColumn: 10,
51+
hash: '',
52+
isModule: false,
53+
url: 'example.com',
54+
hasSourceUrl: false,
55+
sourceMapUrl: undefined,
56+
length: 10,
57+
sourceText: 'source text 1',
58+
auxData: {
59+
frameId: 'ABCDE' as Protocol.Page.FrameId,
60+
isDefault: true,
61+
type: 'type',
62+
},
63+
};
64+
65+
const mockScript2: RehydratingScript = {
66+
scriptId: '2' as Protocol.Runtime.ScriptId,
67+
isolate: '12345',
68+
executionContextId: 2 as Protocol.Runtime.ExecutionContextId,
69+
startLine: 0,
70+
startColumn: 0,
71+
endLine: 1,
72+
endColumn: 10,
73+
hash: '',
74+
isModule: false,
75+
url: 'example.com',
76+
hasSourceUrl: false,
77+
sourceMapUrl: undefined,
78+
length: 10,
79+
sourceText: 'source text 2',
80+
auxData: {
81+
frameId: 'ABCDE' as Protocol.Page.FrameId,
82+
isDefault: true,
83+
type: 'type',
84+
},
85+
};
86+
87+
describe('RehydratingSession', () => {
88+
const sessionId = 1;
89+
const messageId = 1;
90+
const target = mockTarget1;
91+
let mockRehydratingConnection: MockRehydratingConnection;
92+
let mockRehydratingSession: Rehydrating.RehydratingSession;
93+
const executionContextsForTarget1 = [mockExecutionContext1, mockExecutionContext2];
94+
const scriptsForTarget1 = [mockScript1, mockScript2];
95+
96+
class MockRehydratingConnection implements Rehydrating.RehydratingConnectionInterface {
97+
messageQueue: ServerMessage[] = [];
98+
99+
postToFrontend(arg: ServerMessage): void {
100+
this.messageQueue.push(arg);
101+
}
102+
103+
clearMessageQueue(): void {
104+
this.messageQueue = [];
105+
}
106+
}
107+
108+
class RehydratingSessionForTest extends Rehydrating.RehydratingSession {
109+
override sendMessageToFrontend(payload: ServerMessage): void {
110+
this.connection?.postToFrontend(payload);
111+
}
112+
}
113+
114+
beforeEach(() => {
115+
mockRehydratingConnection = new MockRehydratingConnection();
116+
mockRehydratingSession = new RehydratingSessionForTest(
117+
sessionId, target, executionContextsForTarget1, scriptsForTarget1, mockRehydratingConnection);
118+
});
119+
120+
it('send attach to target on construction', async function() {
121+
const attachToTargetMessage = mockRehydratingConnection.messageQueue[0];
122+
assert.isNotNull(attachToTargetMessage);
123+
assert.strictEqual(attachToTargetMessage.method, 'Target.attachedToTarget');
124+
assert.strictEqual(
125+
(attachToTargetMessage.params as Protocol.Target.AttachedToTargetEvent).sessionId.toString(),
126+
sessionId.toString());
127+
assert.strictEqual(
128+
(attachToTargetMessage.params as Protocol.Target.AttachedToTargetEvent).targetInfo.targetId.toString(),
129+
target.targetId.toString());
130+
});
131+
132+
it('sends script parsed and debugger id while handling debugger enable', async function() {
133+
mockRehydratingConnection.clearMessageQueue();
134+
mockRehydratingSession.handleFrontendMessageAsFakeCDPAgent({
135+
id: messageId,
136+
method: 'Debugger.enable',
137+
sessionId,
138+
});
139+
assert.strictEqual(mockRehydratingConnection.messageQueue.length, 3);
140+
const scriptParsedMessages = mockRehydratingConnection.messageQueue.slice(0, 2);
141+
const resultMessage = mockRehydratingConnection.messageQueue.slice(2);
142+
for (const scriptParsedMessage of scriptParsedMessages) {
143+
assert.strictEqual(scriptParsedMessage.method, 'Debugger.scriptParsed');
144+
assert.strictEqual((scriptParsedMessage.params as RehydratingScript).isolate, target.isolate);
145+
}
146+
assert.isNotNull(resultMessage[0]);
147+
assert.strictEqual(resultMessage[0].id, messageId);
148+
assert.isNotNull((resultMessage[0].result as Protocol.Debugger.EnableResponse).debuggerId);
149+
});
150+
151+
it('sends execution context created while handling runtime enable', async function() {
152+
mockRehydratingConnection.clearMessageQueue();
153+
mockRehydratingSession.handleFrontendMessageAsFakeCDPAgent({
154+
id: messageId,
155+
method: 'Runtime.enable',
156+
sessionId,
157+
});
158+
assert.strictEqual(mockRehydratingConnection.messageQueue.length, 3);
159+
const executionContextCreatedMessages = mockRehydratingConnection.messageQueue.slice(0, 2);
160+
const resultMessage = mockRehydratingConnection.messageQueue.slice(2);
161+
for (const executionContextCreatedMessage of executionContextCreatedMessages) {
162+
assert.strictEqual(executionContextCreatedMessage.method, 'Runtime.executionContextCreated');
163+
assert.strictEqual(
164+
(executionContextCreatedMessage.params as Protocol.Runtime.ExecutionContextCreatedEvent)
165+
.context.auxData.frameId,
166+
target.targetId);
167+
}
168+
assert.isNotNull(resultMessage[0]);
169+
assert.strictEqual(resultMessage[0].id, messageId);
170+
});
171+
172+
it('sends script source text while handling get script source', async function() {
173+
mockRehydratingConnection.clearMessageQueue();
174+
mockRehydratingSession.handleFrontendMessageAsFakeCDPAgent({
175+
id: messageId,
176+
method: 'Debugger.getScriptSource',
177+
sessionId,
178+
params: {
179+
scriptId: mockScript1.scriptId,
180+
},
181+
});
182+
assert.strictEqual(mockRehydratingConnection.messageQueue.length, 1);
183+
const scriptSourceTextMessage = mockRehydratingConnection.messageQueue[0];
184+
assert.isNotNull(scriptSourceTextMessage);
185+
assert.strictEqual(scriptSourceTextMessage.id, messageId);
186+
assert.strictEqual(
187+
(scriptSourceTextMessage.result as Protocol.Debugger.GetScriptSourceResponse).scriptSource,
188+
mockScript1.sourceText);
189+
});
190+
});

0 commit comments

Comments
 (0)