Removing blocking read from StdioStepper.
Add additional commands to stepper.
General cleanup.
diff --git a/lib/support/stdio_stepper.dart b/lib/support/stdio_stepper.dart
index bb82918..cf44305 100644
--- a/lib/support/stdio_stepper.dart
+++ b/lib/support/stdio_stepper.dart
@@ -14,23 +14,37 @@
 
 library webdriver.support.stdio_stepper;
 
-import 'dart:async';
-import 'dart:convert';
-import 'dart:io';
+import 'dart:async' show Future, Stream, StreamController;
+import 'dart:convert' show Encoding, JSON;
+import 'dart:io' show exit, Stdin, stdin, SYSTEM_ENCODING;
 
 import 'package:webdriver/src/stepper.dart';
 
+LineReader _stdinLineReader;
+
+/// A [LineReader] instance connected to 'dart:io' [stdin].
+LineReader get stdinLineReader {
+  if (_stdinLineReader == null) {
+    _stdinLineReader = new LineReader(stdin);
+  }
+  return _stdinLineReader;
+}
+
 /// Provides a command line interface for stepping through or skipping
 /// WebDriver commands.
 class StdioStepper implements Stepper {
-  StdioStepper() {
-    stdin.lineMode = true;
-  }
+  bool enabled = true;
 
+  final LineReader _reader;
+
+  StdioStepper({LineReader reader})
+      : _reader = reader == null ? stdinLineReader : reader;
+
+  @override
   Future<bool> step(String method, String command, params) async {
+    if (!enabled) return true;
     print('$method $command(${JSON.encode(params)}):');
-    while (true) {
-      var command = stdin.readLineSync(retainNewlines: false).trim();
+    await for (String command in _reader.onLine) {
       switch (command) {
         case 'continue':
         case 'c':
@@ -45,16 +59,76 @@
         case 'h':
           _printUsage();
           break;
+        case 'disable':
+        case 'd':
+          enabled = false;
+          return true;
+        case 'quit':
+        case 'q':
+          exit(-1);
+          return false;
         default:
           print('invalid command: `$command` enter `h` or `help` for help.');
       }
     }
+    throw new Exception('stdin has been closed');
   }
 
   _printUsage() {
-    print('`c` or `continue`: to execute command');
+    print('`c` or `continue`: execute command');
     print('`s` or `skip`: skip command');
-    print('`b` or `break`: stop execution');
+    print('`d` or `disable`: disable stepper');
+    print('`b` or `break`: throw exception');
+    print('`q` or `quit`: terminate vm');
     print('`h` or `help`: display this message');
   }
 }
+
+/// Converts a Stream<List<int> | int> to Stream<String> that fires an event
+/// for every line of data in the original Stream.
+class LineReader {
+  static const CR = 13;
+  static const LF = 10;
+
+  bool _crPrevious = false;
+  final _bytes = <int>[];
+  final _controller = new StreamController<String>.broadcast();
+
+  final Encoding encoding;
+
+  /// Only encodings that are a superset of ASCII are supported
+  /// TODO(DrMarcII): Support arbitrary encodings
+  LineReader(Stream /* <List<int> | int> */ stream,
+      {this.encoding: SYSTEM_ENCODING}) {
+    if (stream is Stdin) {
+      stdin.lineMode = false;
+    }
+    stream.listen(_listen,
+        onDone: _controller.close, onError: _controller.addError);
+  }
+
+  void _listen(/* List<int> | int */ data) {
+    if (data is List) {
+      data.forEach(_addByte);
+    } else {
+      _addByte(data);
+    }
+  }
+
+  void _addByte(int byte) {
+    if (_crPrevious && byte == LF) {
+      _crPrevious = false;
+      return;
+    }
+    _crPrevious = byte == CR;
+    if (byte == CR || byte == LF) {
+      _controller.add(encoding.decode(_bytes));
+      _bytes.clear();
+    } else {
+      _bytes.add(byte);
+    }
+  }
+
+  /// A Stream that fires for each line of data.
+  Stream<String> get onLine => _controller.stream;
+}