Merge pull request #14 from DrMarcII/refactor

Refactor event handling/transforming for domains.
diff --git a/lib/src/console.dart b/lib/src/console.dart
index cd528f6..1b2e256 100644
--- a/lib/src/console.dart
+++ b/lib/src/console.dart
@@ -4,49 +4,18 @@
 part of wip;
 
 class WipConsole extends WipDomain {
-  final _messageController =
-      new StreamController<ConsoleMessageEvent>.broadcast();
-  final _clearedController = new StreamController.broadcast();
-
-  ConsoleMessageEvent _lastMessage;
-
-  WipConsole(WipConnection connection) : super(connection) {
-    connection._registerDomain('Console', this);
-
-    _register('Console.messageAdded', _messageAdded);
-    _register('Console.messageRepeatCountUpdated', _messageRepeatCountUpdated);
-    _register('Console.messagesCleared', _messagesCleared);
-  }
+  WipConsole(WipConnection connection) : super(connection);
 
   Future enable() => _sendCommand('Console.enable');
   Future disable() => _sendCommand('Console.disable');
   Future clearMessages() => _sendCommand('Console.clearMessages');
 
-  Stream<ConsoleMessageEvent> get onMessage => _messageController.stream;
-  Stream get onCleared => _clearedController.stream;
-
-  void _messageAdded(WipEvent event) {
-    _lastMessage = new ConsoleMessageEvent(event);
-    _messageController.add(_lastMessage);
-  }
-
-  void _messageRepeatCountUpdated(WipEvent event) {
-    if (_lastMessage != null) {
-      _lastMessage.params['repeatCount'] = event.params['count'];
-      _messageController.add(_lastMessage);
-    }
-  }
-
-  void _messagesCleared(WipEvent event) {
-    _lastMessage = null;
-    _clearedController.add(null);
-  }
-
-  @override
-  void close() {
-    _messageController.close();
-    _clearedController.close();
-  }
+  Stream<ConsoleMessageEvent> get onMessage => _eventStream(
+      'Console.messageAdded',
+      (WipEvent event) => new ConsoleMessageEvent(event));
+  Stream<ConsoleClearedEvent> get onCleared => _eventStream(
+      'Console.messagesCleared',
+      (WipEvent event) => new ConsoleClearedEvent(event));
 }
 
 /**
@@ -74,6 +43,10 @@
   String toString() => text;
 }
 
+class ConsoleClearedEvent extends _WrappedWipEvent {
+  ConsoleClearedEvent(WipEvent event) : super(event);
+}
+
 class WipConsoleCallFrame {
   final Map<String, dynamic> _map;
 
diff --git a/lib/src/debugger.dart b/lib/src/debugger.dart
index bc92596..7c70557 100644
--- a/lib/src/debugger.dart
+++ b/lib/src/debugger.dart
@@ -4,32 +4,23 @@
 part of wip;
 
 class WipDebugger extends WipDomain {
-  final _pausedController =
-      new StreamController<DebuggerPausedEvent>.broadcast();
-  final _resumedController = new StreamController.broadcast();
-
-  Map<String, WipScript> _scripts = {};
+  final _scripts = <String, WipScript>{};
 
   WipDebugger(WipConnection connection) : super(connection) {
-    connection._registerDomain('Debugger', this);
-
-    // TODO:
-    //_register('Debugger.breakpointResolved', _breakpointResolved);
-    _register('Debugger.globalObjectCleared', _globalObjectCleared);
-    _register('Debugger.paused', _paused);
-    _register('Debugger.resumed', _resumed);
-    //_register('Debugger.scriptFailedToParse', _scriptFailedToParse);
-    _register('Debugger.scriptParsed', _scriptParsed);
+    onScriptParsed.listen((event) {
+      _scripts[event.script.scriptId] = event.script;
+    });
+    onGlobalObjectCleared.listen((_) {
+      _scripts.clear();
+    });
   }
 
   Future enable() => _sendCommand('Debugger.enable');
   Future disable() => _sendCommand('Debugger.disable');
 
-  Future<String> getScriptSource(String scriptId) async {
-    var resp =
-        await _sendCommand('Debugger.getScriptSource', {'scriptId': scriptId});
-    return resp.result['scriptSource'];
-  }
+  Future<String> getScriptSource(String scriptId) async => (await _sendCommand(
+          'Debugger.getScriptSource', {'scriptId': scriptId})).result[
+      'scriptSource'];
 
   Future pause() => _sendCommand('Debugger.pause');
   Future resume() => _sendCommand('Debugger.resume');
@@ -38,39 +29,52 @@
   Future stepOut() => _sendCommand('Debugger.stepOut');
   Future stepOver() => _sendCommand('Debugger.stepOver');
 
-  /**
-   * State should be one of "all", "none", or "uncaught".
-   */
-  Future setPauseOnExceptions(String state) =>
-      _sendCommand('Debugger.setPauseOnExceptions', {'state': state});
+  Future setPauseOnExceptions(PauseState state) => _sendCommand(
+      'Debugger.setPauseOnExceptions', {'state': _pauseStateToString(state)});
 
