Update WebDriver to use async/await/async*/yield/...

Remove touch APIs.

Remove PageLoader.

Miscellaneous cleanup.
diff --git a/lib/pageloader.dart b/lib/pageloader.dart
deleted file mode 100644
index 136d7a5..0000000
--- a/lib/pageloader.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-library pageloader;
-
-import 'dart:async';
-import 'dart:mirrors';
-
-import 'webdriver.dart';
-
-part 'src/pageloader/annotations.dart';
-part 'src/pageloader/core.dart';
diff --git a/lib/src/alert.dart b/lib/src/alert.dart
index aceac00..25792ef 100644
--- a/lib/src/alert.dart
+++ b/lib/src/alert.dart
@@ -10,8 +10,8 @@
   final String text;
 
 
-  Alert._(this.text, prefix, commandProcessor)
-      : super(prefix, commandProcessor);
+  Alert._(this.text, driver)
+      : super(driver, '');
 
   /**
    * Accepts the currently displayed alert (may not be the alert for which
@@ -20,7 +20,9 @@
    * Throws [WebDriverError] no such alert exception if there isn't currently an
    * alert.
    */
-  Future<Alert> accept() => _post('accept_alert').then((_) => this);
+  Future accept() async {
+    await _post('accept_alert');
+  }
 
   /**
    * Dismisses the currently displayed alert (may not be the alert for which
@@ -29,7 +31,9 @@
    * Throws [WebDriverError] no such alert exception if there isn't currently an
    * alert.
    */
-  Future<Alert> dismiss() => _post('dismiss_alert').then((_) => this);
+  Future dismiss() async {
+    await _post('dismiss_alert');
+  }
 
   /**
    * Sends keys to the currently displayed alert (may not be the alert for which
@@ -38,6 +42,7 @@
    * Throws [WebDriverError] no such alert exception if there isn't currently an
    * alert.
    */
-  Future<Alert> sendKeys(String keysToSend) =>
-      _post('alert_text', { 'text': keysToSend }).then((_) => this);
+  Future sendKeys(String keysToSend) async {
+    await _post('alert_text', { 'text': keysToSend });
+  }
 }
diff --git a/lib/src/command_processor.dart b/lib/src/command_processor.dart
index 6fb4c5f..63c1848 100644
--- a/lib/src/command_processor.dart
+++ b/lib/src/command_processor.dart
@@ -1,139 +1,50 @@
 part of webdriver;
 
