Merge pull request #26 from google/wip_0.3.0

expose log and runtime domain; deprecate console
diff --git a/changelog.md b/changelog.md
index c59af54..356d69e 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,12 @@
 # webkit_inspection_protocol.dart
 
+## 0.3.0
+- Expose the `runtime` domain.
+- Expose the `log` domain.
+- Deprecated the `console` domain.
+- Fix a bug in `Page.reload()`.
+- Remove the use of parts.
+
 ## 0.2.2
 - Make the package strong mode clean.
 
diff --git a/lib/src/console.dart b/lib/src/console.dart
index df87c3c..ec69683 100644
--- a/lib/src/console.dart
+++ b/lib/src/console.dart
@@ -1,27 +1,27 @@
 // Copyright 2015 Google. All rights reserved. Use of this source code is
 // governed by a BSD-style license that can be found in the LICENSE file.
 
-part of wip;
+import 'dart:async';
 
+import '../webkit_inspection_protocol.dart';
+
+@Deprecated('This domain is deprecated - use Runtime or Log instead')
 class WipConsole extends WipDomain {
   WipConsole(WipConnection connection) : super(connection);
 
-  Future enable() => _sendCommand('Console.enable');
-  Future disable() => _sendCommand('Console.disable');
-  Future clearMessages() => _sendCommand('Console.clearMessages');
+  Future enable() => sendCommand('Console.enable');
+  Future disable() => sendCommand('Console.disable');
+  Future clearMessages() => sendCommand('Console.clearMessages');
 
-  Stream<ConsoleMessageEvent> get onMessage => _eventStream(
+  Stream<ConsoleMessageEvent> get onMessage => eventStream(
       'Console.messageAdded',
       (WipEvent event) => new ConsoleMessageEvent(event));
-  Stream<ConsoleClearedEvent> get onCleared => _eventStream(
+  Stream<ConsoleClearedEvent> get onCleared => eventStream(
       'Console.messagesCleared',
       (WipEvent event) => new ConsoleClearedEvent(event));
 }
 
-/**
- * See [WipConsole.onMessage].
- */
-class ConsoleMessageEvent extends _WrappedWipEvent {
+class ConsoleMessageEvent extends WrappedWipEvent {
   ConsoleMessageEvent(WipEvent event) : super(event);
 
   Map get _message => params['message'];
@@ -42,7 +42,7 @@
   String toString() => text;
 }
 
-class ConsoleClearedEvent extends _WrappedWipEvent {
+class ConsoleClearedEvent extends WrappedWipEvent {
   ConsoleClearedEvent(WipEvent event) : super(event);
 }
 
diff --git a/lib/src/debugger.dart b/lib/src/debugger.dart
index f10d12a..980b500 100644
--- a/lib/src/debugger.dart
+++ b/lib/src/debugger.dart
@@ -1,7 +1,10 @@
 // Copyright 2015 Google. All rights reserved. Use of this source code is
 // governed by a BSD-style license that can be found in the LICENSE file.
 
-part of wip;
+import 'dart:async';
+import 'dart:collection';
+
+import '../webkit_inspection_protocol.dart';
 
 class WipDebugger extends WipDomain {
   final _scripts = <String, WipScript>{};
@@ -15,31 +18,33 @@
     });
   }
 
-  Future enable() => _sendCommand('Debugger.enable');
-  Future disable() => _sendCommand('Debugger.disable');
+  Future enable() => sendCommand('Debugger.enable');
+  Future disable() => sendCommand('Debugger.disable');
 
   Future<String> getScriptSource(String scriptId) async =>
-      (await _sendCommand('Debugger.getScriptSource', {'scriptId': scriptId}))
+      (await sendCommand('Debugger.getScriptSource',
+              params: {'scriptId': scriptId}))
           .result['scriptSource'];
 
-  Future pause() => _sendCommand('Debugger.pause');
-  Future resume() => _sendCommand('Debugger.resume');
+  Future pause() => sendCommand('Debugger.pause');
+  Future resume() => sendCommand('Debugger.resume');
 
-  Future stepInto() => _sendCommand('Debugger.stepInto');
-  Future stepOut() => _sendCommand('Debugger.stepOut');
-  Future stepOver() => _sendCommand('Debugger.stepOver');
+  Future stepInto() => sendCommand('Debugger.stepInto');
+  Future stepOut() => sendCommand('Debugger.stepOut');
+  Future stepOver() => sendCommand('Debugger.stepOver');
 
-  Future setPauseOnExceptions(PauseState state) => _sendCommand(
-      'Debugger.setPauseOnExceptions', {'state': _pauseStateToString(state)});
+  Future setPauseOnExceptions(PauseState state) =>
+      sendCommand('Debugger.setPauseOnExceptions',
+          params: {'state': _pauseStateToString(state)});
 
-  Stream<DebuggerPausedEvent> get onPaused => _eventStream(
+  Stream<DebuggerPausedEvent> get onPaused => eventStream(
       'Debugger.paused', (WipEvent event) => new DebuggerPausedEvent(event));
-  Stream<GlobalObjectClearedEvent> get onGlobalObjectCleared => _eventStream(
+  Stream<GlobalObjectClearedEvent> get onGlobalObjectCleared => eventStream(
       'Debugger.globalObjectCleared',
       (WipEvent event) => new GlobalObjectClearedEvent(event));
-  Stream<DebuggerResumedEvent> get onResumed => _eventStream(
+  Stream<DebuggerResumedEvent> get onResumed => eventStream(
       'Debugger.resumed', (WipEvent event) => new DebuggerResumedEvent(event));
-  Stream<ScriptParsedEvent> get onScriptParsed => _eventStream(
+  Stream<ScriptParsedEvent> get onScriptParsed => eventStream(
       'Debugger.scriptParsed',
       (WipEvent event) => new ScriptParsedEvent(event));
 
@@ -61,7 +66,7 @@
 
 enum PauseState { all, none, uncaught }
 
-class ScriptParsedEvent extends _WrappedWipEvent {
+class ScriptParsedEvent extends WrappedWipEvent {
   final WipScript script;
 
   ScriptParsedEvent(WipEvent event)
@@ -69,15 +74,15 @@
         super(event);
 }
 
-class GlobalObjectClearedEvent extends _WrappedWipEvent {
+class GlobalObjectClearedEvent extends WrappedWipEvent {
   GlobalObjectClearedEvent(WipEvent event) : super(event);
 }
 
-class DebuggerResumedEvent extends _WrappedWipEvent {
+class DebuggerResumedEvent extends WrappedWipEvent {
   DebuggerResumedEvent(WipEvent event) : super(event);
 }
 
-class DebuggerPausedEvent extends _WrappedWipEvent {
+class DebuggerPausedEvent extends WrappedWipEvent {
   DebuggerPausedEvent(WipEvent event) : super(event);
 
   String get reason => params['reason'];
diff --git a/lib/src/dom.dart b/lib/src/dom.dart
index edb32a5..f1c33b1 100644
--- a/lib/src/dom.dart
+++ b/lib/src/dom.dart
@@ -1,7 +1,10 @@
 // Copyright 2015 Google. All rights reserved. Use of this source code is
 // governed by a BSD-style license that can be found in the LICENSE file.
 
-part of wip;
+import 'dart:async';
+import 'dart:collection';
+
+import '../webkit_inspection_protocol.dart';
 
 /// Implementation of the
 /// https://developer.chrome.com/devtools/docs/protocol/1.1/dom
@@ -10,18 +13,18 @@
 
   Future<Map<String, String>> getAttributes(int nodeId) async {
     WipResponse resp =
-        await _sendCommand('DOM.getAttributes', {'nodeId': nodeId});
+        await sendCommand('DOM.getAttributes', params: {'nodeId': nodeId});
     return _attributeListToMap(resp.result['attributes']);
   }
 
   Future<Node> getDocument() async =>
-      new Node((await _sendCommand('DOM.getDocument')).result['root']);
+      new Node((await sendCommand('DOM.getDocument')).result['root']);
 
   Future<String> getOuterHtml(int nodeId) async =>
-      (await _sendCommand('DOM.getOuterHTML', {'nodeId': nodeId}))
+      (await sendCommand('DOM.getOuterHTML', params: {'nodeId': nodeId}))
           .result['root'];
 
-  Future hideHighlight() => _sendCommand('DOM.hideHighlight');
+  Future hideHighlight() => sendCommand('DOM.hideHighlight');
 
   Future highlightNode(int nodeId,
       {Rgba borderColor,
@@ -51,7 +54,7 @@
       params['highlightConfig']['showInfo'] = showInfo;
     }
 
-    return _sendCommand('DOM.highlightNode', params);
+    return sendCommand('DOM.highlightNode', params: params);
   }
 
   Future highlightRect(int x, int y, int width, int height,
@@ -71,7 +74,7 @@
       params['outlineColor'] = outlineColor;
     }
 
-    return _sendCommand('DOM.highlightRect', params);
+    return sendCommand('DOM.highlightRect', params: params);
   }
 
   Future<int> moveTo(int nodeId, int targetNodeId,
@@ -82,33 +85,35 @@
       params['insertBeforeNodeId'] = insertBeforeNodeId;
     }
 
-    var resp = await _sendCommand('DOM.moveTo', params);
+    var resp = await sendCommand('DOM.moveTo', params: params);
     return resp.result['nodeId'];
   }
 
   Future<int> querySelector(int nodeId, String selector) async {
-    var resp = await _sendCommand(
-        'DOM.querySelector', {'nodeId': nodeId, 'selector': selector});
+    var resp = await sendCommand('DOM.querySelector',
+        params: {'nodeId': nodeId, 'selector': selector});
     return resp.result['nodeId'];
   }
 
   Future<List<int>> querySelectorAll(int nodeId, String selector) async {
-    var resp = await _sendCommand(
-        'DOM.querySelectorAll', {'nodeId': nodeId, 'selector': selector});
+    var resp = await sendCommand('DOM.querySelectorAll',
+        params: {'nodeId': nodeId, 'selector': selector});
     return resp.result['nodeIds'];
   }
 
   Future removeAttribute(int nodeId, String name) =>
-      _sendCommand('DOM.removeAttribute', {'nodeId': nodeId, 'name': name});
+      sendCommand('DOM.removeAttribute',
+          params: {'nodeId': nodeId, 'name': name});
 
   Future removeNode(int nodeId) =>
-      _sendCommand('DOM.removeNode', {'nodeId': nodeId});
+      sendCommand('DOM.removeNode', params: {'nodeId': nodeId});
 
   Future requestChildNodes(int nodeId) =>
-      _sendCommand('DOM.requestChildNodes', {'nodeId': nodeId});
+      sendCommand('DOM.requestChildNodes', params: {'nodeId': nodeId});
 
   Future<int> requestNode(String objectId) async {
-    var resp = await _sendCommand('DOM.requestNode', {'objectId': objectId});
+    var resp =
+        await sendCommand('DOM.requestNode', params: {'objectId': objectId});
     return resp.result['nodeId'];
   }
 
@@ -118,60 +123,62 @@
       params['objectGroup'] = objectGroup;
     }
 
-    var resp = await _sendCommand('DOM.resolveNode', params);
+    var resp = await sendCommand('DOM.resolveNode', params: params);
     return new WipRemoteObject(resp.result['object']);
   }
 
   Future setAttributeValue(int nodeId, String name, String value) =>
-      _sendCommand('DOM.setAttributeValue',
-          {'nodeId': nodeId, 'name': name, 'value': value});
+      sendCommand('DOM.setAttributeValue',
+          params: {'nodeId': nodeId, 'name': name, 'value': value});
 
   Future setAttributesAsText(int nodeId, String text, {String name}) {
     var params = {'nodeId': nodeId, 'text': text};
     if (name != null) {
       params['name'] = name;
     }
-    return _sendCommand('DOM.setAttributeValue', params);
+    return sendCommand('DOM.setAttributeValue', params: params);
   }
 
   Future<int> setNodeName(int nodeId, String name) async {
-    var resp =
-        await _sendCommand('DOM.setNodeName', {'nodeId': nodeId, 'name': name});
+    var resp = await sendCommand('DOM.setNodeName',
+        params: {'nodeId': nodeId, 'name': name});
     return resp.result['nodeId'];
   }
 
   Future setNodeValue(int nodeId, String value) =>
-      _sendCommand('DOM.setNodeValue', {'nodeId': nodeId, 'value': value});
+      sendCommand('DOM.setNodeValue',
+          params: {'nodeId': nodeId, 'value': value});
 
-  Future setOuterHtml(int nodeId, String outerHtml) => _sendCommand(
-      'DOM.setOuterHTML', {'nodeId': nodeId, 'outerHtml': outerHtml});
+  Future setOuterHtml(int nodeId, String outerHtml) =>
+      sendCommand('DOM.setOuterHTML',
+          params: {'nodeId': nodeId, 'outerHtml': outerHtml});
 
-  Stream<AttributeModifiedEvent> get onAttributeModified => _eventStream(
+  Stream<AttributeModifiedEvent> get onAttributeModified => eventStream(
       'DOM.attributeModified',
       (WipEvent event) => new AttributeModifiedEvent(event));
-  Stream<AttributeRemovedEvent> get onAttributeRemoved => _eventStream(
+  Stream<AttributeRemovedEvent> get onAttributeRemoved => eventStream(
       'DOM.attributeRemoved',
       (WipEvent event) => new AttributeRemovedEvent(event));
-  Stream<CharacterDataModifiedEvent> get onCharacterDataModified =>
-      _eventStream('DOM.characterDataModified',
-          (WipEvent event) => new CharacterDataModifiedEvent(event));
-  Stream<ChildNodeCountUpdatedEvent> get onChildNodeCountUpdated =>
-      _eventStream('DOM.childNodeCountUpdated',
-          (WipEvent event) => new ChildNodeCountUpdatedEvent(event));
-  Stream<ChildNodeInsertedEvent> get onChildNodeInserted => _eventStream(
+  Stream<CharacterDataModifiedEvent> get onCharacterDataModified => eventStream(
+      'DOM.characterDataModified',
+      (WipEvent event) => new CharacterDataModifiedEvent(event));
+  Stream<ChildNodeCountUpdatedEvent> get onChildNodeCountUpdated => 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(
+  Stream<ChildNodeRemovedEvent> get onChildNodeRemoved => eventStream(
       'DOM.childNodeRemoved',
       (WipEvent event) => new ChildNodeRemovedEvent(event));
-  Stream<DocumentUpdatedEvent> get onDocumentUpdated => _eventStream(
+  Stream<DocumentUpdatedEvent> get onDocumentUpdated => eventStream(
       'DOM.documentUpdated',
       (WipEvent event) => new DocumentUpdatedEvent(event));
-  Stream<SetChildNodesEvent> get onSetChildNodes => _eventStream(
+  Stream<SetChildNodesEvent> get onSetChildNodes => eventStream(
       'DOM.setChildNodes', (WipEvent event) => new SetChildNodesEvent(event));
 }
 
-class AttributeModifiedEvent extends _WrappedWipEvent {
+class AttributeModifiedEvent extends WrappedWipEvent {
   AttributeModifiedEvent(WipEvent event) : super(event);
 
   int get nodeId => params['nodeId'];
@@ -179,28 +186,28 @@
   String get value => params['value'];
 }
 
-class AttributeRemovedEvent extends _WrappedWipEvent {
+class AttributeRemovedEvent extends WrappedWipEvent {
   AttributeRemovedEvent(WipEvent event) : super(event);
 
   int get nodeId => params['nodeId'];
   String get name => params['name'];
 }
 
-class CharacterDataModifiedEvent extends _WrappedWipEvent {
+class CharacterDataModifiedEvent extends WrappedWipEvent {
   CharacterDataModifiedEvent(WipEvent event) : super(event);
 
   int get nodeId => params['nodeId'];
   String get characterData => params['characterData'];
 }
 
-class ChildNodeCountUpdatedEvent extends _WrappedWipEvent {
+class ChildNodeCountUpdatedEvent extends WrappedWipEvent {
   ChildNodeCountUpdatedEvent(WipEvent event) : super(event);
 
   int get nodeId => params['nodeId'];
   int get childNodeCount => params['childNodeCount'];
 }
 
-class ChildNodeInsertedEvent extends _WrappedWipEvent {
+class ChildNodeInsertedEvent extends WrappedWipEvent {
   ChildNodeInsertedEvent(WipEvent event) : super(event);
 
   int get parentNodeId => params['parentNodeId'];
@@ -214,18 +221,18 @@
   }
 }
 
-class ChildNodeRemovedEvent extends _WrappedWipEvent {
+class ChildNodeRemovedEvent extends WrappedWipEvent {
   ChildNodeRemovedEvent(WipEvent event) : super(event);
 
   int get parentNodeId => params['parentNodeId'];
   int get nodeId => params['nodeId'];
 }
 
-class DocumentUpdatedEvent extends _WrappedWipEvent {
+class DocumentUpdatedEvent extends WrappedWipEvent {
   DocumentUpdatedEvent(WipEvent event) : super(event);
 }
 
-class SetChildNodesEvent extends _WrappedWipEvent {
+class SetChildNodesEvent extends WrappedWipEvent {
   SetChildNodesEvent(WipEvent event) : super(event);
 
   int get nodeId => params['parentId'];
diff --git a/lib/src/log.dart b/lib/src/log.dart
new file mode 100644
index 0000000..17bc49e
--- /dev/null
+++ b/lib/src/log.dart
@@ -0,0 +1,39 @@
+// Copyright 2017 Google. All rights reserved. Use of this source code is
+// governed by a BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import '../webkit_inspection_protocol.dart';
+
+class WipLog extends WipDomain {
+  WipLog(WipConnection connection) : super(connection);
+
+  Future enable() => sendCommand('Log.enable');
+  Future disable() => sendCommand('Log.disable');
+
+  Stream<LogEntry> get onEntryAdded =>
+      eventStream('Log.entryAdded', (WipEvent event) => new LogEntry(event));
+}
+
+class LogEntry extends WrappedWipEvent {
+  LogEntry(WipEvent event) : super(event);
+
+  Map<String, dynamic> get _entry => params['entry'];
+
+  /// Log entry source. Allowed values: xml, javascript, network, storage,
+  /// appcache, rendering, security, deprecation, worker, violation,
+  /// intervention, other.
+  String get source => _entry['source'];
+
+  /// Log entry severity. Allowed values: verbose, info, warning, error.
+  String get level => _entry['level'];
+
+  /// Logged text.
+  String get text => _entry['text'];
+
+  /// URL of the resource if known.
+  @optional
+  String get url => _entry['url'];
+
+  String toString() => text;
+}
diff --git a/lib/src/page.dart b/lib/src/page.dart
index 7bb20a1..8959a16 100644
--- a/lib/src/page.dart
+++ b/lib/src/page.dart
@@ -1,15 +1,18 @@
 // Copyright 2015 Google. All rights reserved. Use of this source code is
 // governed by a BSD-style license that can be found in the LICENSE file.
 
-part of wip;
+import 'dart:async';
+
+import '../webkit_inspection_protocol.dart';
 
 class WipPage extends WipDomain {
   WipPage(WipConnection connection) : super(connection);
 
-  Future enable() => _sendCommand('Page.enable');
-  Future disable() => _sendCommand('Page.disable');
+  Future enable() => sendCommand('Page.enable');
+  Future disable() => sendCommand('Page.disable');
 
-  Future navigate(String url) => _sendCommand('Page.navigate', {'url': url});
+  Future navigate(String url) =>
+      sendCommand('Page.navigate', params: {'url': url});
 
   Future reload({bool ignoreCache, String scriptToEvaluateOnLoad}) {
     var params = {};
@@ -22,6 +25,6 @@
       params['scriptToEvaluateOnLoad'] = scriptToEvaluateOnLoad;
     }
 
-    return _sendCommand('Page.navigate', params);
+    return sendCommand('Page.reload', params: params);
   }
 }
diff --git a/lib/src/runtime.dart b/lib/src/runtime.dart
new file mode 100644
index 0000000..c14f7c5
--- /dev/null
+++ b/lib/src/runtime.dart
@@ -0,0 +1,153 @@
+// Copyright 2015 Google. All rights reserved. Use of this source code is
+// governed by a BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:math';
+
+import '../webkit_inspection_protocol.dart';
+
+class WipRuntime extends WipDomain {
+  WipRuntime(WipConnection connection) : super(connection);
+
+  Future enable() => sendCommand('Runtime.enable');
+  Future disable() => sendCommand('Runtime.disable');
+
+  Stream<ConsoleAPIEvent> get onConsoleAPICalled => eventStream(
+      'Runtime.consoleAPICalled',
+      (WipEvent event) => new ConsoleAPIEvent(event));
+
+  Stream<ExceptionThrownEvent> get onExceptionThrown => eventStream(
+      'Runtime.exceptionThrown',
+      (WipEvent event) => new ExceptionThrownEvent(event));
+}
+
+class ConsoleAPIEvent extends WrappedWipEvent {
+  ConsoleAPIEvent(WipEvent event) : super(event);
+
+  /// Type of the call. Allowed values: log, debug, info, error, warning, dir,
+  /// dirxml, table, trace, clear, startGroup, startGroupCollapsed, endGroup,
+  /// assert, profile, profileEnd.
+  String get type => params['type'];
+
+  /// Call arguments.
+  List<RemoteObject> get args =>
+      (params['args'] as List).map((m) => new RemoteObject(m)).toList();
+
+  // TODO: stackTrace, StackTrace, Stack trace captured when the call was made.
+}
+
+class ExceptionThrownEvent extends WrappedWipEvent {
+  ExceptionThrownEvent(WipEvent event) : super(event);
+
+  /// Timestamp of the exception.
+  int get timestamp => params['timestamp'];
+
+  ExceptionDetails get exceptionDetails =>
+      new ExceptionDetails(params['exceptionDetails']);
+}
+
+class ExceptionDetails {
+  final Map<String, dynamic> _map;
+
+  ExceptionDetails(this._map);
+
+  /// Exception id.
+  int get exceptionId => _map['exceptionId'];
+
+  /// Exception text, which should be used together with exception object when
+  /// available.
+  String get text => _map['text'];
+
+  /// Line number of the exception location (0-based).
+  int get lineNumber => _map['lineNumber'];
+
+  /// Column number of the exception location (0-based).
+  int get columnNumber => _map['columnNumber'];
+
+  /// URL of the exception location, to be used when the script was not
+  /// reported.
+  @optional
+  String get url => _map['url'];
+
+  /// Script ID of the exception location.
+  @optional
+  String get scriptId => _map['scriptId'];
+
+  /// JavaScript stack trace if available.
+  @optional
+  StackTrace get stackTrace =>
+      _map['stackTrace'] == null ? null : new StackTrace(_map['stackTrace']);
+
+  /// Exception object if available.
+  @optional
+  RemoteObject get exception =>
+      _map['exception'] == null ? null : new RemoteObject(_map['exception']);
+
+  String toString() => text;
+}
+
+class StackTrace {
+  final Map<String, dynamic> _map;
+
+  StackTrace(this._map);
+
+  /// String label of this stack trace. For async traces this may be a name of
+  /// the function that initiated the async call.
+  @optional
+  String get description => _map['description'];
+
+  List<CallFrame> get callFrames =>
+      (_map['callFrames'] as List).map((m) => new CallFrame(m)).toList();
+
+  // TODO: parent, StackTrace, Asynchronous JavaScript stack trace that preceded
+  // this stack, if available.
+
+  List<String> printFrames() {
+    List<CallFrame> frames = callFrames;
+
+    int width = frames.fold(0, (int val, CallFrame frame) {
+      return max(val, frame.functionName.length);
+    });
+
+    return frames.map((CallFrame frame) {
+      return '${frame.functionName}()'.padRight(width + 2) +
+          ' ${frame.url} ${frame.lineNumber}:${frame.columnNumber}';
+    }).toList();
+  }
+
+  String toString() => callFrames.map((f) => '  $f').join('\n');
+}
+
+class CallFrame {
+  final Map<String, dynamic> _map;
+
+  CallFrame(this._map);
+
+  /// JavaScript function name.
+  String get functionName => _map['functionName'];
+
+  /// JavaScript script id.
+  String get scriptId => _map['scriptId'];
+
+  /// JavaScript script name or url.
+  String get url => _map['url'];
+
+  /// JavaScript script line number (0-based).
+  int get lineNumber => _map['lineNumber'];
+
+  /// JavaScript script column number (0-based).
+  int get columnNumber => _map['columnNumber'];
+
+  String toString() => '$functionName() ($url $lineNumber:$columnNumber)';
+}
+
+class RemoteObject {
+  final Map<String, dynamic> _map;
+
+  RemoteObject(this._map);
+
+  String get type => _map['type'];
+  String get value => _map['value'];
+
+  String toString() => '$type $value';
+}
diff --git a/lib/webkit_inspection_protocol.dart b/lib/webkit_inspection_protocol.dart
index 46ff1d9..1596c1b 100644
--- a/lib/webkit_inspection_protocol.dart
+++ b/lib/webkit_inspection_protocol.dart
@@ -6,25 +6,25 @@
  */
 library wip;
 
-import 'dart:async'
-    show
-        Completer,
-        EventSink,
-        Future,
-        Stream,
-        StreamController,
-        StreamTransformer;
-import 'dart:collection' show UnmodifiableListView, UnmodifiableMapView;
-import 'dart:convert' show JSON, UTF8;
+import 'dart:async';
+import 'dart:convert';
 import 'dart:io' show HttpClient, HttpClientResponse, WebSocket;
 
 import 'package:logging/logging.dart' show Logger;
-import 'package:logging/logging.dart';
 
-part 'src/console.dart';
-part 'src/debugger.dart';
-part 'src/dom.dart';
-part 'src/page.dart';
+import 'src/console.dart';
+import 'src/debugger.dart';
+import 'src/dom.dart';
+import 'src/log.dart';
+import 'src/page.dart';
+import 'src/runtime.dart';
+
+export 'src/console.dart';
+export 'src/debugger.dart';
+export 'src/dom.dart';
+export 'src/log.dart';
+export 'src/page.dart';
+export 'src/runtime.dart';
 
 /**
  * A class to connect to a Chrome instance and reflect on its available tabs.
@@ -119,7 +119,7 @@
  * A Webkit Inspection Protocol (WIP) connection.
  */
 class WipConnection {
-  static final _log = new Logger('WipConnection');
+  static final _logger = new Logger('WipConnection');
 
   /**
    * The WebSocket URL.
@@ -130,19 +130,31 @@
 
   int _nextId = 0;
 
-  var _console;
+  WipConsole _console; // ignore: deprecated_member_use
+  @Deprecated('This domain is deprecated - use Runtime or Log instead')
   WipConsole get console => _console;
-  var _debugger;
+
+  WipDebugger _debugger;
   WipDebugger get debugger => _debugger;
-  var _dom;
+
+  WipDom _dom;
   WipDom get dom => _dom;
-  var _page;
+
+  WipPage _page;
   WipPage get page => _page;
 
-  final _completers = <int, Completer<WipResponse>>{};
+  WipLog _log;
+  WipLog get log => _log;
 
-  final _closeController = new StreamController<WipConnection>.broadcast();
-  final _notificationController = new StreamController<WipEvent>.broadcast();
+  WipRuntime _runtime;
+  WipRuntime get runtime => _runtime;
+
+  final Map _completers = <int, Completer<WipResponse>>{};
+
+  final StreamController _closeController =
+      new StreamController<WipConnection>.broadcast();
+  final StreamController _notificationController =
+      new StreamController<WipEvent>.broadcast();
 
   static Future<WipConnection> connect(String url) {
     return WebSocket.connect(url).then((socket) {
@@ -151,10 +163,12 @@
   }
 
   WipConnection._(this.url, this._ws) {
-    _console = new WipConsole(this);
+    _console = new WipConsole(this); // ignore: deprecated_member_use
     _debugger = new WipDebugger(this);
     _dom = new WipDom(this);
     _page = new WipPage(this);
+    _log = new WipLog(this);
+    _runtime = new WipRuntime(this);
 
     _ws.listen((data) {
       var json = JSON.decode(data);
@@ -176,7 +190,7 @@
 
   Future<WipResponse> sendCommand(String method,
       [Map<String, dynamic> params]) {
-    _log.finest('Sending command: $method($params)');
+    _logger.finest('Sending command: $method($params)');
     var completer = new Completer<WipResponse>();
     var json = {'id': _nextId++, 'method': method};
     if (params != null) {
@@ -188,7 +202,7 @@
   }
 
   void _handleNotification(Map<String, dynamic> json) {
-    _log.finest('Received notification: $json');
+    _logger.finest('Received notification: $json');
     _notificationController.add(new WipEvent(json));
   }
 
@@ -196,10 +210,10 @@
     var completer = _completers.remove(event['id']);
 
     if (event.containsKey('error')) {
-      _log.info('Received error: $event');
+      _logger.info('Received error: $event');
       completer.completeError(new WipError(event));
     } else {
-      _log.finest('Received response: $event');
+      _logger.finest('Received response: $event');
       completer.complete(new WipResponse(event));
     }
   }
@@ -246,9 +260,10 @@
 
 typedef WipEvent WipEventTransformer(WipEvent event);
 
-abstract class WipDomain {
-  static final _log = new Logger('WipDomain');
+/// @optional
+const String optional = 'optional';
 
+abstract class WipDomain {
   Map<String, Stream> _eventStreams = {};
 
   final WipConnection connection;
@@ -262,27 +277,31 @@
     }).bind(connection.onClose);
   }
 
-  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));
+  Stream<WipEvent> eventStream(String method, WipEventTransformer transformer) {
+    return _eventStreams.putIfAbsent(
+      method,
+      () => new StreamTransformer.fromHandlers(
+            handleData: (WipEvent event, EventSink<WipEvent> sink) {
+              if (event.method == method) {
+                sink.add(transformer(event));
+              }
+            },
+          ).bind(connection.onNotification),
+    );
+  }
 
-  Future<WipResponse> _sendCommand(String method,
-          [Map<String, dynamic> params]) =>
-      connection.sendCommand(method, params);
+  Future<WipResponse> sendCommand(
+    String method, {
+    Map<String, dynamic> params,
+  }) {
+    return connection.sendCommand(method, params);
+  }
 }
 
-class _WrappedWipEvent implements WipEvent {
+class WrappedWipEvent implements WipEvent {
   final WipEvent _wrapped;
 
-  _WrappedWipEvent(this._wrapped);
+  WrappedWipEvent(this._wrapped);
 
   @override
   String get method => _wrapped.method;
diff --git a/pubspec.yaml b/pubspec.yaml
index 725aacc..651f690 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: webkit_inspection_protocol
-version: 0.2.2
+version: 0.3.0
 description: A client for the Webkit Inspection Protocol (WIP).
 
 homepage: https://github.com/google/webkit_inspection_protocol.dart
diff --git a/test/console_test.dart b/test/console_test.dart
index 866c4fc..139e06b 100644
--- a/test/console_test.dart
+++ b/test/console_test.dart
@@ -13,7 +13,7 @@
 
 main() {
   group('WipConsole', () {
-    WipConsole console;
+    WipConsole console; // ignore: deprecated_member_use
     List<ConsoleMessageEvent> events = [];
     var subs = [];
 
@@ -32,6 +32,7 @@
     }
 
     setUp(() async {
+      // ignore: deprecated_member_use
       console = (await wipConnection).console;
       await console.clearMessages();
       events.clear();