Wrap a zone exception handler around our Chrome event handlers (#732)

diff --git a/dwds/lib/src/debugging/debugger.dart b/dwds/lib/src/debugging/debugger.dart
index 4977c94..f88295f 100644
--- a/dwds/lib/src/debugging/debugger.dart
+++ b/dwds/lib/src/debugging/debugger.dart
@@ -5,7 +5,8 @@
 import 'dart:async';
 
 import 'package:logging/logging.dart';
-import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
+    hide StackTrace;
 
 import '../../asset_handler.dart';
 import '../../dwds.dart' show LogWriter;
@@ -20,8 +21,6 @@
 import 'remote_debugger.dart';
 import 'sources.dart';
 
-final _logger = Logger('Debugger');
-
 /// Converts from ExceptionPauseMode strings to [PauseState] enums.
 ///
 /// Values defined in:
@@ -33,6 +32,8 @@
 };
 
 class Debugger extends Domain {
+  static final logger = Logger('Debugger');
+
   final AssetHandler _assetHandler;
   final LogWriter _logWriter;
   final RemoteDebugger _remoteDebugger;
@@ -173,9 +174,13 @@
     // We must add a listener before enabling the debugger otherwise we will
     // miss events.
     // Allow a null debugger/connection for unit tests.
-    _remoteDebugger?.onScriptParsed?.listen(sources.scriptParsed);
-    _remoteDebugger?.onPaused?.listen(_pauseHandler);
-    _remoteDebugger?.onResumed?.listen(_resumeHandler);
+    runZoned(() {
+      _remoteDebugger?.onScriptParsed?.listen(sources.scriptParsed);
+      _remoteDebugger?.onPaused?.listen(_pauseHandler);
+      _remoteDebugger?.onResumed?.listen(_resumeHandler);
+    }, onError: (e, StackTrace s) {
+      logger.warning('Error handling Chrome event', e, s);
+    });
 
     handleErrorIfPresent(await _remoteDebugger?.sendCommand('Page.enable'));
     handleErrorIfPresent(await _remoteDebugger?.enable() as WipResponse);
@@ -445,7 +450,7 @@
       _streamNotify('Debug', event);
     } on ChromeDebugException catch (e, s) {
       if (e.exception.description.contains('require is not defined')) {
-        _logger.warning(
+        logger.warning(
             'Error handling pause event, app does not appear to be loaded',
             e,
             s);
diff --git a/dwds/test/debugger_test.dart b/dwds/test/debugger_test.dart
index 71843bf..b0ac91e 100644
--- a/dwds/test/debugger_test.dart
+++ b/dwds/test/debugger_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 @TestOn('vm')
+import 'dart:async';
 import 'package:dwds/dwds.dart' show ModuleStrategy;
 import 'package:dwds/src/debugging/debugger.dart';
 import 'package:dwds/src/debugging/inspector.dart';
@@ -11,6 +12,8 @@
 import 'package:dwds/src/utilities/shared.dart';
 import 'package:source_maps/parser.dart';
 import 'package:test/test.dart';
+import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart'
+    show DebuggerPausedEvent;
 
 import 'fixtures/context.dart';
 import 'fixtures/debugger_data.dart';
@@ -20,11 +23,14 @@
 AppInspector inspector;
 Debugger debugger;
 FakeWebkitDebugger webkitDebugger;
+StreamController<DebuggerPausedEvent> pausedController;
 
 void main() async {
   setUpAll(() async {
     globalModuleStrategy = ModuleStrategy.requireJS;
     webkitDebugger = FakeWebkitDebugger();
+    pausedController = StreamController<DebuggerPausedEvent>();
+    webkitDebugger.onPaused = pausedController.stream;
     debugger = await Debugger.create(
       null,
       webkitDebugger,
@@ -65,4 +71,21 @@
     var frame1Variables = firstFrame.vars.map((each) => each.name).toList();
     expect(frame1Variables, ['a', 'b']);
   });
+
+  group('errors', () {
+    setUp(() {
+      // We need to provide an Isolate so that the code doesn't bail out on a null
+      // check before it has a chance to throw.
+      inspector = FakeInspector(fakeIsolate: simpleIsolate);
+    });
+
+    test('errors in the zone are caught and logged', () async {
+      // Add a DebuggerPausedEvent with a null parameter to provoke an error.
+      pausedController.sink.add(DebuggerPausedEvent(null));
+      expect(
+          Debugger.logger.onRecord,
+          emitsThrough(predicate(
+              (log) => log.message == 'Error handling Chrome event')));
+    });
+  });
 }
diff --git a/dwds/test/fixtures/fakes.dart b/dwds/test/fixtures/fakes.dart
index 2b5b915..3dfffc7 100644
--- a/dwds/test/fixtures/fakes.dart
+++ b/dwds/test/fixtures/fakes.dart
@@ -20,8 +20,26 @@
 
 import 'debugger_data.dart';
 
+/// Constructs a trivial Isolate we can use when we need to provide one but
+/// don't want go through initialization.
+Isolate get simpleIsolate => Isolate(
+      id: '1',
+      number: '1',
+      name: 'fake',
+      libraries: [],
+      exceptionPauseMode: 'abc',
+      breakpoints: [],
+      pauseOnExit: false,
+      pauseEvent: null,
+      startTime: 0,
+      livePorts: 0,
+      runnable: false,
+    );
+
 class FakeInspector extends Domain implements AppInspector {
-  FakeInspector() : super.forInspector();
+  FakeInspector({this.fakeIsolate}) : super.forInspector();
+
+  Isolate fakeIsolate;
 
   @override
   Object noSuchMethod(Invocation invocation) {
@@ -45,9 +63,9 @@
   @override
   Future<ScriptRef> scriptWithId(String scriptId) => null;
   @override
-  Isolate checkIsolate(String isolateId) => null;
+  Isolate checkIsolate(String isolateId) => fakeIsolate;
   @override
-  Isolate get isolate => null;
+  Isolate get isolate => fakeIsolate;
   @override
   IsolateRef get isolateRef => null;
   @override
@@ -119,7 +137,7 @@
   Stream<GlobalObjectClearedEvent> get onGlobalObjectCleared => null;
 
   @override
-  Stream<DebuggerPausedEvent> get onPaused => null;
+  Stream<DebuggerPausedEvent> onPaused;
 
   @override
   Stream<DebuggerResumedEvent> get onResumed => null;