Breakpoint support with refreshing (#454)
* Reload support for breakpoints
diff --git a/dwds/lib/src/chrome_proxy_service.dart b/dwds/lib/src/chrome_proxy_service.dart
index 6de1619..b2f129f 100644
--- a/dwds/lib/src/chrome_proxy_service.dart
+++ b/dwds/lib/src/chrome_proxy_service.dart
@@ -51,8 +51,7 @@
Debugger _debugger;
AppInspector _inspector;
- AppInspector _appInspectorProvider() =>
- _inspector ?? (throw StateError('Null AppInspetor.'));
+ AppInspector _appInspectorProvider() => _inspector;
StreamSubscription<ConsoleAPIEvent> _consoleSubscription;
diff --git a/dwds/lib/src/debugger.dart b/dwds/lib/src/debugger.dart
index e772fbe..ecc9bee 100644
--- a/dwds/lib/src/debugger.dart
+++ b/dwds/lib/src/debugger.dart
@@ -47,9 +47,6 @@
/// The scripts and sourcemaps for the application, both JS and Dart.
Sources sources;
- /// Mapping from Dart script IDs to their ScriptRefs.
- Map<String, ScriptRef> _scriptRefs;
-
/// The breakpoints we have set so far, indexable by either
/// Dart or JS ID.
_Breakpoints _breakpoints;
@@ -171,13 +168,14 @@
/// Note that line and column are Dart source locations and one-based.
Future<Breakpoint> addBreakpoint(String isolateId, String scriptId, int line,
{int column}) async {
- var isolate = _appInspectorProvider().isolate;
+ var inspector = _appInspectorProvider();
+ var isolate = inspector.isolate;
if (isolateId != isolate.id) {
throw ArgumentError.value(
isolateId, 'isolateId', 'Unrecognized isolate id');
}
- var dartScript = await _scriptWithId(isolateId, scriptId);
+ var dartScript = await inspector.scriptWithId(scriptId);
// TODO(401): Remove the additional parameter.
var dartUri =
DartUri(dartScript.uri, '${Uri.parse(_root).path}/garbage.dart');
@@ -235,19 +233,6 @@
return Success();
}
- /// Look up the script by id in an isolate.
- Future<ScriptRef> _scriptWithId(String isolateId, String scriptId) async {
- // TODO: Reduce duplication with _scriptRefs in mainProxy.
- if (_scriptRefs == null) {
- _scriptRefs = {};
- var scripts = await _appInspectorProvider().scriptRefs(isolateId);
- for (var script in scripts) {
- _scriptRefs[script.id] = script;
- }
- }
- return _scriptRefs[scriptId];
- }
-
/// Call the Chrome protocol setBreakpoint and return the breakpoint ID.
Future<String> _setBreakpoint(Location location) async {
// Location is 0 based according to:
@@ -378,7 +363,9 @@
/// Handles resume events coming from the Chrome connection.
Future<void> _resumeHandler(DebuggerResumedEvent e) async {
- var isolate = _appInspectorProvider().isolate;
+ // We can receive a resume event in the middle of a reload which will
+ // result in a null isolate.
+ var isolate = _appInspectorProvider()?.isolate;
if (isolate == null) return;
_pausedStack = null;
_streamNotify(
diff --git a/dwds/lib/src/inspector.dart b/dwds/lib/src/inspector.dart
index 3ea714c..865fcaf 100644
--- a/dwds/lib/src/inspector.dart
+++ b/dwds/lib/src/inspector.dart
@@ -339,6 +339,10 @@
return scripts;
}
+ /// Look up the script by id in an isolate.
+ Future<ScriptRef> scriptWithId(String scriptId) async =>
+ (await scriptRefs(isolate.id)).firstWhere((ref) => ref.id == scriptId);
+
/// Returns all libraryRefs in the app.
///
/// Note this can return a cached result.
diff --git a/dwds/lib/src/sources.dart b/dwds/lib/src/sources.dart
index f52e7bb..e572ab6 100644
--- a/dwds/lib/src/sources.dart
+++ b/dwds/lib/src/sources.dart
@@ -14,9 +14,6 @@
/// The scripts and sourcemaps for the application, both JS and Dart.
class Sources {
- /// Controller for a stream of events when a source map is loaded.
- final _sourceMapLoadedController = StreamController<String>.broadcast();
-
/// Map from Dart server path to tokenPosTable as defined in the
/// Dart VM Service Protocol:
/// https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md#script
@@ -28,6 +25,12 @@
/// Map from JS scriptId to all corresponding [Location] data.
final _scriptIdToLocation = <String, Set<Location>>{};
+ /// Map from JS source url to corresponding Dart server paths.
+ final _sourceToServerPaths = <String, Set<String>>{};
+
+ /// Map from JS source url to Chrome script ID.
+ final _sourceToScriptId = <String, String>{};
+
final AssetHandler _assetHandler;
Sources(this._assetHandler);
@@ -46,9 +49,13 @@
var script = e.script;
var sourceMapContents = await _sourceMap(script);
if (sourceMapContents == null) return;
+ _clearCacheFor(script);
+ _sourceToScriptId[script.url] = script.scriptId;
// This happens to be a [SingleMapping] today in DDC.
var mapping = parse(sourceMapContents);
if (mapping is SingleMapping) {
+ var serverPaths =
+ _sourceToServerPaths.putIfAbsent(script.url, () => Set());
// Create TokenPos for each entry in the source map.
for (var lineEntry in mapping.lines) {
for (var entry in lineEntry.entries) {
@@ -61,6 +68,7 @@
entry,
dartUri,
);
+ serverPaths.add(dartUri.serverPath);
_sourceToLocation
.putIfAbsent(dartUri.serverPath, () => Set())
.add(location);
@@ -69,11 +77,6 @@
.add(location);
}
}
-
- // Notify which Dart file's source maps have been parsed.
- for (var serverPath in _sourceToLocation.keys) {
- _sourceMapLoadedController.add(serverPath);
- }
}
}
@@ -106,8 +109,21 @@
return tokenPosTable;
}
+ void _clearCacheFor(WipScript script) {
+ var serverPaths = _sourceToServerPaths[script.url] ?? {};
+ for (var serverPath in serverPaths) {
+ _sourceToLocation.remove(serverPath);
+ _sourceToTokenPosTable.remove(serverPath);
+ }
+ // This is the previous script ID for the file with the same URL.
+ var scriptId = _sourceToScriptId[script.url];
+ _scriptIdToLocation.remove(scriptId);
+
+ _sourceToServerPaths.remove(script.url);
+ _sourceToScriptId.remove(script.url);
+ }
+
/// The source map for a DDC-compiled JS [script].
- // TODO(grouma) - Reuse this logic in `DartUri`.
Future<String> _sourceMap(WipScript script) {
var sourceMapUrl = script.sourceMapURL;
if (sourceMapUrl == null || !sourceMapUrl.endsWith('.ddc.js.map')) {
diff --git a/dwds/test/chrome_proxy_service_test.dart b/dwds/test/chrome_proxy_service_test.dart
index 348e6c4..4f1d09f 100644
--- a/dwds/test/chrome_proxy_service_test.dart
+++ b/dwds/test/chrome_proxy_service_test.dart
@@ -75,6 +75,21 @@
await service.removeBreakpoint(isolate.id, bp.id);
expect(isolate.breakpoints, isEmpty);
});
+
+ test('can add and remove after a refresh', () async {
+ var stream = service.onEvent('Isolate');
+ await context.webDriver.refresh();
+ // Wait for the refresh to propagate through.
+ await stream.firstWhere((e) => e.kind != EventKind.kIsolateStart);
+ var refreshedScriptList = await service.getScripts(isolate.id);
+ var refreshedMain = refreshedScriptList.scripts
+ .lastWhere((each) => each.uri.contains('main.dart'));
+ var bp = await service.addBreakpoint(isolate.id, refreshedMain.id, 21);
+ expect(isolate.breakpoints, [bp]);
+ expect(bp.id, '3');
+ await service.removeBreakpoint(isolate.id, bp.id);
+ expect(isolate.breakpoints, isEmpty);
+ });
});
group('callServiceExtension', () {
diff --git a/webdev/lib/src/serve/handlers/dev_handler.dart b/webdev/lib/src/serve/handlers/dev_handler.dart
index c83be0b..5d92326 100644
--- a/webdev/lib/src/serve/handlers/dev_handler.dart
+++ b/webdev/lib/src/serve/handlers/dev_handler.dart
@@ -157,7 +157,7 @@
.sendCommand('Target.createTarget', {
'newWindow': true,
'url': 'http://${_devTools.hostname}:${_devTools.port}'
- '/?uri=${appServices.debugService.wsUri}',
+ '/?hide=none&uri=${appServices.debugService.wsUri}',
});
} else if (message is ConnectRequest) {
if (appId != null) {
diff --git a/webdev/pubspec.yaml b/webdev/pubspec.yaml
index 98cd608..97e5e11 100644
--- a/webdev/pubspec.yaml
+++ b/webdev/pubspec.yaml
@@ -53,6 +53,6 @@
executables:
webdev:
-# dependency_overrides:
-# dwds:
-# path: ../dwds
+dependency_overrides:
+ dwds:
+ path: ../dwds