Skip to content

Commit 1a57656

Browse files
authored
Merge pull request #1293 from microsoft/chore/1283
feat: support new canBeRestarted flag from the runtime
2 parents 1fbf943 + 0e06350 commit 1a57656

File tree

8 files changed

+442
-107
lines changed

8 files changed

+442
-107
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This changelog records changes to stable releases since 1.50.2. "TBA" changes he
66

77
- fix: debugged child processes in ext host causing teardown ([#1289](https://github.com/microsoft/vscode-js-debug/issues/1289))
88
- fix: errors thrown in process tree lookup not being visible ([vscode#150754](https://github.com/microsoft/vscode/issues/150754))
9+
- chore: adopt new restartFrame semantics from Chrome 104 ([#1283](https://github.com/microsoft/vscode-js-debug/issues/1283))
910

1011
## v1.68 (May 2022)
1112

src/adapter/console/textualMessage.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { formatMessage } from '../messageFormat';
1111
import { messageFormatters, previewAsObject } from '../objectPreview';
1212
import { AnyObject } from '../objectPreview/betterTypes';
1313
import { IUiLocation } from '../sources';
14-
import { StackTrace } from '../stackTrace';
14+
import { StackFrame, StackTrace } from '../stackTrace';
1515
import { Thread } from '../threads';
1616
import { IConsoleMessage } from './consoleMessage';
1717

@@ -41,8 +41,12 @@ export abstract class TextualMessage<T extends { stackTrace?: Cdp.Runtime.StackT
4141
}
4242

4343
let firstExistingLocation: IUiLocation | undefined;
44-
for (let i = 0; i < stackTrace.frames.length; i++) {
45-
const uiLocation = await stackTrace.frames[i].uiLocation();
44+
for (const frame of stackTrace.frames) {
45+
if (!(frame instanceof StackFrame)) {
46+
continue;
47+
}
48+
49+
const uiLocation = await frame.uiLocation();
4650
if (!uiLocation) {
4751
continue;
4852
}

src/adapter/smartStepping.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { inject, injectable } from 'inversify';
66
import { ILogger, LogTag } from '../common/logging';
7+
import { isInstanceOf } from '../common/objUtils';
78
import { AnyLaunchConfiguration } from '../configuration';
89
import { isSourceWithMap, UnmappedReason } from './sources';
910
import { StackFrame } from './stackTrace';
@@ -78,8 +79,8 @@ export class SmartStepper {
7879
return;
7980
}
8081

81-
const frame = (await pausedDetails.stackTrace.loadFrames(1))[0];
82-
const should = await shouldStepOverStackFrame(frame);
82+
const frame = (await pausedDetails.stackTrace.loadFrames(1)).find(isInstanceOf(StackFrame));
83+
const should = frame && (await shouldStepOverStackFrame(frame));
8384
if (should === StackFrameStepOverReason.NotStepped) {
8485
this.resetSmartStepCount();
8586
return;

src/adapter/stackTrace.ts

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,28 @@ import { IExtraProperty, IScopeRef, IVariableContainer } from './variableStore';
1616

1717
const localize = nls.loadMessageBundle();
1818

19+
export interface IFrameElement {
20+
/** Formats the stack element as V8 would format it */
21+
formatAsNative(): Promise<string>;
22+
/** Pretty formats the stack element as text */
23+
format(): Promise<string>;
24+
/** Formats the element for DAP */
25+
toDap(format?: Dap.StackFrameFormat): Promise<Dap.StackFrame>;
26+
}
27+
28+
type FrameElement = StackFrame | AsyncSeparator;
29+
1930
export class StackTrace {
20-
public readonly frames: StackFrame[] = [];
31+
public readonly frames: FrameElement[] = [];
2132
private _frameById: Map<number, StackFrame> = new Map();
2233
private _asyncStackTraceId?: Cdp.Runtime.StackTraceId;
2334
private _lastFrameThread?: Thread;
2435

2536
public static fromRuntime(thread: Thread, stack: Cdp.Runtime.StackTrace): StackTrace {
2637
const result = new StackTrace(thread);
27-
for (let frameNo = 0; frameNo < stack.callFrames.length; frameNo++) {
28-
if (!stack.callFrames[frameNo].url.endsWith(SourceConstants.InternalExtension)) {
29-
result.frames.push(StackFrame.fromRuntime(thread, stack.callFrames[frameNo], false));
38+
for (const frame of stack.callFrames) {
39+
if (!frame.url.endsWith(SourceConstants.InternalExtension)) {
40+
result.frames.push(StackFrame.fromRuntime(thread, frame, false));
3041
}
3142
}
3243

@@ -88,7 +99,7 @@ export class StackTrace {
8899
this._lastFrameThread = thread;
89100
}
90101

91-
async loadFrames(limit: number): Promise<StackFrame[]> {
102+
async loadFrames(limit: number): Promise<FrameElement[]> {
92103
while (this.frames.length < limit && this._asyncStackTraceId) {
93104
if (this._asyncStackTraceId.debuggerId)
94105
this._lastFrameThread = Thread.threadForDebuggerId(this._asyncStackTraceId.debuggerId);
@@ -117,9 +128,10 @@ export class StackTrace {
117128
stackTrace.callFrames.shift();
118129

119130
if (stackTrace.callFrames.length) {
120-
this._appendFrame(StackFrame.asyncSeparator(thread, stackTrace.description || 'async'));
121-
for (const callFrame of stackTrace.callFrames)
131+
this._appendFrame(new AsyncSeparator(stackTrace.description || 'async'));
132+
for (const callFrame of stackTrace.callFrames) {
122133
this._appendFrame(StackFrame.fromRuntime(thread, callFrame, true));
134+
}
123135
}
124136

125137
if (stackTrace.parentId) {
@@ -131,9 +143,11 @@ export class StackTrace {
131143
}
132144
}
133145

134-
_appendFrame(frame: StackFrame) {
146+
_appendFrame(frame: FrameElement) {
135147
this.frames.push(frame);
136-
this._frameById.set(frame._id, frame);
148+
if (frame instanceof StackFrame) {
149+
this._frameById.set(frame._id, frame);
150+
}
137151
}
138152

139153
async formatAsNative(): Promise<string> {
@@ -174,7 +188,23 @@ interface IScope {
174188
callFrameId: string;
175189
}
176190

177-
export class StackFrame {
191+
export class AsyncSeparator implements IFrameElement {
192+
constructor(private readonly label = 'async') {}
193+
194+
public async toDap(): Promise<Dap.StackFrame> {
195+
return { name: this.label, id: 0, line: 0, column: 0, presentationHint: 'label' };
196+
}
197+
198+
public async formatAsNative(): Promise<string> {
199+
return ` --- ${this.label} ---`;
200+
}
201+
202+
public async format(): Promise<string> {
203+
return `◀ ${this.label} ▶`;
204+
}
205+
}
206+
207+
export class StackFrame implements IFrameElement {
178208
private static _lastFrameId = 0;
179209

180210
_id: number;
@@ -184,7 +214,6 @@ export class StackFrame {
184214
| Promise<IPreferredUiLocation | undefined>
185215
| IPreferredUiLocation
186216
| undefined;
187-
private _isAsyncSeparator = false;
188217
private _scope: IScope | undefined;
189218
private _thread: Thread;
190219

@@ -198,11 +227,11 @@ export class StackFrame {
198227
callFrame: Cdp.Runtime.CallFrame,
199228
isAsync: boolean,
200229
): StackFrame {
201-
return new StackFrame(thread, callFrame.functionName, thread.rawLocation(callFrame), isAsync);
230+
return new StackFrame(thread, callFrame, thread.rawLocation(callFrame), isAsync);
202231
}
203232

204233
static fromDebugger(thread: Thread, callFrame: Cdp.Debugger.CallFrame): StackFrame {
205-
const result = new StackFrame(thread, callFrame.functionName, thread.rawLocation(callFrame));
234+
const result = new StackFrame(thread, callFrame, thread.rawLocation(callFrame));
206235
result._scope = {
207236
chain: callFrame.scopeChain,
208237
thisObject: callFrame.this,
@@ -214,37 +243,32 @@ export class StackFrame {
214243
return result;
215244
}
216245

217-
static asyncSeparator(thread: Thread, name: string): StackFrame {
218-
// todo@connor4312: this should probably be a different type of object
219-
const result = new StackFrame(
220-
thread,
221-
name,
222-
{ lineNumber: 1, columnNumber: 1, scriptId: '' },
223-
true,
224-
);
225-
result._isAsyncSeparator = true;
226-
return result;
227-
}
228-
229246
constructor(
230247
thread: Thread,
231-
name: string,
248+
private readonly callFrame: Cdp.Debugger.CallFrame | Cdp.Runtime.CallFrame,
232249
rawLocation: RawLocation,
233250
private readonly isAsync = false,
234251
) {
235252
this._id = ++StackFrame._lastFrameId;
236-
this._name = name || '<anonymous>';
253+
this._name = callFrame.functionName || '<anonymous>';
237254
this._rawLocation = rawLocation;
238255
this.uiLocation = once(() => thread.rawLocationToUiLocation(rawLocation));
239256
this._thread = thread;
240257
}
241258

259+
/**
260+
* Gets whether the runtime explicitly said this frame can be restarted.
261+
*/
262+
public get canExplicitlyBeRestarted() {
263+
return !!(this.callFrame as Cdp.Debugger.CallFrame).canBeRestarted;
264+
}
265+
242266
/**
243267
* Gets whether this stackframe is at the same position as the other frame.
244268
*/
245-
public equivalentTo(other: StackFrame | undefined) {
269+
public equivalentTo(other: unknown) {
246270
return (
247-
other &&
271+
other instanceof StackFrame &&
248272
other._rawLocation.columnNumber === this._rawLocation.columnNumber &&
249273
other._rawLocation.lineNumber === this._rawLocation.lineNumber &&
250274
other._rawLocation.scriptId === this._rawLocation.scriptId
@@ -336,15 +360,12 @@ export class StackFrame {
336360
return { scopes };
337361
}
338362

363+
/** @inheritdoc */
339364
async toDap(format?: Dap.StackFrameFormat): Promise<Dap.StackFrame> {
340365
const uiLocation = await this.uiLocation();
341366
const source = uiLocation ? await uiLocation.source.toDap() : undefined;
342367
const isSmartStepped = await shouldStepOverStackFrame(this);
343-
const presentationHint = this._isAsyncSeparator
344-
? 'label'
345-
: isSmartStepped
346-
? 'deemphasize'
347-
: 'normal';
368+
const presentationHint = isSmartStepped ? 'deemphasize' : 'normal';
348369
if (isSmartStepped && source) {
349370
source.origin =
350371
isSmartStepped === StackFrameStepOverReason.SmartStep
@@ -374,12 +395,14 @@ export class StackFrame {
374395
column,
375396
source,
376397
presentationHint,
377-
canRestart: !this.isAsync,
398+
// If `canBeRestarted` is present, use that
399+
// https://github.com/microsoft/vscode-js-debug/issues/1283
400+
canRestart: (this.callFrame as Cdp.Debugger.CallFrame).canBeRestarted ?? !this.isAsync,
378401
} as Dap.StackFrame;
379402
}
380403

404+
/** @inheritdoc */
381405
async formatAsNative(): Promise<string> {
382-
if (this._isAsyncSeparator) return ` --- ${this._name} ---`;
383406
const uiLocation = await this.uiLocation();
384407
const url =
385408
(await uiLocation?.source.existingAbsolutePath()) ||
@@ -389,8 +412,8 @@ export class StackFrame {
389412
return ` at ${this._name} (${url}:${lineNumber}:${columnNumber})`;
390413
}
391414

415+
/** @inheritdoc */
392416
async format(): Promise<string> {
393-
if (this._isAsyncSeparator) return `◀ ${this._name} ▶`;
394417
const uiLocation = await this.uiLocation();
395418
const prettyName = (await uiLocation?.source.prettyName()) || '<unknown>';
396419
const anyLocation = uiLocation || this._rawLocation;

src/adapter/threads.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { DebugType } from '../common/contributionUtils';
88
import { EventEmitter } from '../common/events';
99
import { HrTime } from '../common/hrnow';
1010
import { ILogger, LogTag } from '../common/logging';
11+
import { isInstanceOf } from '../common/objUtils';
1112
import { Base1Position } from '../common/positions';
1213
import { delay, getDeferred, IDeferred } from '../common/promiseUtil';
1314
import { IRenameProvider } from '../common/sourceMaps/renameProvider';
@@ -303,12 +304,32 @@ export class Thread implements IVariableStoreLocationProvider {
303304
);
304305
}
305306

306-
await this._cdp.Debugger.restartFrame({ callFrameId });
307+
// Cast is necessary since the devtools-protocol is being slow to update:
308+
// https://github.com/microsoft/vscode-js-debug/issues/1283#issuecomment-1148219994
309+
// https://github.com/ChromeDevTools/devtools-protocol/issues/263
310+
const ok = await this._cdp.Debugger.restartFrame({
311+
callFrameId,
312+
mode: 'StepInto',
313+
} as Cdp.Debugger.RestartFrameParams);
314+
if (!ok) {
315+
return errors.createUserError(
316+
localize('error.unknownRestartError', 'Frame could not be restarted'),
317+
);
318+
}
319+
307320
this._expectedPauseReason = {
308321
reason: 'frame_entry',
309322
description: localize('reason.description.restart', 'Paused on frame entry'),
310323
};
311-
await this._cdp.Debugger.stepInto({});
324+
325+
// Chromium versions before 104 didn't have an explicit `canBeRestarted`
326+
// flag on their call frame. And on those versions, when we `restartFrame`,
327+
// we need to manually `stepInto` to unpause. However, with 104, restarting
328+
// the frame will automatically resume execution.
329+
if (!stackFrame.canExplicitlyBeRestarted) {
330+
await this._cdp.Debugger.stepInto({});
331+
}
332+
312333
return {};
313334
}
314335

@@ -770,7 +791,8 @@ export class Thread implements IVariableStoreLocationProvider {
770791
return false;
771792
}
772793

773-
const first = await trace.frames[0]?.uiLocation();
794+
const firstFrame = trace.frames[0];
795+
const first = firstFrame instanceof StackFrame && (await firstFrame.uiLocation());
774796
if (!first) {
775797
return false;
776798
}
@@ -792,9 +814,14 @@ export class Thread implements IVariableStoreLocationProvider {
792814
if (!stackLocations) {
793815
// for some reason, if this is assigned directly to stackLocations,
794816
// then TS will think it can still be undefined below
795-
const x = await trace
796-
.loadFrames(excludedCallerSearchDepth)
797-
.then(frames => Promise.all(frames.slice(1).map(f => f.uiLocation())));
817+
const x = await trace.loadFrames(excludedCallerSearchDepth).then(frames =>
818+
Promise.all(
819+
frames
820+
.slice(1)
821+
.filter(isInstanceOf(StackFrame))
822+
.map(f => f.uiLocation()),
823+
),
824+
);
798825
stackLocations = x;
799826
}
800827

@@ -1431,7 +1458,7 @@ export class Thread implements IVariableStoreLocationProvider {
14311458
Promise.all(
14321459
details.hitBreakpoints
14331460
.map(bp => this._breakpointManager._resolvedBreakpoints.get(bp))
1434-
.filter((bp): bp is UserDefinedBreakpoint => bp instanceof UserDefinedBreakpoint)
1461+
.filter(isInstanceOf(UserDefinedBreakpoint))
14351462
.map(r => r.untilSetCompleted().then(() => r.dapId)),
14361463
),
14371464
]);

0 commit comments

Comments
 (0)