-class CommandProcessor {
-  final Uri _uri;
+final ContentType _contentTypeJson = new ContentType("application", "json", charset: "utf-8");
+const _defaultDecoder = const JsonDecoder();
 
-  String get _host => _uri.host;
-  int get _port => _uri.port;
-  String get path => _uri.path;
+class _CommandProcessor {
 
-  String get url => _uri.toString();
+  final HttpClient client = new HttpClient();
 
-  CommandProcessor([
-      String host = 'localhost',
-      int port = 4444,
-      String path = '/wd/hub']) :
-      _uri = new Uri(scheme: 'http', host: host, port: port, path: path) {
-    assert(!this._host.isEmpty);
+  Object post(Uri uri, dynamic params, {Converter<String, dynamic> decoder: _defaultDecoder}) async {
+    HttpClientRequest request = await client.postUrl(uri);
+    _setUpRequest(request);
+    request.headers.contentType = _contentTypeJson;
+    request.encoding = UTF8;
+    request.write(JSON.encode(params));
+    return await _processResponse(await request.close(), decoder);
   }
 
-  void _failRequest(Completer completer, error, [stackTrace]) {
-    completer
-        .completeError(new WebDriverError(-1, error.toString()), stackTrace);
+  Object get(Uri uri, {Converter<String, dynamic> decoder: _defaultDecoder}) async {
+    HttpClientRequest request = await client.getUrl(uri);
+    _setUpRequest(request);
+    return await _processResponse(await request.close(), decoder);
   }
 
-  /**
-   * Execute a request to the WebDriver server. [httpMethod] should be
-   * one of 'GET', 'POST', or 'DELETE'. [command] is the text to append
-   * to the base URL path to get the full URL. [params] are the additional
-   * parameters. If a [List] or [Map] they will be posted as JSON parameters.
-   * If a number or string, "/params" is appended to the URL.
-   */
-  Future _serverRequest(String httpMethod, String command, {params}) {
-    const successCodes = const [ HttpStatus.OK, HttpStatus.NO_CONTENT ];
-    var completer = new Completer();
+  Object delete(Uri uri, {Converter<String, dynamic> decoder: _defaultDecoder}) async {
+    HttpClientRequest request = await client.deleteUrl(uri);
+    _setUpRequest(request);
+    return await _processResponse(await request.close(), decoder);
+  }
 
-    try {
-      var path = command;
-      if (params != null) {
-        if (params is num || params is String) {
-          path = '$path/$params';
-          params = null;
-        } else if (httpMethod != 'POST') {
-          throw new Exception(
-            'The http method called for ${command} is ${httpMethod} but it '
-            'must be POST if you want to pass the JSON params '
-            '${JSON.encode(params)}');
-        }
-      }
-
-      var client = new HttpClient();
-      client.open(httpMethod, _host, _port, path).then((req) {
-        req.followRedirects = false;
-        req.headers.add(HttpHeaders.ACCEPT, "application/json");
-        req.headers.contentType = _CONTENT_TYPE_JSON;
-        if (params != null) {
-          var body = UTF8.encode(JSON.encode(params));
-          req.contentLength = body.length;
-          req.add(body);
-        } else {
-          req.contentLength = 0;
-        }
-        return req.close();
-      }).then((HttpClientResponse rsp) {
-        return rsp.transform(new Utf8Decoder())
-            .fold(new StringBuffer(), (buffer, data) => buffer..write(data))
-            .then((StringBuffer buffer) {
-              // For some reason we get a bunch of NULs on the end
-              // of the text and the json.parse blows up on these, so
-              // strip them.
-              // These NULs can be seen in the TCP packet, so it is not
-              // an issue with character encoding; it seems to be a bug
-              // in WebDriver stack.
-              var results = buffer.toString()
-                  .replaceAll(new RegExp('\u{0}*\$'), '');
-
-              var status = 0;
-              var message = null;
-              var value = null;
-              // 4xx responses send plain text; others send JSON
-              if (HttpStatus.BAD_REQUEST <= rsp.statusCode
-                  && rsp.statusCode < HttpStatus.INTERNAL_SERVER_ERROR) {
-                if (rsp.statusCode == HttpStatus.NOT_FOUND) {
-                  status = 9; // UnkownCommand
-                } else {
-                  status = 13; // UnknownError
-                }
-                message = results;
-              } else if (!results.isEmpty) {
-                results = JSON.decode(results);
-                if (results.containsKey('status')) {
-                  status = results['status'];
-                }
-                if (results.containsKey('value')) {
-                  value = results['value'];
-                  if (value is Map && value.containsKey('message')) {
-                    message = results['message'];
-                  }
-                }
-              }
-
-              if (status != 0) {
-                completer.completeError(new WebDriverError(status, message));
-              } else if (!successCodes.contains(rsp.statusCode)) {
-                completer.completeError(new WebDriverError(-1,
-                    'Unexpected response ${rsp.statusCode}; $results'));
-              } else {
-                completer.complete(value);
-              }
-            });
-      }).catchError((error, s) => _failRequest(completer, error, s));
-    } catch (e, s) {
-      _failRequest(completer, e, s);
+  _processResponse(HttpClientResponse response, Converter<String, dynamic> decoder) async {
+    var respBody = decoder.convert(await UTF8.decodeStream(response));
+    if (response.statusCode < 200 || response.statusCode > 299 || (respBody is Map && respBody['status'] != 0)) {
+      // TODO(DrMarcII) FIXME
+      throw new WebDriverException(httpStatusCode: response.statusCode, httpReasonPhrase: response.reasonPhrase, jsonResp: respBody);
     }
-    return completer.future;
+    if (respBody is Map) {
+      return respBody['value'];
+    }
+    return respBody;
   }
 
-  Future get(String extraPath) => _serverRequest('GET', _command(extraPath));
-
-
-  Future post(String extraPath, [params]) =>
-      _serverRequest('POST', _command(extraPath), params: params);
-
-
-  Future delete(String extraPath) =>
-      _serverRequest('DELETE', _command(extraPath));
-
-  String _command(String extraPath) {
-    var command;
-    if (extraPath.startsWith('/')) {
-      command = '${path}$extraPath';
-    } else {
-      command = '${path}/$extraPath';
-    }
-    if (command.endsWith('/')) {
-      command = command.substring(0, command.length - 1);
-    }
-    return command;
+  void _setUpRequest(HttpClientRequest request) {
+    request.followRedirects = false;
+    request.headers.add(HttpHeaders.ACCEPT, "application/json");
+    request.headers.add(HttpHeaders.ACCEPT, "application/json");
+    request.headers.add(HttpHeaders.ACCEPT_CHARSET, UTF8.name);
+    request.headers.add(HttpHeaders.CACHE_CONTROL, "no-cache");
   }
-}
+}
\ No newline at end of file
diff --git a/lib/src/common.dart b/lib/src/common.dart
index dc14341..04e2c79 100644
--- a/lib/src/common.dart
+++ b/lib/src/common.dart
@@ -1,8 +1,6 @@
 part of webdriver;
 
 const String _ELEMENT = 'ELEMENT';
-final ContentType _CONTENT_TYPE_JSON =
-    new ContentType("application", "json", charset: "utf-8");
 
 /**
  * Simple class to provide access to indexed properties such as WebElement
@@ -10,8 +8,8 @@
  */
 class Attributes extends _WebDriverBase {
 
-  Attributes._(command, prefix, commandProcessor)
-    : super('$prefix/$command', commandProcessor);
+  Attributes._(driver, command)
+    : super(driver, command);
 
   Future<String> operator [](String name) => _get(name);
 }
@@ -47,7 +45,7 @@
 abstract class SearchContext {
 
   /// Searches for multiple elements within the context.
-  Future<List<WebElement>> findElements(By by);
+  Stream<WebElement> findElements(By by);
 
   /**
    * Searchs for an element within the context.
@@ -60,25 +58,25 @@
 
 abstract class _WebDriverBase {
   final String _prefix;
-  final CommandProcessor _commandProcessor;
+  final WebDriver driver;
 
-  _WebDriverBase(this._prefix, this._commandProcessor);
+  _WebDriverBase(this.driver, this._prefix);
 
   Future _post(String command, [param]) =>
-      _commandProcessor.post(_command(command), param);
+      driver._post(resolve(command), param);
 
-  Future _get(String command) => _commandProcessor.get(_command(command));
+  Future _get(String command) => driver._get(resolve(command));
 
-  Future _delete(String command) => _commandProcessor.delete(_command(command));
+  Future _delete(String command) => driver._delete(resolve(command));
 
-  String _command(String command) {
+  String resolve(command) {
+    if (_prefix == null || _prefix.isEmpty) {
+      return command;
+    }
     if (command == null || command.isEmpty) {
       return _prefix;
-    } else if (_prefix == null || _prefix.isEmpty) {
-      return '$command';
-    } else {
-      return '$_prefix/$command';
     }
+    return '$_prefix/$command';
   }
 }
 
diff --git a/lib/src/exception.dart b/lib/src/exception.dart
new file mode 100644
index 0000000..675f890
--- /dev/null
+++ b/lib/src/exception.dart
@@ -0,0 +1,206 @@
+/*
+Copyright 2015 Google Inc. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+part of webdriver;
+
+abstract class WebDriverException {
+  /**
+   * Either the status value returned in the JSON response (preferred) or the
+   * HTTP status code.
+   */
+  final int statusCode;
+
+  /**
+   * A message describing the error.
+   */
+  final String message;
+
+  factory WebDriverException(
+      {int httpStatusCode, String httpReasonPhrase, dynamic jsonResp}) {
+    if (jsonResp is Map) {
+      var status = jsonResp['status'];
+      var message = jsonResp['value']['message'];
+
+      switch (status) {
+        case 0:
+          throw new StateError(
+              'Not a WebDriverError Status: 0 Message: $message');
+        case 6: // NoSuchDriver
+          return new NoSuchDriverException(status, message);
+        case 7: // NoSuchElement
+          return new NoSuchElementException(status, message);
+        case 8: // NoSuchFrame
+          return new NoSuchFrameException(status, message);
+        case 9: // UnknownCommand
+          return new UnknownCommandException(status, message);
+        case 10: // StaleElementReferenceException
+          return new StaleElementReferenceException(status, message);
+        case 11: // ElementNotVisible
+          return new ElementNotVisibleException(status, message);
+        case 12: // InvalidElementState
+          return new InvalidElementStateException(status, message);
+        case 15: // ElementIsNotSelectable
+          return new ElementIsNotSelectableException(status, message);
+        case 17: // JavaScriptError
+          return new JavaScriptException(status, message);
+        case 19: // XPathLookupError
+          return new XPathLookupException(status, message);
+        case 21: // Timeout
+          return new TimeoutException(status, message);
+        case 23: // NoSuchWindow
+          return new NoSuchWindowException(status, message);
+        case 24: // InvalidCookieDomain
+          return new InvalidCookieDomainException(status, message);
+        case 25: // UnableToSetCookie
+          return new UnableToSetCookieException(status, message);
+        case 26: // UnexpectedAlertOpen
+          return new UnexpectedAlertOpenException(status, message);
+        case 27: // NoAlertOpenError
+          return new NoAlertOpenException(status, message);
+        case 29: // InvalidElementCoordinates
+          return new InvalidElementCoordinatesException(status, message);
+        case 30: // IMENotAvailable
+          return new IMENotAvailableException(status, message);
+        case 31: // IMEEngineActivationFailed
+          return new IMEEngineActivationFailedException(status, message);
+        case 32: // InvalidSelector
+          return new InvalidSelectorException(status, message);
+        case 33: // SessionNotCreatedException
+          return new SessionNotCreatedException(status, message);
+        case 34: // MoveTargetOutOfBounds
+          return new MoveTargetOutOfBoundsException(status, message);
+        case 13: // UnknownError
+        default: // new error?
+          return new UnknownException(status, message);
+      }
+    }
+    if (jsonResp != null) {
+      return new InvalidRequestException(httpStatusCode, jsonResp);
+    }
+    return new InvalidRequestException(httpStatusCode, httpReasonPhrase);
+  }
+
+  const WebDriverException._(this.statusCode, this.message);
+
+  String toString() => '$runtimeType ($statusCode): $message';
+}
+
+class InvalidRequestException extends WebDriverException {
+  InvalidRequestException(statusCode, message) : super._(statusCode, message);
+}
+
+class UnknownException extends WebDriverException {
+  UnknownException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchDriverException extends WebDriverException {
+  NoSuchDriverException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchElementException extends WebDriverException {
+  NoSuchElementException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchFrameException extends WebDriverException {
+  NoSuchFrameException(statusCode, message) : super._(statusCode, message);
+}
+
+class UnknownCommandException extends WebDriverException {
+  UnknownCommandException(statusCode, message) : super._(statusCode, message);
+}
+
+class StaleElementReferenceException extends WebDriverException {
+  StaleElementReferenceException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class ElementNotVisibleException extends WebDriverException {
+  ElementNotVisibleException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class InvalidElementStateException extends WebDriverException {
+  InvalidElementStateException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class ElementIsNotSelectableException extends WebDriverException {
+  ElementIsNotSelectableException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class JavaScriptException extends WebDriverException {
+  JavaScriptException(statusCode, message) : super._(statusCode, message);
+}
+
+class XPathLookupException extends WebDriverException {
+  XPathLookupException(statusCode, message) : super._(statusCode, message);
+}
+
+class TimeoutException extends WebDriverException {
+  TimeoutException(statusCode, message) : super._(statusCode, message);
+}
+
+class NoSuchWindowException extends WebDriverException {
+  NoSuchWindowException(statusCode, message) : super._(statusCode, message);
+}
+
+class InvalidCookieDomainException extends WebDriverException {
+  InvalidCookieDomainException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class UnableToSetCookieException extends WebDriverException {
+  UnableToSetCookieException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class UnexpectedAlertOpenException extends WebDriverException {
+  UnexpectedAlertOpenException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class NoAlertOpenException extends WebDriverException {
+  NoAlertOpenException(statusCode, message) : super._(statusCode, message);
+}
+
+class InvalidElementCoordinatesException extends WebDriverException {
+  InvalidElementCoordinatesException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class IMENotAvailableException extends WebDriverException {
+  IMENotAvailableException(statusCode, message) : super._(statusCode, message);
+}
+
+class IMEEngineActivationFailedException extends WebDriverException {
+  IMEEngineActivationFailedException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class InvalidSelectorException extends WebDriverException {
+  InvalidSelectorException(statusCode, message) : super._(statusCode, message);
+}
+
+class SessionNotCreatedException extends WebDriverException {
+  SessionNotCreatedException(statusCode, message)
+      : super._(statusCode, message);
+}
+
+class MoveTargetOutOfBoundsException extends WebDriverException {
+  MoveTargetOutOfBoundsException(statusCode, message)
+      : super._(statusCode, message);
+}
diff --git a/lib/src/keyboard.dart b/lib/src/keyboard.dart
index 421c456..7835a9e 100644
--- a/lib/src/keyboard.dart
+++ b/lib/src/keyboard.dart
@@ -1,43 +1,16 @@
 part of webdriver;
 
-class Keyboard extends _WebDriverBase implements Future {
+class Keyboard extends _WebDriverBase {
 
-  Future _future;
-
-  Keyboard._(prefix, commandProcessor, [this._future])
-      : super(prefix, commandProcessor) {
-    if (_future == null) {
-      _future = new Future.value();
-    }
-  }
+  Keyboard._(driver)
+      : super(driver, '');
 
   /**
-   * Send a [keysToSend] to the active element.
+   * Send [keysToSend] to the active element.
    */
-  Keyboard sendKeys(dynamic keysToSend) {
-    if (keysToSend is String) {
-      keysToSend = [ keysToSend ];
-    }
-    return _createNext((_) => _post(
+  Future sendKeys(String keysToSend) async {
+    await _post(
         'keys',
-        { 'value' : keysToSend as List<String>}));
+        { 'value' : [ keysToSend ]});
   }
-
-  Keyboard _createNext(f(value)) {
-    return new Keyboard._(_prefix, _commandProcessor, _future.then(f));
-  }
-
-  Stream asStream() => _future.asStream();
-
-  Future catchError(onError(error), {bool test(error)}) =>
-      _future.catchError(onError, test: test);
-
-  Future then(onValue(value), {onError(error)}) =>
-      _future.then(onValue, onError: onError);
-
-  Future whenComplete(action()) =>
-      _future.whenComplete(action);
-
-  Future timeout(Duration timeLimit, {onTimeout()}) =>
-      _future.timeout(timeLimit, onTimeout: onTimeout);
 }
diff --git a/lib/src/mouse.dart b/lib/src/mouse.dart
index 241858a..7108e49 100644
--- a/lib/src/mouse.dart
+++ b/lib/src/mouse.dart
@@ -1,58 +1,51 @@
 part of webdriver;
 
-class Mouse extends _WebDriverBase implements Future {
+class Mouse extends _WebDriverBase {
 
   static const int LEFT = 0;
   static const int MIDDLE = 1;
   static const int RIGHT = 2;
 
-  Future _future;
-
-  Mouse._(prefix, commandProcessor, [this._future])
-      : super(prefix, commandProcessor) {
-    if (_future == null) {
-      _future = new Future.value();
-    }
-  }
+  Mouse._(driver)
+      : super(driver, '');
 
   /// Click any mouse button (at the coordinates set by the last moveTo).
-  Mouse click([int button]) {
+  Future click([int button]) async {
     var json = {};
     if (button is num) {
       json['button'] = button.clamp(0, 2).floor();
     }
-    return _createNext((_) => _post('click', json));
+    await _post('click', json);
   }
 
   /**
    * Click and hold any mouse button (at the coordinates set by the last
    * moveTo command).
    */
-  Mouse down([int button]) {
+  Future down([int button]) async {
     var json = {};
     if (button is num) {
       json['button'] = button.clamp(0, 2).floor();
     }
-    return _createNext((_) =>
-        _post('buttondown', json));
+    await _post('buttondown', json);
   }
 
   /**
    * Releases the mouse button previously held (where the mouse is currently
    * at).
    */
-  Mouse up([int button]) {
+  Future up([int button]) async {
     var json = {};
     if (button is num) {
       json['button'] = button.clamp(0, 2).floor();
     }
-    return _createNext((_) =>
-        _post('buttonup', json));
+    await _post('buttonup', json);
   }
 
   /// Double-clicks at the current mouse coordinates (set by moveTo).
-  Mouse doubleClick() =>
-      _createNext((_) => _post('doubleclick'));
+  Future doubleClick() async {
+     await _post('doubleclick');
+  }
 
   /**
    * Move the mouse.
@@ -68,33 +61,15 @@
    *
    * All other combinations of parameters are illegal.
    */
-  Mouse moveTo({WebElement element, int xOffset, int yOffset}) {
+  Future moveTo({WebElement element, int xOffset, int yOffset}) async {
     var json = {};
     if (element is WebElement) {
-      json['element'] = element._elementId;
+      json['element'] = element.id;
     }
     if (xOffset is num && yOffset is num) {
       json['xoffset'] = xOffset.floor();
       json['yoffset'] = yOffset.floor();
     }
-    return _createNext((_) => _post('moveto', json));
+    await _post('moveto', json);
   }
-
-  Mouse _createNext(f(value)) {
-    return new Mouse._(_prefix, _commandProcessor, _future.then(f));
-  }
-
-  Stream asStream() => _future.asStream();
-
-  Future catchError(onError(error), {bool test(error)}) =>
-      _future.catchError(onError, test: test);
-
-  Future then(onValue(value), {onError(error)}) =>
-      _future.then(onValue, onError: onError);
-
-  Future whenComplete(action()) =>
-      _future.whenComplete(action);
-
-  Future timeout(Duration timeLimit, {onTimeout()}) =>
-      _future.timeout(timeLimit, onTimeout: onTimeout);
 }
diff --git a/lib/src/navigation.dart b/lib/src/navigation.dart
index 020c9ed..407da8f 100644
--- a/lib/src/navigation.dart
+++ b/lib/src/navigation.dart
@@ -2,14 +2,20 @@
 
 class Navigation extends _WebDriverBase {
 
-  Navigation._(prefix, commandProcessor) : super(prefix, commandProcessor);
+  Navigation._(driver) : super(driver, '');
 
   ///  Navigate forwards in the browser history, if possible.
-  Future<Navigation> forward() => _post('forward').then((_) => this);
+  Future forward() async {
+    await _post('forward');
+  }
 
   /// Navigate backwards in the browser history, if possible.
-  Future<Navigation> back() => _post('back').then((_) => this);
+  Future back() async {
+    await _post('back');
+  }
 
   /// Refresh the current page.
-  Future<Navigation> refresh() => _post('refresh').then((_) => this);
+  Future refresh() async {
+    await _post('refresh');
+  }
 }
diff --git a/lib/src/options.dart b/lib/src/options.dart
index 1e79d2c..22da230 100644
--- a/lib/src/options.dart
+++ b/lib/src/options.dart
@@ -2,24 +2,31 @@
 
 class Cookies extends _WebDriverBase {
 
-  Cookies._(prefix, commandProcessor)
-      : super('$prefix/cookie', commandProcessor);
+  Cookies._(driver)
+      : super(driver, 'cookie');
 
   /// Set a cookie.
-  Future<Cookies> add(Cookie cookie) => _post('', { 'cookie': cookie })
-      .then((_) => this);
+  Future add(Cookie cookie) async {
+    await _post('', { 'cookie': cookie });
+  }
 
   /// Delete the cookie with the given [name].
-  Future<Cookies> delete(String name) => _delete('$name').then((_) => this);
+  Future delete(String name) async {
+    await _delete('$name');
+  }
 
   /// Delete all cookies visible to the current page.
-  Future<Cookies> deleteAll() => _delete('').then((_) => this);
+  Future deleteAll() async {
+    await _delete('');
+  }
 
   /// Retrieve all cookies visible to the current page.
-  Future<List<Cookie>> get all =>
-      _get('')
-      .then((cookies) =>
-          cookies.map((cookie) => new Cookie.fromJson(cookie)).toList());
+  Stream<Cookie> get all async* {
+    var cookies = await _get('');
+    for (var cookie in cookies) {
+      yield new Cookie.fromJson(cookie);
+    }
+  }
 }
 
 class Cookie {
@@ -77,32 +84,22 @@
 
 class Timeouts extends _WebDriverBase {
 
-  Timeouts._(prefix, commandProcessor)
-      : super('$prefix/timeouts', commandProcessor);
+  Timeouts._(driver)
+      : super(driver, 'timeouts');
 
-  Future<Timeouts> _set(String type, Duration duration) =>
-      _post('', { 'type' : type, 'ms': duration.inMilliseconds})
-      .then((_) => this);
+  Future _set(String type, Duration duration) async {
+      await _post('', { 'type' : type, 'ms': duration.inMilliseconds});
+  }
 
   /// Set the script timeout.
-  Future<Timeouts> setScriptTimeout(Duration duration) =>
+  Future setScriptTimeout(Duration duration) =>
       _set('script', duration);
 
   /// Set the implicit timeout.
-  Future<Timeouts> setImplicitTimeout(Duration duration) =>
+  Future setImplicitTimeout(Duration duration) =>
       _set('implicit', duration);
 
   /// Set the page load timeout.
-  Future<Timeouts> setPageLoadTimeout(Duration duration) =>
+  Future setPageLoadTimeout(Duration duration) =>
       _set('page load', duration);
-
-  /// Set the async script timeout.
-  Future<Timeouts> setAsyncScriptTimeout(Duration duration) =>
-      _post('async_script', { 'ms': duration.inMilliseconds})
-      .then((_) => this);
-
-  /// Set the implicit wait timeout.
-  Future<Timeouts> setImplicitWaitTimeout(Duration duration) =>
-      _post('implicit_wait', { 'ms': duration.inMilliseconds})
-      .then((_) => this);
 }
diff --git a/lib/src/pageloader/annotations.dart b/lib/src/pageloader/annotations.dart
deleted file mode 100644
index 9821692..0000000
--- a/lib/src/pageloader/annotations.dart
+++ /dev/null
@@ -1,163 +0,0 @@
-part of pageloader;
-
-/**
- * Annotate a field as representing a [List] of [type]. If [type] is
- * not supplied, defaults to [WebElement].
- */
-// Hack because I can't figure out how to get the full type for fields.
-class ListOf {
-  final Type type;
-
-  const ListOf([this.type = WebElement]);
-}
-
-/// Finders identify an initial set of [WebElement]s to be used for a field.
-abstract class Finder extends _FilterFinder {
-  const Finder();
-
-  /// returns the [List<WebElement>] that should be considered for a field.
-  Future<List<WebElement>> findElements(WebDriver driver, SearchContext context);
-}
-
-/// Filters reduce the set of [WebElement]s to be used for a field.
-abstract class Filter extends _FilterFinder {
-
-  const Filter();
-
-  /// Returns a subset of [elements] that should be kept for a field.
-  Future<List<WebElement>> filter(List<WebElement> elements);
-}
-
-/**
- * Convenience class for [Filter]s that only need information about a specific
- * [WebElement] to determine whether to keep it or not.
- */
-abstract class ElementFilter extends Filter {
-
-  const ElementFilter();
-
-  @override
-  Future<List<WebElement>> filter(List<WebElement> elements) =>
-      Future.wait(elements.map(keep))
-        .then((keeps) {
-          var i = 0;
-          var newElements = new List<WebElement>();
-          for (var keep in keeps) {
-            if (keep) {
-              newElements.add(elements[i]);
-            }
-            i++;
-          }
-          return newElements;
-        });
-
-  /// Return [true] if you want to keep [element].
-  Future<bool> keep(WebElement element);
-}
-
-abstract class _FilterFinder {
-  const _FilterFinder();
-
-  /**
-   * Returns a set of [FilterFinderOption]s that control the behavior of this
-   * [Filter] or [Finder].
-   */
-  List<FilterFinderOption> get options => const [];
-}
-
-/**
- * [Filter] that keeps [WebElement]s based on their visibility. Overrides the
- * default visibility filter used by [PageLoader].
- */
-class WithState extends ElementFilter {
-
-  final _displayed;
-
-  const WithState._(this._displayed);
-
-  /// Keep all [WebElement]s regardless of whether they are visible or not.
-  const WithState.present() : this._(null);
-
-  /**
-   * Keep only [WebElement]s that are visible. This is the default for
-   * [PageLoader] so should generally not be necessary.
-   */
-  const WithState.visible() : this._(true);
-
-  /// Keep only [WebElement]s that are invisible.
-  const WithState.invisible() : this._(false);
-
-  @override
-  Future<bool> keep(WebElement element) {
-    if (_displayed == null) {
-      return new Future.value(true);
-    } else {
-      return element.displayed.then((displayed) => displayed == _displayed);
-    }
-  }
-
-  @override
-  List<FilterFinderOption> get options =>
-      const [ FilterFinderOption.DISABLE_IMPLICIT_DISPLAY_FILTERING ];
-}
-
-/**
- * Matches the root [WebElement] being used for constructing the current page
- * object.
- */
-class Root extends Finder {
-  const Root();
-
-  @override
-  Future<List<WebElement>> findElements(WebDriver driver, SearchContext context) {
-    if (context is WebElement) {
-      return new Future.value([ context ]);
-    } else {
-      return context.findElements(const By.xpath('/*'));
-    }
-  }
-
-  @override
-  List<FilterFinderOption> get options =>
-      const [ FilterFinderOption.DISABLE_IMPLICIT_DISPLAY_FILTERING ];
-}
-
-/**
- * Keeps only [WebElement]s that have the given attribute with the given value.
- */
-class WithAttribute extends ElementFilter {
-
-  final String name;
-  final String value;
-
-  /**
-   * @param value String or null if checking for the absence of an attribute.
-   */
-  const WithAttribute(this.name, this.value);
-
-  @override
-  Future<bool> keep(WebElement element) => element.attributes[name]
-      .then((attribute) => attribute == value);
-}
-
-class _ByFinder extends Finder {
-  final By _by;
-
-  const _ByFinder(this._by);
-
-  @override
-  Future<List<WebElement>> findElements(WebDriver driver, SearchContext context) {
-    return context.findElements(_by);
-  }
-}
-
-/// Enum of options for that can be returned by [_FilterFinder.options].
-class FilterFinderOption {
-  final String option;
-
-  const FilterFinderOption._(this.option);
-
-  /// Disable the default implicit display filtering for a field.
-  static const FilterFinderOption DISABLE_IMPLICIT_DISPLAY_FILTERING =
-      const FilterFinderOption._('DISABLE_IMPLICIT_DISPLAY_FILTERING');
-}
diff --git a/lib/src/pageloader/core.dart b/lib/src/pageloader/core.dart
deleted file mode 100644
index 31f50ac..0000000
--- a/lib/src/pageloader/core.dart
+++ /dev/null
@@ -1,189 +0,0 @@
-part of pageloader;
-
-/**
- * Mechanism for specifying hierarchical page objects using annotations on
- * fields in simple Dart objects.
- */
-class PageLoader {
-  final WebDriver _driver;
-
-  PageLoader(this._driver);
-
-  /**
-   * Creates a new instance of [type] and binds annotated fields to
-   * corresponding [WebElement]s.
-   */
-  Future getInstance(Type type) =>
-      _getInstance(reflectClass(type), _driver);
-
-  Future _getInstance(ClassMirror type, SearchContext context) {
-    var fieldInfos = _fieldInfos(type);
-    var instance = _reflectedInstance(type);
-
-    var fieldFutures = fieldInfos.map((info) =>
-        info.setField(instance, context, this));
-
-    return Future.wait(fieldFutures).then((_) => instance.reflectee);
-  }
-
-  InstanceMirror _reflectedInstance(ClassMirror aClass) {
-    InstanceMirror page;
-
-    Iterable<MethodMirror> ctors = aClass.instanceMembers.values.where(
-        (member) => member.isConstructor);
-    for (MethodMirror constructor in ctors) {
-      if (constructor.parameters.isEmpty) {
-        page = aClass.newInstance(constructor.constructorName, []);
-        break;
-      }
-    }
-
-    if (page == null) {
-      throw new StateError('$aClass has no acceptable constructors');
-    }
-    return page;
-  }
-
-  Iterable<_FieldInfo> _fieldInfos(ClassMirror type) {
-    var infos = <_FieldInfo>[];
-
-    while (type != null) {
-      for (DeclarationMirror decl in type.declarations.values) {
-        _FieldInfo info = new _FieldInfo(_driver, decl);
-        if (info != null) {
-          infos.add(info);
-        }
-      }
-      type = type.superclass;
-    }
-
-    return infos;
-  }
-}
-
-class _FieldInfo {
-  final WebDriver _driver;
-  final Symbol _fieldName;
-  final Finder _finder;
-  final List<Filter> _filters;
-  final TypeMirror _instanceType;
-  final bool _isList;
-
-  factory _FieldInfo(WebDriver driver, DeclarationMirror field) {
-    var finder;
-    var filters = new List<Filter>();
-    var type;
-    var name;
-
-    if (field is VariableMirror && !field.isFinal) {
-      type = field.type;
-      name = field.simpleName;
-    } else if (field is MethodMirror && field.isSetter) {
-      type = field.parameters.first.type;
-      // HACK to get correct symbol name for operating with setField.
-      name = field.simpleName.toString();
-      name = new Symbol(name.substring(8, name.length - 3));
-    } else {
-      return null;
-    }
-
-    var isList = false;
-
-    if (type.simpleName == const Symbol('List')) {
-      isList = true;
-      type = null;
-    }
-
-    var implicitDisplayFiltering = true;
-
-    for (InstanceMirror metadatum in field.metadata) {
-      if (!metadatum.hasReflectee) {
-        continue;
-      }
-      var datum = metadatum.reflectee;
-
-      if (datum is By) {
-        if (finder != null) {
-          throw new StateError('Cannot have multiple finders on field');
-        }
-        finder = new _ByFinder(datum);
-      } else if (datum is Finder) {
-        if (finder != null) {
-          throw new StateError('Cannot have multiple finders on field');
-        }
-        finder = datum;
-      } else if (datum is Filter) {
-        filters.add(datum);
-      } else if (datum is ListOf) {
-        if (type != null && type.simpleName != const Symbol('dynamic')) {
-          throw new StateError('Field type is not compatible with ListOf');
-        }
-        isList = true;
-        type = reflectClass(datum.type);
-      }
-
-      if (datum is _FilterFinder &&
-          datum.options.contains(
-              FilterFinderOption.DISABLE_IMPLICIT_DISPLAY_FILTERING)) {
-        implicitDisplayFiltering = false;
-      }
-    }
-
-    if (type == null || type.simpleName == const Symbol('dynamic')) {
-      type = reflectClass(WebElement);
-    }
-
-    if (implicitDisplayFiltering) {
-      filters.insert(0, new WithState.visible());
-    }
-
-    if (finder != null) {
-      return new _FieldInfo._(driver, name, finder, filters, type, isList);
-    } else {
-      return null;
-    }
-  }
-
-  _FieldInfo._(
-      this._driver,
-      this._fieldName,
-      this._finder,
-      this._filters,
-      this._instanceType,
-      this._isList);
-
-  Future setField(
-      InstanceMirror instance,
-      SearchContext context,
-      PageLoader loader) {
-    var future = _getElements(context);
-
-    if (_instanceType.simpleName != const Symbol('WebElement')) {
-      future = future.then((elements) =>
-          Future.wait(elements.map((element) =>
-              loader._getInstance(_instanceType, element))));
-    }
-
-    if (!_isList) {
-      future = future.then((objects) => objects.first);
-    }
-
-    return future.then((value) => instance.setField(_fieldName, value));
-  }
-
-  Future<List<WebElement>> _getElements(SearchContext context) {
-    var future = _finder.findElements(_driver, context);
-    for (var filter in _filters) {
-      future = future.then(filter.filter);
-    }
-    if (!_isList) {
-      future = future.then((elements) {
-        if (elements.length != 1) {
-          throw new StateError('multiple or no elements found for field');
-        }
-        return elements.take(1);
-      });
-    }
-    return future;
-  }
-}
diff --git a/lib/src/target_locator.dart b/lib/src/target_locator.dart
index 05966e5..e00ddac 100644
--- a/lib/src/target_locator.dart
+++ b/lib/src/target_locator.dart
@@ -2,13 +2,13 @@
 
 class TargetLocator extends _WebDriverBase {
 
-  TargetLocator._(prefix, commandProcessor) : super(prefix, commandProcessor);
+  TargetLocator._(driver) : super(driver, '');
 
   /**
    * Change focus to another frame on the page.
    *
    * If [frame] is a:
-   *   int: select by its zero-based indexed
+   *   int: select by its zero-based index
    *   String: select frame by the name of the frame window or the id of the
    *           frame or iframe tag.
    *   WebElement: select the frrame for a previously found frame or iframe
@@ -18,8 +18,9 @@
    * Throws [WebDriverError] no such frame if the specified frame can't be
    * found.
    */
-  Future<TargetLocator> frame([frame]) =>
-      _post('frame', { 'id': frame}).then((_) => this);
+  Future frame([frame]) async {
+      await _post('frame', { 'id': frame});
+  }
 
   /**
    * Switch the focus of future commands for this driver to the window with the
@@ -28,15 +29,24 @@
    * Throws [WebDriverError] no such window if the specified window can't be
    * found.
    */
-  Future<TargetLocator> window(String window) =>
-      _post('window', { 'name': window}).then((_) => this);
+  Future window(dynamic window) async {
+    if (window is Window) {
+      await _post('window', { 'name': window.handle});
+    } else if (window is String){
+      await _post('window', { 'name': window });
+    } else {
+      throw 'Unsupported type: ${window.runtimeType}';
+    }
+  }
 
   /**
    * Switches to the currently active modal dialog for this particular driver
    * instance.
    *
-   * Throws WebDriverEror no alert present if their is not currently an alert.
+   * Throws WebDriverEror no alert present if there is not currently an alert.
    */
-  Future<Alert> get alert => _get('alert_text')
-      .then((text) => new Alert._(text, _prefix, _commandProcessor));
+  Future<Alert> get alert async {
+    var text = await _get('alert_text');
+    return new Alert._(text, driver);
+  }
 }
diff --git a/lib/src/touch.dart b/lib/src/touch.dart
deleted file mode 100644
index 30760d6..0000000
--- a/lib/src/touch.dart
+++ /dev/null
@@ -1,101 +0,0 @@
-part of webdriver;
-
-class Touch extends _WebDriverBase implements Future {
-
-  Future _future;
-
-  Touch._(prefix, commandProcessor, [this._future])
-      : super('$prefix/touch', commandProcessor) {
-    if (_future == null) {
-      _future = new Future.value();
-    }
-  }
-
-  /**
-   * Single tap on the touch enabled device.
-   */
-  Touch click(WebElement element) =>
-      _createNext((_) => _post('click', element));
-
-  /**
-   * Finger down on the screen.
-   */
-  Touch down(Point point) =>
-      _createNext((_) => _post('down', point));
-
-  /**
-   * Finger up on the screen.
-   */
-  Touch up(Point point) =>
-      _createNext((_) => _post('up', point));
-
-  /**
-   * Finger move on the screen.
-   */
-  Touch move(Point point) =>
-      _createNext((_) => _post('move', point));
-
-  /**
-   * Scroll on the touch screen using finger based motion events.
-   *
-   * If start is specified, will start scrolling from that location, otherwise
-   * will start scrolling from an arbitrary location.
-   */
-  Touch scroll(int xOffset, int yOffset, [WebElement start]) {
-    var json = { 'xoffset': xOffset.floor(), 'yoffset': yOffset.floor()};
-    if (start is WebElement) {
-      json['element'] = start._elementId;
-    }
-    return _createNext((_) => _post('scroll', json));
-  }
-
-  /**
-   * Double tap on the touch screen using finger motion events.
-   */
-  Touch doubleClick(WebElement element) =>
-      _createNext((_) => _post('doubleclick', element));
-
-  /**
-   * Long press on the touch screen using finger motion events.
-   */
-  Touch longClick(WebElement element) =>
-      _createNext((_) => _post('longclick', element));
-
-  /**
-   * Flick on the touch screen using finger motion events.
-   */
-  Touch flickElement(WebElement start, int xOffset, int yOffset, int speed) =>
-      _createNext((_) => _post('flick', {
-        'element': start._elementId,
-        'xoffset': xOffset.floor(),
-        'yoffset': yOffset.floor(),
-        'speed': speed.floor()
-      }));
-
-  /**
-   * Flick on the touch screen using finger motion events.
-   */
-  Touch flick(int xSpeed, int ySpeed) =>
-      _createNext((_) => _post('flick', {
-        'xspeed': xSpeed.floor(),
-        'yspeed': ySpeed.floor()
-      }));
-
-  Touch _createNext(f(value)) {
-    return new Touch._(_prefix, _commandProcessor, _future.then(f));
-  }
-
-  Stream asStream() => _future.asStream();
-
-  Future catchError(onError(error), {bool test(error)}) =>
-      _future.catchError(onError, test: test);
-
-  Future then(onValue(value), {onError(error)}) =>
-      _future.then(onValue, onError: onError);
-
-  Future whenComplete(action()) =>
-      _future.whenComplete(action);
-
-  Future timeout(Duration timeLimit, {onTimeout()}) =>
-      _future.timeout(timeLimit, onTimeout: onTimeout);
-}
diff --git a/lib/src/web_driver.dart b/lib/src/web_driver.dart
index a0a3eea..a9e0ae3 100644
--- a/lib/src/web_driver.dart
+++ b/lib/src/web_driver.dart
@@ -1,84 +1,36 @@
 part of webdriver;
 
-class WebDriver extends _WebDriverBase implements SearchContext {
+class WebDriver implements SearchContext {
 
-  WebDriver._(commandProcessor) : super('', commandProcessor) ;
+  final _CommandProcessor _commandProcessor;
+  final Uri _prefix;
+  final Map<String, dynamic> capabilities;
+
+  WebDriver._(this._commandProcessor, this._prefix, this.capabilities);
 
   /// Creates a WebDriver instance connected to the specified WebDriver server.
   static Future<WebDriver> createDriver({
-      String url: 'http://localhost:4444/wd/hub',
-      Map<String, dynamic> desiredCapabilities}) {
+      Uri uri,
+      Map<String, dynamic> desiredCapabilities}) async {
+
+    if (uri == null) {
+      uri = Uri.parse('http://localhost:4444/wd/hub');
+    }
+
+    var commandProcessor = new _CommandProcessor();
 
     if (desiredCapabilities == null) {
       desiredCapabilities = Capabilities.empty;
     }
-    var client = new HttpClient();
-    var requestUri = Uri.parse(url + "/session");
-    return client.openUrl('POST', requestUri).then((req) {
-      req.followRedirects = false;
-      req.headers.add(HttpHeaders.ACCEPT, "application/json");
-      req.headers.contentType = _CONTENT_TYPE_JSON;
-      var body = UTF8.encode(
-          JSON.encode({'desiredCapabilities': desiredCapabilities}));
-      req.contentLength = body.length;
-      req.add(body);
-      return req.close();
 
-    }).then((rsp) {
-      // Starting in Selenium 2.34.0, the post/redirect/get pattern is no
-      // longer used when creating a new session.
-      if (300 <= rsp.statusCode && rsp.statusCode <= 399) {
-        return Uri.parse(rsp.headers.value(HttpHeaders.LOCATION));
-      }
-
-      return rsp.transform(new Utf8Decoder())
-          .fold(new StringBuffer(), (buffer, data) => buffer..write(data))
-          .then((StringBuffer buffer) {
-            // Strip NULs that WebDriver seems to include in some responses.
-            var results = buffer.toString()
-                .replaceAll(new RegExp('\u{0}*\$'), '');
-
-            var status = 0;
-
-            // 4xx responses send plain text; others send JSON
-            if (HttpStatus.BAD_REQUEST <= rsp.statusCode
-                && rsp.statusCode < HttpStatus.INTERNAL_SERVER_ERROR) {
-              status = 13;  // UnknownError
-              if (rsp.statusCode == HttpStatus.NOT_FOUND) {
-                status = 9; // UnkownCommand
-              }
-              throw new WebDriverError(status, results);
-            }
-
-            var respObj = JSON.decode(results);
-            status = respObj['status'];
-            if (status != 0) {
-              var value = respObj['value'];
-              var message =
-                  value.containsKey('message') ? value['message'] : null;
-              throw new WebDriverError(status, message);
-            }
-
-            return Uri.parse(url + '/session/' + respObj['sessionId']);
-          });
-    }).then((sessionUrl) {
-      var host = sessionUrl.host;
-      var port = sessionUrl.port;
-      if (host.isEmpty) {
-        host = requestUri.host;
-        if (port == 0) {
-          port = requestUri.port;
-        }
-      }
-      CommandProcessor processor = new CommandProcessor(
-          host, port, sessionUrl.path);
-      return new WebDriver._(processor);
-    });
+    var response = await commandProcessor.post(uri.resolve('session'), { 'desiredCapabilities' : desiredCapabilities });
+    return new WebDriver._(commandProcessor, uri.resolve(response['id']), new UnmodifiableMapView(response['capabilities']));
   }
 
   /// Navigate to the specified url.
-  Future<WebDriver> get(String url) =>
-      _post('url', {'url': url}).then((_) => this);
+  Future get(String url) async {
+    await _post('url', {'url': url});
+  }
 
   /// The current url.
   Future<String> get currentUrl => _get('url');
@@ -88,20 +40,27 @@
 
   /// Search for multiple elements within the entire current page.
   @override
-  Future<List<WebElement>> findElements(By by) => _post('elements', by)
-      .then((response) =>
-          response.map((element) =>
-              new WebElement._(element, _prefix, _commandProcessor))
-              .toList());
+  Stream<WebElement> findElements(By by) async* {
+    var elements = await _post('elements', by);
+    int i = 0;
+
+    for (var element in elements) {
+      yield new WebElement._(this, element['ELEMENT'], this, by, i);
+      i++;
+    }
+  }
+
 
   /**
    * Search for an element within the entire current page.
    *
    * Throws [WebDriverError] no such element if a matching element is not found.
    */
-  Future<WebElement> findElement(By by) => _post('element', by)
-      .then((element) =>
-          new WebElement._(element, _prefix, _commandProcessor));
+  Future<WebElement> findElement(By by) async {
+    var element = await _post('element', by);
+    return new WebElement._(this,  element['ELEMENT'], this, by);
+  }
+
 
   /// An artist's rendition of the current page's source.
   Future<String> get pageSource => _get('source');
@@ -110,49 +69,47 @@
   Future close() => _delete('window');
 
   /// Quit the browser.
-  Future quit() => _delete(_prefix);
+  Future quit() => _delete('');
 
   /// Handles for all of the currently displayed tabs/windows.
-  Future<List<String>> get windowHandles => _get('window_handles');
+  Stream<Window> get windows async* {
+    var handles = await _get('window_handles');
+
+    for (var handle in handles) {
+      yield new Window._(this, handle);
+    }
+  }
 
   /// Handle for the active tab/window.
-  Future<String> get windowHandle => _get('window_handle');
+  Future<Window> get window async {
+    var handle = await _get('window_handle');
+    return new Window._(this, handle);
+  }
 
   /**
    *  The currently focused element, or the body element if no
    *  element has focus.
    */
-  Future<WebElement> get activeElement => _get('element/active')
-      .then((element) {
-        if (element == null) {
-          return null;
-        } else {
-          new WebElement._(element['ELEMENT'], _prefix, _commandProcessor);
-        }
-      });
-
+  Future<WebElement> get activeElement async {
+    var element = await _get('element/active');
+    if (element != null) {
+      return new WebElement._(this, element['ELEMENT'], this, 'activeElement');
+    }
+    return null;
+  }
 
   TargetLocator get switchTo =>
-      new TargetLocator._(_prefix, _commandProcessor);
+      new TargetLocator._(this);
 
-  Navigation get navigate => new Navigation._(_prefix, _commandProcessor);
+  Navigation get navigate => new Navigation._(this);
 
-  Cookies get cookies => new Cookies._(_prefix, _commandProcessor);
+  Cookies get cookies => new Cookies._(this);
 
-  Timeouts get timeouts => new Timeouts._(_prefix, _commandProcessor);
+  Timeouts get timeouts => new Timeouts._(this);
 
-  Keyboard get keyboard => new Keyboard._(_prefix, _commandProcessor);
+  Keyboard get keyboard => new Keyboard._(this);
 
-  Mouse get mouse => new Mouse._(_prefix, _commandProcessor);
-
-  Touch get touch => new Touch._(_prefix, _commandProcessor);
-
-  Window get window =>
-      new Window._( 'current', _prefix, _commandProcessor);
-
-  Future<List<Window>> get windows => windowHandles
-      .then((windows) => windows.map((window) =>
-          new Window._(window, _prefix, _commandProcessor)).toList());
+  Mouse get mouse => new Mouse._(this);
 
   /// Take a screenshot of the current page as PNG.
   Future<List<int>> captureScreenshot() => _get('screenshot')
@@ -209,7 +166,7 @@
   dynamic _recursiveElementify(result) {
     if (result is Map) {
       if (result.length == 1 && result.containsKey(_ELEMENT)) {
-        return new WebElement._(result, _prefix, _commandProcessor);
+        return new WebElement._(this, result['ELEMENT'], this, 'javascript');
       } else {
         var newResult = {};
         result.forEach((key, value) {
@@ -223,4 +180,10 @@
       return result;
     }
   }
+
+  Future _post(String command, [params]) => _commandProcessor.post(_prefix.resolve(command), params);
+
+  Future _get(String command) => _commandProcessor.get(_prefix.resolve(command));
+
+  Future _delete(String command) => _commandProcessor.delete(_prefix.resolve(command));
 }
diff --git a/lib/src/web_element.dart b/lib/src/web_element.dart
index 46f2d33..ed0127a 100644
--- a/lib/src/web_element.dart
+++ b/lib/src/web_element.dart
@@ -1,32 +1,36 @@
 part of webdriver;
 
 class WebElement extends _WebDriverBase implements SearchContext {
-  String _elementId;
-  String _originalPrefix;
+  final String id;
 
-  WebElement._(element, prefix, commandProcessor)
-      : super('$prefix/element/${element[_ELEMENT]}', commandProcessor) {
-    this._elementId = element[_ELEMENT];
-    this._originalPrefix = prefix;
-  }
+  // These fields represent information about how the element was found.
+  final SearchContext context;
+  final dynamic /* String | By */ locator;
+  final int index;
+
+  WebElement._(driver, id, [this.context, this.locator, this.index])
+      : this.id = id,
+        super(driver, 'element/$id');
 
   /// Click on this element.
-  Future<WebElement> click() => _post('click').then((_) => this);
+  Future click() async {
+    await _post('click');
+  }
 
   /// Submit this element if it is part of a form.
-  Future<WebElement> submit() => _post('submit').then((_) => this);
+  Future submit() async {
+    await _post('submit');
+  }
 
-  /// Send [keysToSend] (a [String] or [List<String>]) to this element.
-  Future<WebElement> sendKeys(dynamic keysToSend) {
-    if (keysToSend is String) {
-      keysToSend = [ keysToSend ];
-    }
-    return _post('value', { 'value' : keysToSend as List<String>})
-        .then((_) => this);
+  /// Send [keysToSend] to this element.
+  Future sendKeys(String keysToSend) async {
+    await _post('value', { 'value' : [ keysToSend ]});
   }
 
   /// Clear the content of a text element.
-  Future<WebElement> clear() => _post('clear').then((_) => this);
+  Future clear() async {
+    await _post('clear');
+  }
 
   /// Is this radio button/checkbox selected?
   Future<bool> get selected => _get('selected');
@@ -38,12 +42,16 @@
   Future<bool> get displayed => _get('displayed');
 
   /// The location within the document of this element.
-  Future<Point> get location => _get('location')
-      .then((json) => new Point.fromJson(json));
+  Future<Point> get location async {
+    var point = await _get('location');
+    return new Point.fromJson(point);
+  }
 
   /// The size of this element.
-  Future<Size> get size => _get('size')
-      .then((json) => new Size.fromJson(json));
+  Future<Size> get size async {
+    var size = await _get('size');
+    return new Size.fromJson(size);
+  }
 
   /// The tag name for this element.
   Future<String> get name => _get('name');
@@ -56,15 +64,20 @@
    *
    * Throws [WebDriverError] no such element if matching element is not found.
    */
-  Future<WebElement> findElement(By by) => _post('element', by)
-      .then((element) =>
-          new WebElement._(element, _originalPrefix, _commandProcessor));
+  Future<WebElement> findElement(By by) async {
+    var element = await _post('element', by);
+    return new WebElement._(driver, element['ELEMENT'], this, by);
+  }
 
   /// Find multiple elements nested within this element.
-  Future<List<WebElement>> findElements(By by) => _post('elements', by)
-      .then((response) => response.map((element) =>
-          new WebElement._(element, _originalPrefix, _commandProcessor))
-              .toList());
+  Stream<WebElement> findElements(By by) async* {
+    var elements = await _post('elements', by);
+    int i = 0;
+    for (var element in elements) {
+      yield new WebElement._(driver, element['ELEMENT'], this, by, i);
+      i++;
+    }
+  }
 
   /**
    * Access to the HTML attributes of this tag.
@@ -72,7 +85,7 @@
    * TODO(DrMarcII): consider special handling of boolean attributes.
    */
   Attributes get attributes =>
-      new Attributes._('attribute', _prefix, _commandProcessor);
+      new Attributes._(driver, '$_prefix/attribute');
 
   /**
    * Access to the cssProperties of this element.
@@ -81,14 +94,13 @@
    *                 properties.
    */
   Attributes get cssProperties =>
-      new Attributes._('css', _prefix, _commandProcessor);
+      new Attributes._(driver, '$_prefix/css');
 
   /**
    * Does this element represent the same element as another element?
    * Not the same as ==
    */
-  Future<bool> equals(WebElement other) => _get('equals/${other._elementId}');
+  Future<bool> equals(WebElement other) => _get('equals/${other.id}');
 
-  Map<String, String> toJson() =>
-      new Map<String, String>()..[_ELEMENT] = _elementId;
+  Map<String, String> toJson() => {'ELEMENT': id};
 }
diff --git a/lib/src/window.dart b/lib/src/window.dart
index f99ba95..8d81198 100644
--- a/lib/src/window.dart
+++ b/lib/src/window.dart
@@ -3,24 +3,36 @@
 
 class Window extends _WebDriverBase {
 
-  Window._(windowHandle, prefix, commandProcessor)
-      : super('$prefix/window/$windowHandle', commandProcessor);
+  final String handle;
+
+  Window._(driver, handle)
+      : this.handle = handle,
+      super(driver, 'window/$handle');
 
   /// The size of this window.
-  Future<Size> get size =>  _get('size')
-      .then((json) => new Size.fromJson(json));
+  Future<Size> get size async {
+    var size = await _get('size');
+    return new Size.fromJson(size);
+  }
 
   /// The location of this window.
-  Future<Point> get location =>  _get('position')
-      .then((json) => new Point.fromJson(json));
+  Future<Point> get location async {
+    var point = await _get('position');
+    return new Point.fromJson(point);
+  }
 
   /// Maximize this window.
-  Future<Window> maximize() => _post('maximize').then((_) => this);
+  Future maximize() async {
+    await _post('maximize');
+  }
 
   /// Set this window size.
-  Future<Window> setSize(Size size) => _post('size', size).then((_) => this);
+  Future setSize(Size size) async {
+    await _post('size', size);
+  }
 
   /// Set this window location.
-  Future<Window> setLocation(Point point) =>
-      _post('position', point).then((_) => this);
+  Future setLocation(Point point) async {
+    await _post('position', point);
+  }
 }
diff --git a/lib/webdriver.dart b/lib/webdriver.dart
index 8d984e2..aeb3aef 100644
--- a/lib/webdriver.dart
+++ b/lib/webdriver.dart
@@ -5,8 +5,9 @@
 library webdriver;
 
 import 'dart:async';
+import 'dart:collection';
 import 'dart:convert';
-import 'dart:io' hide JSON;
+import 'dart:io';
 
 import 'package:crypto/crypto.dart';
 
@@ -14,13 +15,13 @@
 part 'src/capabilities.dart';
 part 'src/command_processor.dart';
 part 'src/common.dart';
+part 'src/exception.dart';
 part 'src/keyboard.dart';
 part 'src/keys.dart';
 part 'src/mouse.dart';
 part 'src/navigation.dart';
 part 'src/options.dart';
 part 'src/target_locator.dart';
-part 'src/touch.dart';
 part 'src/web_driver.dart';
 part 'src/web_element.dart';
 part 'src/window.dart';
diff --git a/test/pageloader_test.dart b/test/pageloader_test.dart
deleted file mode 100644
index 96bc8cc..0000000
--- a/test/pageloader_test.dart
+++ /dev/null
@@ -1,165 +0,0 @@
-library webdriver_test;
-
-import 'package:webdriver/pageloader.dart';
-import 'package:webdriver/webdriver.dart';
-import 'package:unittest/unittest.dart';
-import 'package:unittest/compact_vm_config.dart';
-
-import 'test_util.dart';
-
-/**
- * These tests are not expected to be run as part of normal automated testing,
- * as they are slow and they have external dependencies.
- */
-void main() {
-  useCompactVMConfiguration();
-
-  WebDriver driver;
-  PageLoader loader;
-
-  setUp(() => WebDriver.createDriver(desiredCapabilities: Capabilities.chrome)
-        .then((_driver) {
-          driver = _driver;
-          loader = new PageLoader(driver);
-          return driver.get(testPagePath);
-        }));
-
-  tearDown(() => driver.quit());
-
-  test('simple', () {
-    return loader.getInstance(PageForSimpleTest)
-        .then((PageForSimpleTest page) {
-          expect(page.table.rows, hasLength(2));
-          expect(page.table.rows[0].cells, hasLength(2));
-          expect(page.table.rows[1].cells, hasLength(2));
-          expect(page.table.rows[0].cells[0].text, completion('r1c1'));
-          expect(page.table.rows[0].cells[1].text, completion('r1c2'));
-          expect(page.table.rows[1].cells[0].text, completion('r2c1'));
-          expect(page.table.rows[1].cells[1].text, completion('r2c2'));
-        });
-  });
-
-  test('displayed filtering', () {
-    return loader.getInstance(PageForDisplayedFilteringTest)
-        .then((PageForDisplayedFilteringTest page) {
-          expect(page.shouldHaveOneElement, hasLength(1));
-          expect(page.shouldBeEmpty, isEmpty);
-          expect(page.shouldAlsoBeEmpty, isEmpty);
-        });
-  });
-
-  test('setters', () {
-    return loader.getInstance(PageForSettersTest)
-        .then((PageForSettersTest page) {
-          expect(page._shouldHaveOneElement, hasLength(1));
-        });
-  });
-
-  test('skip finals', () {
-    return loader.getInstance(PageForSkipFinalTest)
-        .then((PageForSkipFinalTest page) {
-          expect(page.shouldHaveOneElement, hasLength(1));
-          expect(page.shouldBeNull, isNull);
-        });
-  });
-
-  test('skip fields without finders', () {
-    return loader.getInstance(PageForSkipFieldsWithoutFinderTest)
-        .then((PageForSkipFieldsWithoutFinderTest page) {
-          expect(page.shouldHaveOneElement, hasLength(1));
-          expect(page.shouldBeNull, isNull);
-        });
-  });
-
-  test('no matching element', () {
-    expect(loader.getInstance(PageForNoMatchingElementTest), throws);
-  });
-
-  test('multiple matching element', () {
-    expect(loader.getInstance(PageForMultipleMatchingElementTest), throws);
-  });
-
-  test('multiple finders', () {
-    expect(() => loader.getInstance(PageForMultipleFinderTest), throws);
-  });
-
-  test('invalid constructor', () {
-    expect(() => loader.getInstance(PageForInvalidConstructorTest), throws);
-  });
-}
-
-class PageForSimpleTest {
-  @By.tagName('table')
-  Table table;
-}
-
-class Table {
-  @By.tagName('tr')
-  @ListOf(Row)
-  List<Row> rows;
-}
-
-class Row {
-  @By.tagName('td')
-  List<WebElement> cells;
-}
-
-class PageForDisplayedFilteringTest {
-  @By.id('div') @WithState.present()
-  List<WebElement> shouldHaveOneElement;
-
-  @By.id('div')
-  List<WebElement> shouldBeEmpty;
-
-  @By.id('div') @WithState.visible()
-  @ListOf()
-  dynamic shouldAlsoBeEmpty;
-}
-
-class PageForSettersTest {
-  List<WebElement> _shouldHaveOneElement;
-
-  @By.id('div') @WithState.present()
-  set shouldHaveOneElement(List<WebElement> elements) {
-    _shouldHaveOneElement = elements;
-  }
-}
-
-class PageForSkipFinalTest {
-  @By.id('div') @WithState.present()
-  List<WebElement> shouldHaveOneElement;
-
-  @By.id('div') @WithState.present()
-  final List<WebElement> shouldBeNull = null;
-}
-
-class PageForSkipFieldsWithoutFinderTest {
-  @By.id('div') @WithState.present()
-  List<WebElement> shouldHaveOneElement;
-
-  @WithState.present()
-  List<WebElement> shouldBeNull;
-}
-
-class PageForNoMatchingElementTest {
-  @By.id('non-existent id')
-  WebElement doesntExist;
-}
-
-class PageForMultipleMatchingElementTest {
-  @By.tagName('td')
-  WebElement doesntExist;
-}
-
-class PageForMultipleFinderTest {
-  @By.id('non-existent id') @By.name('a-name')
-  WebElement multipleFinder;
-}
-
-class PageForInvalidConstructorTest {
-
-  PageForInvalidConstructorTest(String someArg);
-
-  @By.id('div') @WithState.present()
-  List<WebElement> shouldHaveOneElement;
-}