-  Stream get onPaused => _pausedController.stream;
-  Stream get onResumed => _resumedController.stream;
+  Stream<DebuggerPausedEvent> get onPaused => _eventStream(
+      'Debugger.paused', (WipEvent event) => new DebuggerPausedEvent(event));
+  Stream<GlobalObjectClearedEvent> get onGlobalObjectCleared => _eventStream(
+      'Debugger.globalObjectCleared',
+      (WipEvent event) => new GlobalObjectClearedEvent(event));
+  Stream<DebuggerResumedEvent> get onResumed => _eventStream(
+      'Debugger.resumed', (WipEvent event) => new DebuggerResumedEvent(event));
+  Stream<ScriptParsedEvent> get onScriptParsed => _eventStream(
+      'Debugger.scriptParsed',
+      (WipEvent event) => new ScriptParsedEvent(event));
 
-  WipScript getScript(String scriptId) => _scripts[scriptId];
+  Map<String, WipScript> get scripts => new UnmodifiableMapView(_scripts);
+}
 
-  void _globalObjectCleared(WipEvent event) {
-    _scripts.clear();
+String _pauseStateToString(PauseState state) {
+  switch (state) {
+    case PauseState.all:
+      return 'all';
+    case PauseState.none:
+      return 'none';
+    case PauseState.uncaught:
+      return 'uncaught';
+    default:
+      throw new ArgumentError('unknown state: $state');
   }
+}
 
-  void _paused(WipEvent event) {
-    _pausedController.add(new DebuggerPausedEvent(event));
-  }
+enum PauseState { all, none, uncaught }
 
-  void _resumed(WipEvent event) {
-    _resumedController.add(null);
-  }
+class ScriptParsedEvent extends _WrappedWipEvent {
+  final WipScript script;
 
-  void _scriptParsed(WipEvent event) {
-    var script = new WipScript(event.params);
-    _scripts[script.scriptId] = script;
-  }
+  ScriptParsedEvent(WipEvent event)
+      : this.script = new WipScript(event.params),
+        super(event);
+}
 
-  @override
-  void close() {
-    _pausedController.close();
-    _resumedController.close();
-  }
+class GlobalObjectClearedEvent extends _WrappedWipEvent {
+  GlobalObjectClearedEvent(WipEvent event) : super(event);
+}
+
+class DebuggerResumedEvent extends _WrappedWipEvent {
+  DebuggerResumedEvent(WipEvent event) : super(event);
 }
 
 class DebuggerPausedEvent extends _WrappedWipEvent {
diff --git a/lib/src/dom.dart b/lib/src/dom.dart
index b3d0c62..db0bf05 100644
--- a/lib/src/dom.dart
+++ b/lib/src/dom.dart
@@ -6,18 +6,7 @@
 /// Implementation of the
 /// https://developer.chrome.com/devtools/docs/protocol/1.1/dom
 class WipDom extends WipDomain {
-  WipDom(WipConnection connection) : super(connection) {
-    connection._registerDomain('DOM', this);
-
-    _register('DOM.attributeModified', _attributeModified);
-    _register('DOM.attributeRemoved', _attributeRemoved);
-    _register('DOM.characterDataModified', _characterDataModified);
-    _register('DOM.childNodeCountUpdated', _childNodeCountUpdated);
-    _register('DOM.childNodeInserted', _childNodeInserted);
-    _register('DOM.childNodeRemoved', _childNodeRemoved);
-    _register('DOM.documentUpdated', _documentUpdated);
-    _register('DOM.setChildNodes', _setChildNodes);
-  }
+  WipDom(WipConnection connection) : super(connection);
 
   Future<Map<String, String>> getAttributes(int nodeId) async {
     WipResponse resp =
@@ -106,10 +95,13 @@
 
   Future removeAttribute(int nodeId, String name) =>
       _sendCommand('DOM.removeAttribute', {'nodeId': nodeId, 'name': name});
+
   Future removeNode(int nodeId) =>
       _sendCommand('DOM.removeNode', {'nodeId': nodeId});
+
   Future requestChildNodes(int nodeId) =>
       _sendCommand('DOM.requestChildNodes', {'nodeId': nodeId});
+
   Future<int> requestNode(String objectId) async {
     var resp = await _sendCommand('DOM.requestNode', {'objectId': objectId});
     return resp.result['nodeId'];
@@ -131,6 +123,7 @@
     'name': name,
     'value': value
   });
+
   Future setAttributesAsText(int nodeId, String text, {String name}) {
     var params = {'nodeId': nodeId, 'text': text};
     if (name != null) {
@@ -151,75 +144,29 @@
   Future setOuterHtml(int nodeId, String outerHtml) => _sendCommand(
       'DOM.setOuterHTML', {'nodeId': nodeId, 'outerHtml': outerHtml});
 
-  final _attributeModifiedController =
-      new StreamController<AttributeModifiedEvent>.broadcast();
-  Stream<AttributeModifiedEvent> get onAttributeModified =>
-      _attributeModifiedController.stream;
-  void _attributeModified(WipEvent event) =>
-      _attributeModifiedController.add(new AttributeModifiedEvent(event));
-
-  final _attributeRemovedController =
-      new StreamController<AttributeRemovedEvent>.broadcast();
-  Stream<AttributeRemovedEvent> get onAttributeRemoved =>
-      _attributeRemovedController.stream;
-  void _attributeRemoved(WipEvent event) =>
-      _attributeRemovedController.add(new AttributeRemovedEvent(event));
-
-  final _characterDataModifiedController =
-      new StreamController<CharacterDataModifiedEvent>.broadcast();
+  Stream<AttributeModifiedEvent> get onAttributeModified => _eventStream(
+      'DOM.attributeModified',
+      (WipEvent event) => new AttributeModifiedEvent(event));
+  Stream<AttributeRemovedEvent> get onAttributeRemoved => _eventStream(
+      'DOM.attributeRemoved',
+      (WipEvent event) => new AttributeRemovedEvent(event));
   Stream<CharacterDataModifiedEvent> get onCharacterDataModified =>
-      _characterDataModifiedController.stream;
-  void _characterDataModified(WipEvent event) =>
-      _characterDataModifiedController
-          .add(new CharacterDataModifiedEvent(event));
-
-  final _childNodeCountUpdatedController =
-      new StreamController<ChildNodeCountUpdatedEvent>.broadcast();
+      _eventStream('DOM.characterDataModified',
+          (WipEvent event) => new CharacterDataModifiedEvent(event));
   Stream<ChildNodeCountUpdatedEvent> get onChildNodeCountUpdated =>
-      _childNodeCountUpdatedController.stream;
-  void _childNodeCountUpdated(WipEvent event) =>
-      _childNodeCountUpdatedController
-          .add(new ChildNodeCountUpdatedEvent(event));
-
-  final _childNodeInsertedController =
-      new StreamController<ChildNodeInsertedEvent>.broadcast();
-  Stream<ChildNodeInsertedEvent> get onChildNodeInserted =>
-      _childNodeInsertedController.stream;
-  void _childNodeInserted(WipEvent event) =>
-      _childNodeInsertedController.add(new ChildNodeInsertedEvent(event));
-
-  final _childNodeRemovedController =
-      new StreamController<ChildNodeRemovedEvent>.broadcast();
-  Stream<ChildNodeRemovedEvent> get onChildNodeRemoved =>
-      _childNodeRemovedController.stream;
-  void _childNodeRemoved(WipEvent event) =>
-      _childNodeRemovedController.add(new ChildNodeRemovedEvent(event));
-
-  final _documentUpdatedController =
-      new StreamController<DocumentUpdatedEvent>.broadcast();
-  Stream<DocumentUpdatedEvent> get onDocumentUpdated =>
-      _documentUpdatedController.stream;
-  void _documentUpdated(WipEvent event) =>
-      _documentUpdatedController.add(new DocumentUpdatedEvent(event));
-
-  final _setChildNodesController =
-      new StreamController<SetChildNodesEvent>.broadcast();
-  Stream<SetChildNodesEvent> get onSetChildNodes =>
-      _setChildNodesController.stream;
-  void _setChildNodes(WipEvent event) =>
-      _setChildNodesController.add(new SetChildNodesEvent(event));
-
-  @override
-  void close() {
-    _attributeModifiedController.close();
-    _attributeRemovedController.close();
-    _characterDataModifiedController.close();
-    _childNodeCountUpdatedController.close();
-    _childNodeInsertedController.close();
-    _childNodeRemovedController.close();
-    _documentUpdatedController.close();
-    _setChildNodesController.close();
-  }
+      _eventStream('DOM.childNodeCountUpdated',
+          (WipEvent event) => new ChildNodeCountUpdatedEvent(event));
+  Stream<ChildNodeInsertedEvent> get onChildNodeInserted => _eventStream(
+      'DOM.childNodeInserted',
+      (WipEvent event) => new ChildNodeInsertedEvent(event));
+  Stream<ChildNodeRemovedEvent> get onChildNodeRemoved => _eventStream(
+      'DOM.childNodeRemoved',
+      (WipEvent event) => new ChildNodeRemovedEvent(event));
+  Stream<DocumentUpdatedEvent> get onDocumentUpdated => _eventStream(
+      'DOM.documentUpdated',
+      (WipEvent event) => new DocumentUpdatedEvent(event));
+  Stream<SetChildNodesEvent> get onSetChildNodes => _eventStream(
+      'DOM.setChildNodes', (WipEvent event) => new SetChildNodesEvent(event));
 }
 
 class AttributeModifiedEvent extends _WrappedWipEvent {
diff --git a/lib/src/page.dart b/lib/src/page.dart
index 5305c80..7bb20a1 100644
--- a/lib/src/page.dart
+++ b/lib/src/page.dart
@@ -4,13 +4,7 @@
 part of wip;
 
 class WipPage extends WipDomain {
-  WipPage(WipConnection connection) : super(connection) {
-    connection._registerDomain('Page', this);
-
-    // TODO:
-    // Page.loadEventFired
-    // Page.domContentEventFired
-  }
+  WipPage(WipConnection connection) : super(connection);
 
   Future enable() => _sendCommand('Page.enable');
   Future disable() => _sendCommand('Page.disable');
@@ -30,7 +24,4 @@
 
     return _sendCommand('Page.navigate', params);
   }
-
-  @override
-  void close() {}
 }
diff --git a/lib/webkit_inspection_protocol.dart b/lib/webkit_inspection_protocol.dart
index b9f3ab1..4966fae 100644
--- a/lib/webkit_inspection_protocol.dart
+++ b/lib/webkit_inspection_protocol.dart
@@ -6,7 +6,14 @@
  */
 library wip;
 
-import 'dart:async' show Completer, Future, Stream, StreamController;
+import 'dart:async'
+    show
+        Completer,
+        EventSink,
+        Future,
+        Stream,
+        StreamController,
+        StreamTransformer;
 import 'dart:collection' show UnmodifiableMapView;
 import 'dart:convert' show JSON, UTF8;
 import 'dart:io' show HttpClient, HttpClientResponse, WebSocket;
@@ -131,8 +138,6 @@
   var _page;
   WipPage get page => _page;
 
-  final _domains = <String, WipDomain>{};
-
   final _completers = <int, Completer<WipResponse>>{};
 
   final _closeController = new StreamController<WipConnection>.broadcast();
@@ -168,12 +173,9 @@
 
   String toString() => url;
 
-  void _registerDomain(String domainId, WipDomain domain) {
-    _domains[domainId] = domain;
-  }
-
   Future<WipResponse> sendCommand(String method,
       [Map<String, dynamic> params]) {
+    _log.finest('Sending command: $method($params)');
     var completer = new Completer<WipResponse>();
     var json = {'id': _nextId++, 'method': method};
     if (params != null) {
@@ -185,26 +187,18 @@
   }
 
   void _handleNotification(Map<String, dynamic> json) {
-    var event = new WipEvent(json);
-    var domainId = event.method;
-    var index = domainId.indexOf('.');
-    if (index != -1) {
-      domainId = domainId.substring(0, index);
-    }
-    if (_domains.containsKey(domainId)) {
-      _domains[domainId]._handleNotification(event);
-    } else {
-      _log.warning('unhandled event notification: ${event.method}');
-    }
-    _notificationController.add(event);
+    _log.finest('Received notification: $json');
+    _notificationController.add(new WipEvent(json));
   }
 
   void _handleResponse(Map<String, dynamic> event) {
     var completer = _completers.remove(event['id']);
 
     if (event.containsKey('error')) {
+      _log.info('Received error: $event');
       completer.completeError(new WipError(event));
     } else {
+      _log.finest('Received response: $event');
       completer.complete(new WipResponse(event));
     }
   }
@@ -213,7 +207,6 @@
     _closeController.add(this);
     _closeController.close();
     _notificationController.close();
-    _domains.values.forEach((d) => d.close());
   }
 }
 
@@ -250,28 +243,36 @@
   String toString() => 'WipResponse $id: $result';
 }
 
-typedef WipEventCallback(WipEvent event);
+typedef WipEvent WipEventTransformer(WipEvent event);
 
 abstract class WipDomain {
-  Map<String, WipEventCallback> _callbacks = {};
+  static final _log = new Logger('WipDomain');
+
+  Map<String, Stream> _eventStreams = {};
 
   final WipConnection connection;
+  var _onClosed;
+  Stream<WipDomain> get onClosed => _onClosed;
 
-  WipDomain(this.connection);
-
-  void _register(String method, WipEventCallback callback) {
-    _callbacks[method] = callback;
+  WipDomain(WipConnection connection) : this.connection = connection {
+    this._onClosed = new StreamTransformer.fromHandlers(
+        handleData: (event, EventSink sink) {
+      sink.add(this);
+    }).bind(connection.onClose);
   }
 
-  void _handleNotification(WipEvent event) {
-    var f = _callbacks[event.method];
-    if (f != null) f(event);
-  }
+  Stream<WipEvent> _eventStream(
+      String method, WipEventTransformer transformer) => _eventStreams
+      .putIfAbsent(method, () => new StreamTransformer.fromHandlers(
+          handleData: (WipEvent event, EventSink<WipEvent> sink) {
+    if (event.method == method) {
+      _log.finest('Transforming $event to $method');
+      sink.add(transformer(event));
+    }
+  }).bind(connection.onNotification));
 
   Future<WipResponse> _sendCommand(String method,
       [Map<String, dynamic> params]) => connection.sendCommand(method, params);
-
-  void close();
 }
 
 class _WrappedWipEvent implements WipEvent {