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