Skip to content

Show JS frames when app is paused and no Dart frames are available #2038

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions dwds/lib/src/debugging/debugger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,33 @@ class Debugger extends Domain {
return dartFrame;
}

/// Returns a JS [Frame] from a Chrome frame.
Frame? calculateJsFrameFor(
WipCallFrame frame,
int frameIndex, {
bool isAsync = false,
}) {
final location = frame.location;
final url = urlForScriptId(location.scriptId);

final jsFrame = Frame(
index: frameIndex,
code: CodeRef(
name: frame.functionName.isEmpty ? 'anonymous' : frame.functionName,
kind: CodeKind.kNative,
id: createId(),
),
location: SourceLocation(
script: ScriptRef(uri: url, id: createId()),
line: location.lineNumber,
column: location.columnNumber,
),
kind: isAsync ? FrameKind.kAsyncCausal : FrameKind.kRegular,
);

return jsFrame;
}

/// Handles pause events coming from the Chrome connection.
Future<void> _pauseHandler(DebuggerPausedEvent e) async {
final isolate = inspector.isolate;
Expand Down
26 changes: 24 additions & 2 deletions dwds/lib/src/debugging/frame_computer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class FrameComputer {

final List<WipCallFrame> _callFrames;
final List<Frame> _computedFrames = [];
final List<Frame> _jsFrames = [];

var _frameIndex = 0;

Expand Down Expand Up @@ -51,6 +52,11 @@ class FrameComputer {
_computedFrames.removeLast();
}

// Only show JS frames if we were unable to calculate any Dart frames.
if (_computedFrames.isEmpty && _jsFrames.isNotEmpty) {
return _jsFrames.take(limit ?? _jsFrames.length).toList();
}

return _computedFrames;
});
}
Expand All @@ -61,10 +67,16 @@ class FrameComputer {

final callFrame = _callFrames[_frameIndex];
final dartFrame =
await debugger.calculateDartFrameFor(callFrame, _frameIndex++);
await debugger.calculateDartFrameFor(callFrame, _frameIndex);
if (dartFrame != null) {
_computedFrames.add(dartFrame);
} else {
final jsFrame = debugger.calculateJsFrameFor(callFrame, _frameIndex);
if (jsFrame != null) {
_jsFrames.add(jsFrame);
}
}
_frameIndex++;
}
}

Expand Down Expand Up @@ -103,13 +115,23 @@ class FrameComputer {

final frame = await debugger.calculateDartFrameFor(
tempWipFrame,
_frameIndex++,
_frameIndex,
populateVariables: false,
);
if (frame != null) {
frame.kind = FrameKind.kAsyncCausal;
_computedFrames.add(frame);
} else {
final jsFrame = debugger.calculateJsFrameFor(
tempWipFrame,
_frameIndex,
isAsync: true,
);
if (jsFrame != null) {
_jsFrames.add(jsFrame);
}
}
_frameIndex++;
}
}

Expand Down
40 changes: 38 additions & 2 deletions dwds/test/debugger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ void main() async {

/// Test that we get expected variable values from a hard-coded
/// stack frame.
test('frames 1', () async {
final stackComputer = FrameComputer(debugger, frames1);
test('Only Dart frames', () async {
final stackComputer = FrameComputer(debugger, dartFrames);
final frames = await stackComputer.calculateFrames();
expect(frames, isNotNull);
expect(frames, isNotEmpty);
Expand All @@ -112,6 +112,42 @@ void main() async {
expect(frame1Variables, ['a', 'b']);
});

/// Test that we get JS frames if only JS frames are in the stack trace:
test('Only JS frames', () async {
final stackComputer = FrameComputer(debugger, jsFrames);
final frames = await stackComputer.calculateFrames();

expect(frames, isNotNull);
expect(frames.length, equals(3));

final firstFrame = frames[0];
expect(firstFrame.code?.kind, equals('Native'));
expect(firstFrame.code?.name, equals('createTimer'));

final secondFrame = frames[1];
expect(secondFrame.code?.kind, equals('Native'));
expect(secondFrame.code?.name, equals('rootRun'));

final thirdFrame = frames[2];
expect(thirdFrame.code?.kind, equals('Native'));
expect(thirdFrame.code?.name, equals('runGuarded'));
});

/// Test that we get only Dart frames if both Dart and JS frames are in the stack trace:
test('Dart and JS frames', () async {
final stackComputer = FrameComputer(debugger, dartAndJsFrames);
final frames = await stackComputer.calculateFrames();

expect(frames, isNotNull);
expect(frames, isNotEmpty);

final jsFrames = frames.where((frame) => frame.code?.kind == 'Native');
final dartFrames = frames.where((frame) => frame.code?.kind == 'Dart');

expect(jsFrames, isEmpty);
expect(dartFrames, isNotEmpty);
});

test('creates async frames', () async {
final stackComputer = FrameComputer(
debugger,
Expand Down
4 changes: 2 additions & 2 deletions dwds/test/extension_debugger_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ void main() async {
test('an ExtensionEvent', () async {
final extensionEvent = ExtensionEvent((b) => b
..method = jsonEncode('Debugger.paused')
..params = jsonEncode(frames1Json[0]));
..params = jsonEncode(dartFramesJson[0]));
connection.controllerIncoming.sink
.add(jsonEncode(serializers.serialize(extensionEvent)));
final wipEvent = await extensionDebugger.onNotification.first;
expect(wipEvent.method, 'Debugger.paused');
expect(wipEvent.params, frames1Json[0]);
expect(wipEvent.params, dartFramesJson[0]);
});

test('a BatchedEvents', () async {
Expand Down
42 changes: 40 additions & 2 deletions dwds/test/fixtures/debugger_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
///
/// This is taken from a real run, but truncated to two levels of scope and one
/// level of stack.
List<WipCallFrame> frames1 = frames1Json.map(WipCallFrame.new).toList();
List<WipCallFrame> dartFrames = dartFramesJson.map(WipCallFrame.new).toList();

List<Map<String, dynamic>> frames1Json = [
List<WipCallFrame> jsFrames = jsFramesJson.map(WipCallFrame.new).toList();

List<WipCallFrame> dartAndJsFrames =
[...dartFramesJson, ...jsFramesJson].map(WipCallFrame.new).toList();

List<Map<String, dynamic>> dartFramesJson = [
{
"callFrameId": "{\"ordinal\":0,\"injectedScriptId\":2}",
"functionName": "",
Expand Down Expand Up @@ -86,6 +91,39 @@ List<Map<String, dynamic>> frames1Json = [
}
];

final jsFramesJson = [
{
"url": "http://localhost:63691/dwds/src/injected/client.js",
"functionName": "createTimer",
"location": {
"scriptId": "239",
"lineNumber": 19833,
"columnNumber": 18,
},
"scopeChain": []
},
{
"url": "http://localhost:63691/dwds/src/injected/client.js",
"functionName": "rootRun",
"location": {
"scriptId": "239",
"lineNumber": 12193,
"columnNumber": 2,
},
"scopeChain": []
},
{
"url": "http://localhost:63691/dwds/src/injected/client.js",
"functionName": "runGuarded",
"location": {
"scriptId": "239",
"lineNumber": 13008,
"columnNumber": 13,
},
"scopeChain": []
},
];

/// Data in the form returned from getProperties called twice on successive
/// elements of a scope chain.
///
Expand Down