Version 1.18.0-dev.4.4

Cherry-pick faaff39aa9efea958f56e29ba6c9cecfb84d4668 to dev
Cherry-pick ca4930357635d36d43433334271cdfd658a3a746 to dev
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23a3e2a..8f44d60 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,6 @@
 
 ### Core library changes
 
-* `dart:core`
-  * Improved performance when parsing some common URIs.
-  * Fixed bug in `Uri.resolve` (SDK issue [26804](https://github.com/dart-lang/sdk/issues/26804)).
 * `dart:io`
   * Adds file locking modes `FileLock.BLOCKING_SHARED` and
     `FileLock.BLOCKING_EXCLUSIVE`.
diff --git a/pkg/analyzer/lib/src/util/fast_uri.dart b/pkg/analyzer/lib/src/util/fast_uri.dart
index 0df5ce05..8af548c 100644
--- a/pkg/analyzer/lib/src/util/fast_uri.dart
+++ b/pkg/analyzer/lib/src/util/fast_uri.dart
@@ -62,7 +62,23 @@
   bool get hasFragment => false;
 
   @override
-  int get hashCode => _text.hashCode;
+  int get hashCode {
+    // This code is copied from the standard Uri implementation.
+    // It is important that Uri and FastUri generate compatible hashCodes
+    // because Uri and FastUri may be used as keys in the same map.
+    int combine(part, current) {
+      // The sum is truncated to 30 bits to make sure it fits into a Smi.
+      return (current * 31 + part.hashCode) & 0x3FFFFFFF;
+    }
+    return _hashCode ??= combine(
+        scheme,
+        combine(
+            userInfo,
+            combine(
+                host,
+                combine(port,
+                    combine(path, combine(query, combine(fragment, 1)))))));
+  }
 
   @override
   bool get hasPort => false;
diff --git a/runtime/lib/uri_patch.dart b/runtime/lib/uri_patch.dart
index 80024c2..2664b9b 100644
--- a/runtime/lib/uri_patch.dart
+++ b/runtime/lib/uri_patch.dart
@@ -16,16 +16,14 @@
 _UriBaseClosure _uriBaseClosure = _unsupportedUriBase;
 
 patch class Uri {
-  /* patch */ static Uri get base => _uriBaseClosure();
-}
-
-patch class _Uri {
   static final bool _isWindowsCached = _isWindowsPlatform;
 
-  static bool get _isWindowsPlatform native "Uri_isWindowsPlatform";
-
   /* patch */ static bool get _isWindows => _isWindowsCached;
 
+  /* patch */ static Uri get base => _uriBaseClosure();
+
+  static bool get _isWindowsPlatform native "Uri_isWindowsPlatform";
+
   /* patch */ static String _uriEncode(List<int> canonicalTable,
                                        String text,
                                        Encoding encoding,
diff --git a/sdk/lib/_internal/js_runtime/lib/core_patch.dart b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
index 129ce01..9004574 100644
--- a/sdk/lib/_internal/js_runtime/lib/core_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
@@ -587,17 +587,15 @@
 @patch
 class Uri {
   @patch
+  static bool get _isWindows => false;
+
+  @patch
   static Uri get base {
     String uri = Primitives.currentUri();
     if (uri != null) return Uri.parse(uri);
     throw new UnsupportedError("'Uri.base' is not supported");
   }
-}
 
-@patch
-class _Uri {
-  @patch
-  static bool get _isWindows => false;
 
   // Matches a String that _uriEncodes to itself regardless of the kind of
   // component.  This corresponds to [_unreservedTable], i.e. characters that
diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
index b43b3ad..5718ca8 100644
--- a/sdk/lib/core/uri.dart
+++ b/sdk/lib/core/uri.dart
@@ -4,24 +4,6 @@
 
 part of dart.core;
 
-// Frequently used character codes.
-const int _SPACE = 0x20;
-const int _PERCENT = 0x25;
-const int _PLUS = 0x2B;
-const int _DOT = 0x2E;
-const int _SLASH = 0x2F;
-const int _COLON = 0x3A;
-const int _UPPER_CASE_A = 0x41;
-const int _UPPER_CASE_Z = 0x5A;
-const int _LEFT_BRACKET = 0x5B;
-const int _BACKSLASH = 0x5C;
-const int _RIGHT_BRACKET = 0x5D;
-const int _LOWER_CASE_A = 0x61;
-const int _LOWER_CASE_F = 0x66;
-const int _LOWER_CASE_Z = 0x7A;
-
-const String _hexDigits = "0123456789ABCDEF";
-
 /**
  * A parsed URI, such as a URL.
  *
@@ -33,17 +15,77 @@
  * [uris]: https://www.dartlang.org/docs/dart-up-and-running/ch03.html#uris
  * [libtour]: https://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html
  */
-abstract class Uri {
+class Uri {
   /**
-   * Returns the natural base URI for the current platform.
+   * The scheme component of the URI.
    *
-   * When running in a browser this is the current URL of the current page
-   * (from `window.location.href`).
+   * Returns the empty string if there is no scheme component.
    *
-   * When not running in a browser this is the file URI referencing
-   * the current working directory.
+   * A URI scheme is case insensitive.
+   * The returned scheme is canonicalized to lowercase letters.
    */
-  external static Uri get base;
+  // We represent the missing scheme as an empty string.
+  // A valid scheme cannot be empty.
+  final String scheme;
+
+  /**
+   * The user-info part of the authority.
+   *
+   * Does not distinguish between an empty user-info and an absent one.
+   * The value is always non-null.
+   * Is considered absent if [_host] is `null`.
+   */
+  final String _userInfo;
+
+  /**
+   * The host name of the URI.
+   *
+   * Set to `null` if there is no authority in the URI.
+   * The host name is the only mandatory part of an authority, so we use
+   * it to mark whether an authority part was present or not.
+   */
+  final String _host;
+
+  /**
+   * The port number part of the authority.
+   *
+   * The port. Set to null if there is no port. Normalized to null if
+   * the port is the default port for the scheme.
+   */
+  int _port;
+
+  /**
+   * The path of the URI.
+   *
+   * Always non-null.
+   */
+  String _path;
+
+  // The query content, or null if there is no query.
+  final String _query;
+
+  // The fragment content, or null if there is no fragment.
+  final String _fragment;
+
+  /**
+   * Cache the computed return value of [pathSegements].
+   */
+  List<String> _pathSegments;
+
+  /**
+   * Cache the computed return value of [queryParameters].
+   */
+  Map<String, String> _queryParameters;
+  Map<String, List<String>> _queryParameterLists;
+
+  /// Internal non-verifying constructor. Only call with validated arguments.
+  Uri._internal(this.scheme,
+                this._userInfo,
+                this._host,
+                this._port,
+                this._path,
+                this._query,
+                this._fragment);
 
   /**
    * Creates a new URI from its components.
@@ -116,15 +158,39 @@
    * general delimiters, are escaped if necessary.
    * If `fragment` is omitted or `null`, the URI has no fragment part.
    */
-  factory Uri({String scheme,
-               String userInfo,
+  factory Uri({String scheme : "",
+               String userInfo : "",
                String host,
                int port,
                String path,
                Iterable<String> pathSegments,
                String query,
                Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
-               String fragment}) = _Uri;
+               String fragment}) {
+    scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
+    userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
+    host = _makeHost(host, 0, _stringOrNullLength(host), false);
+    // Special case this constructor for backwards compatibility.
+    if (query == "") query = null;
+    query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
+    fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
+    port = _makePort(port, scheme);
+    bool isFile = (scheme == "file");
+    if (host == null &&
+        (userInfo.isNotEmpty || port != null || isFile)) {
+      host = "";
+    }
+    bool hasAuthority = (host != null);
+    path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
+                     scheme, hasAuthority);
+    if (scheme.isEmpty && host == null && !path.startsWith('/')) {
+      path = _normalizeRelativePath(path);
+    } else {
+      path = _removeDotSegments(path);
+    }
+    return new Uri._internal(scheme, userInfo, host, port,
+                             path, query, fragment);
+  }
 
   /**
    * Creates a new `http` URI from authority, path and query.
@@ -161,7 +227,9 @@
    */
   factory Uri.http(String authority,
                    String unencodedPath,
-                   [Map<String, String> queryParameters]) = _Uri.http;
+                   [Map<String, String> queryParameters]) {
+    return _makeHttpUri("http", authority, unencodedPath, queryParameters);
+  }
 
   /**
    * Creates a new `https` URI from authority, path and query.
@@ -171,7 +239,424 @@
    */
   factory Uri.https(String authority,
                     String unencodedPath,
-                    [Map<String, String> queryParameters]) = _Uri.https;
+                    [Map<String, String> queryParameters]) {
+    return _makeHttpUri("https", authority, unencodedPath, queryParameters);
+  }
+
+  /**
+   * Returns the authority component.
+   *
+   * The authority is formatted from the [userInfo], [host] and [port]
+   * parts.
+   *
+   * Returns the empty string if there is no authority component.
+   */
+  String get authority {
+    if (!hasAuthority) return "";
+    var sb = new StringBuffer();
+    _writeAuthority(sb);
+    return sb.toString();
+  }
+
+  /**
+   * Returns the user info part of the authority component.
+   *
+   * Returns the empty string if there is no user info in the
+   * authority component.
+   */
+  String get userInfo => _userInfo;
+
+  /**
+   * Returns the host part of the authority component.
+   *
+   * Returns the empty string if there is no authority component and
+   * hence no host.
+   *
+   * If the host is an IP version 6 address, the surrounding `[` and `]` is
+   * removed.
+   *
+   * The host string is case-insensitive.
+   * The returned host name is canonicalized to lower-case
+   * with upper-case percent-escapes.
+   */
+  String get host {
+    if (_host == null) return "";
+    if (_host.startsWith('[')) {
+      return _host.substring(1, _host.length - 1);
+    }
+    return _host;
+  }
+
+  /**
+   * Returns the port part of the authority component.
+   *
+   * Returns the defualt port if there is no port number in the authority
+   * component. That's 80 for http, 443 for https, and 0 for everything else.
+   */
+  int get port {
+    if (_port == null) return _defaultPort(scheme);
+    return _port;
+  }
+
+  // The default port for the scheme of this Uri..
+  static int _defaultPort(String scheme) {
+    if (scheme == "http") return 80;
+    if (scheme == "https") return 443;
+    return 0;
+  }
+
+  /**
+   * Returns the path component.
+   *
+   * The returned path is encoded. To get direct access to the decoded
+   * path use [pathSegments].
+   *
+   * Returns the empty string if there is no path component.
+   */
+  String get path => _path;
+
+  /**
+   * Returns the query component. The returned query is encoded. To get
+   * direct access to the decoded query use [queryParameters].
+   *
+   * Returns the empty string if there is no query component.
+   */
+  String get query => (_query == null) ? "" : _query;
+
+  /**
+   * Returns the fragment identifier component.
+   *
+   * Returns the empty string if there is no fragment identifier
+   * component.
+   */
+  String get fragment => (_fragment == null) ? "" : _fragment;
+
+  /**
+   * Creates a new `Uri` object by parsing a URI string.
+   *
+   * If [start] and [end] are provided, only the substring from `start`
+   * to `end` is parsed as a URI.
+   *
+   * If the string is not valid as a URI or URI reference,
+   * a [FormatException] is thrown.
+   */
+  static Uri parse(String uri, [int start = 0, int end]) {
+    // This parsing will not validate percent-encoding, IPv6, etc.
+    // When done splitting into parts, it will call, e.g., [_makeFragment]
+    // to do the final parsing.
+    //
+    // Important parts of the RFC 3986 used here:
+    // URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+    //
+    // hier-part     = "//" authority path-abempty
+    //               / path-absolute
+    //               / path-rootless
+    //               / path-empty
+    //
+    // URI-reference = URI / relative-ref
+    //
+    // absolute-URI  = scheme ":" hier-part [ "?" query ]
+    //
+    // relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
+    //
+    // relative-part = "//" authority path-abempty
+    //               / path-absolute
+    //               / path-noscheme
+    //               / path-empty
+    //
+    // scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+    //
+    // authority     = [ userinfo "@" ] host [ ":" port ]
+    // userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
+    // host          = IP-literal / IPv4address / reg-name
+    // port          = *DIGIT
+    // reg-name      = *( unreserved / pct-encoded / sub-delims )
+    //
+    // path          = path-abempty    ; begins with "/" or is empty
+    //               / path-absolute   ; begins with "/" but not "//"
+    //               / path-noscheme   ; begins with a non-colon segment
+    //               / path-rootless   ; begins with a segment
+    //               / path-empty      ; zero characters
+    //
+    // path-abempty  = *( "/" segment )
+    // path-absolute = "/" [ segment-nz *( "/" segment ) ]
+    // path-noscheme = segment-nz-nc *( "/" segment )
+    // path-rootless = segment-nz *( "/" segment )
+    // path-empty    = 0<pchar>
+    //
+    // segment       = *pchar
+    // segment-nz    = 1*pchar
+    // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
+    //               ; non-zero-length segment without any colon ":"
+    //
+    // pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
+    //
+    // query         = *( pchar / "/" / "?" )
+    //
+    // fragment      = *( pchar / "/" / "?" )
+    const int EOI = -1;
+
+    String scheme = "";
+    String userinfo = "";
+    String host = null;
+    int port = null;
+    String path = null;
+    String query = null;
+    String fragment = null;
+    if (end == null) end = uri.length;
+
+    int index = start;
+    int pathStart = start;
+    // End of input-marker.
+    int char = EOI;
+
+    void parseAuth() {
+      if (index == end) {
+        char = EOI;
+        return;
+      }
+      int authStart = index;
+      int lastColon = -1;
+      int lastAt = -1;
+      char = uri.codeUnitAt(index);
+      while (index < end) {
+        char = uri.codeUnitAt(index);
+        if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
+          break;
+        }
+        if (char == _AT_SIGN) {
+          lastAt = index;
+          lastColon = -1;
+        } else if (char == _COLON) {
+          lastColon = index;
+        } else if (char == _LEFT_BRACKET) {
+          lastColon = -1;
+          int endBracket = uri.indexOf(']', index + 1);
+          if (endBracket == -1) {
+            index = end;
+            char = EOI;
+            break;
+          } else {
+            index = endBracket;
+          }
+        }
+        index++;
+        char = EOI;
+      }
+      int hostStart = authStart;
+      int hostEnd = index;
+      if (lastAt >= 0) {
+        userinfo = _makeUserInfo(uri, authStart, lastAt);
+        hostStart = lastAt + 1;
+      }
+      if (lastColon >= 0) {
+        int portNumber;
+        if (lastColon + 1 < index) {
+          portNumber = 0;
+          for (int i = lastColon + 1; i < index; i++) {
+            int digit = uri.codeUnitAt(i);
+            if (_ZERO > digit || _NINE < digit) {
+              _fail(uri, i, "Invalid port number");
+            }
+            portNumber = portNumber * 10 + (digit - _ZERO);
+          }
+        }
+        port = _makePort(portNumber, scheme);
+        hostEnd = lastColon;
+      }
+      host = _makeHost(uri, hostStart, hostEnd, true);
+      if (index < end) {
+        char = uri.codeUnitAt(index);
+      }
+    }
+
+    // When reaching path parsing, the current character is known to not
+    // be part of the path.
+    const int NOT_IN_PATH = 0;
+    // When reaching path parsing, the current character is part
+    // of the a non-empty path.
+    const int IN_PATH = 1;
+    // When reaching authority parsing, authority is possible.
+    // This is only true at start or right after scheme.
+    const int ALLOW_AUTH = 2;
+
+    // Current state.
+    // Initialized to the default value that is used when exiting the
+    // scheme loop by reaching the end of input.
+    // All other breaks set their own state.
+    int state = NOT_IN_PATH;
+    int i = index;  // Temporary alias for index to avoid bug 19550 in dart2js.
+    while (i < end) {
+      char = uri.codeUnitAt(i);
+      if (char == _QUESTION || char == _NUMBER_SIGN) {
+        state = NOT_IN_PATH;
+        break;
+      }
+      if (char == _SLASH) {
+        state = (i == start) ? ALLOW_AUTH : IN_PATH;
+        break;
+      }
+      if (char == _COLON) {
+        if (i == start) _fail(uri, start, "Invalid empty scheme");
+        scheme = _makeScheme(uri, start, i);
+        i++;
+        if (scheme == "data") {
+          // This generates a URI that is (potentially) not path normalized.
+          // Applying part normalization to a non-hierarchial URI isn't
+          // meaningful.
+          return UriData._parse(uri, i, null).uri;
+        }
+        pathStart = i;
+        if (i == end) {
+          char = EOI;
+          state = NOT_IN_PATH;
+        } else {
+          char = uri.codeUnitAt(i);
+          if (char == _QUESTION || char == _NUMBER_SIGN) {
+            state = NOT_IN_PATH;
+          } else if (char == _SLASH) {
+            state = ALLOW_AUTH;
+          } else {
+            state = IN_PATH;
+          }
+        }
+        break;
+      }
+      i++;
+      char = EOI;
+    }
+    index = i;  // Remove alias when bug is fixed.
+
+    if (state == ALLOW_AUTH) {
+      assert(char == _SLASH);
+      // Have seen one slash either at start or right after scheme.
+      // If two slashes, it's an authority, otherwise it's just the path.
+      index++;
+      if (index == end) {
+        char = EOI;
+        state = NOT_IN_PATH;
+      } else {
+        char = uri.codeUnitAt(index);
+        if (char == _SLASH) {
+          index++;
+          parseAuth();
+          pathStart = index;
+        }
+        if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
+          state = NOT_IN_PATH;
+        } else {
+          state = IN_PATH;
+        }
+      }
+    }
+
+    assert(state == IN_PATH || state == NOT_IN_PATH);
+    if (state == IN_PATH) {
+      // Characters from pathStart to index (inclusive) are known
+      // to be part of the path.
+      while (++index < end) {
+        char = uri.codeUnitAt(index);
+        if (char == _QUESTION || char == _NUMBER_SIGN) {
+          break;
+        }
+        char = EOI;
+      }
+      state = NOT_IN_PATH;
+    }
+
+    assert(state == NOT_IN_PATH);
+    bool hasAuthority = (host != null);
+    path = _makePath(uri, pathStart, index, null, scheme, hasAuthority);
+
+    if (char == _QUESTION) {
+      int numberSignIndex = -1;
+      for (int i = index + 1; i < end; i++) {
+        if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
+          numberSignIndex = i;
+          break;
+        }
+      }
+      if (numberSignIndex < 0) {
+        query = _makeQuery(uri, index + 1, end, null);
+      } else {
+        query = _makeQuery(uri, index + 1, numberSignIndex, null);
+        fragment = _makeFragment(uri, numberSignIndex + 1, end);
+      }
+    } else if (char == _NUMBER_SIGN) {
+      fragment = _makeFragment(uri, index + 1, end);
+    }
+    return new Uri._internal(scheme,
+                             userinfo,
+                             host,
+                             port,
+                             path,
+                             query,
+                             fragment);
+  }
+
+  // Report a parse failure.
+  static void _fail(String uri, int index, String message) {
+    throw new FormatException(message, uri, index);
+  }
+
+  static Uri _makeHttpUri(String scheme,
+                          String authority,
+                          String unencodedPath,
+                          Map<String, String> queryParameters) {
+    var userInfo = "";
+    var host = null;
+    var port = null;
+
+    if (authority != null && authority.isNotEmpty) {
+      var hostStart = 0;
+      // Split off the user info.
+      bool hasUserInfo = false;
+      for (int i = 0; i < authority.length; i++) {
+        if (authority.codeUnitAt(i) == _AT_SIGN) {
+          hasUserInfo = true;
+          userInfo = authority.substring(0, i);
+          hostStart = i + 1;
+          break;
+        }
+      }
+      var hostEnd = hostStart;
+      if (hostStart < authority.length &&
+          authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
+        // IPv6 host.
+        for (; hostEnd < authority.length; hostEnd++) {
+          if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
+        }
+        if (hostEnd == authority.length) {
+          throw new FormatException("Invalid IPv6 host entry.",
+                                    authority, hostStart);
+        }
+        parseIPv6Address(authority, hostStart + 1, hostEnd);
+        hostEnd++;  // Skip the closing bracket.
+        if (hostEnd != authority.length &&
+            authority.codeUnitAt(hostEnd) != _COLON) {
+          throw new FormatException("Invalid end of authority",
+                                    authority, hostEnd);
+        }
+      }
+      // Split host and port.
+      bool hasPort = false;
+      for (; hostEnd < authority.length; hostEnd++) {
+        if (authority.codeUnitAt(hostEnd) == _COLON) {
+          var portString = authority.substring(hostEnd + 1);
+          // We allow the empty port - falling back to initial value.
+          if (portString.isNotEmpty) port = int.parse(portString);
+          break;
+        }
+      }
+      host = authority.substring(hostStart, hostEnd);
+    }
+    return new Uri(scheme: scheme,
+                   userInfo: userInfo,
+                   host: host,
+                   port: port,
+                   pathSegments: unencodedPath.split("/"),
+                   queryParameters: queryParameters);
+  }
 
   /**
    * Creates a new file URI from an absolute or relative file path.
@@ -254,7 +739,11 @@
    *
    * If the path passed is not a legal file path [ArgumentError] is thrown.
    */
-  factory Uri.file(String path, {bool windows}) = _Uri.file;
+  factory Uri.file(String path, {bool windows}) {
+    windows = (windows == null) ? Uri._isWindows : windows;
+    return windows ? _makeWindowsFileUrl(path, false)
+                   : _makeFileUri(path, false);
+  }
 
   /**
    * Like [Uri.file] except that a non-empty URI path ends in a slash.
@@ -263,7 +752,11 @@
    * then a slash is added to the returned URI's path.
    * In all other cases, the result is the same as returned by `Uri.file`.
    */
-  factory Uri.directory(String path, {bool windows}) = _Uri.directory;
+  factory Uri.directory(String path, {bool windows}) {
+    windows = (windows == null) ? Uri._isWindows : windows;
+    return windows ? _makeWindowsFileUrl(path, true)
+                   : _makeFileUri(path, true);
+  }
 
   /**
    * Creates a `data:` URI containing the [content] string.
@@ -334,1280 +827,16 @@
   }
 
   /**
-   * The scheme component of the URI.
+   * Returns the natural base URI for the current platform.
    *
-   * Returns the empty string if there is no scheme component.
+   * When running in a browser this is the current URL (from
+   * `window.location.href`).
    *
-   * A URI scheme is case insensitive.
-   * The returned scheme is canonicalized to lowercase letters.
+   * When not running in a browser this is the file URI referencing
+   * the current working directory.
    */
-  String get scheme;
-
-  /**
-   * Returns the authority component.
-   *
-   * The authority is formatted from the [userInfo], [host] and [port]
-   * parts.
-   *
-   * Returns the empty string if there is no authority component.
-   */
-  String get authority;
-
-  /**
-   * Returns the user info part of the authority component.
-   *
-   * Returns the empty string if there is no user info in the
-   * authority component.
-   */
-  String get userInfo;
-
-  /**
-   * Returns the host part of the authority component.
-   *
-   * Returns the empty string if there is no authority component and
-   * hence no host.
-   *
-   * If the host is an IP version 6 address, the surrounding `[` and `]` is
-   * removed.
-   *
-   * The host string is case-insensitive.
-   * The returned host name is canonicalized to lower-case
-   * with upper-case percent-escapes.
-   */
-  String get host;
-
-  /**
-   * Returns the port part of the authority component.
-   *
-   * Returns the defualt port if there is no port number in the authority
-   * component. That's 80 for http, 443 for https, and 0 for everything else.
-   */
-  int get port;
-
-  /**
-   * Returns the path component.
-   *
-   * The returned path is encoded. To get direct access to the decoded
-   * path use [pathSegments].
-   *
-   * Returns the empty string if there is no path component.
-   */
-  String get path;
-
-  /**
-   * Returns the query component. The returned query is encoded. To get
-   * direct access to the decoded query use [queryParameters].
-   *
-   * Returns the empty string if there is no query component.
-   */
-  String get query;
-
-  /**
-   * Returns the fragment identifier component.
-   *
-   * Returns the empty string if there is no fragment identifier
-   * component.
-   */
-  String get fragment;
-
-  /**
-   * Returns the URI path split into its segments. Each of the segments in the
-   * returned list have been decoded. If the path is empty the empty list will
-   * be returned. A leading slash `/` does not affect the segments returned.
-   *
-   * The returned list is unmodifiable and will throw [UnsupportedError] on any
-   * calls that would mutate it.
-   */
-  List<String> get pathSegments;
-
-  /**
-   * Returns the URI query split into a map according to the rules
-   * specified for FORM post in the [HTML 4.01 specification section
-   * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
-   * Each key and value in the returned map has been decoded.
-   * If there is no query the empty map is returned.
-   *
-   * Keys in the query string that have no value are mapped to the
-   * empty string.
-   * If a key occurs more than once in the query string, it is mapped to
-   * an arbitrary choice of possible value.
-   * The [queryParametersAll] getter can provide a map
-   * that maps keys to all of their values.
-   *
-   * The returned map is unmodifiable.
-   */
-  Map<String, String> get queryParameters;
-
-  /**
-   * Returns the URI query split into a map according to the rules
-   * specified for FORM post in the [HTML 4.01 specification section
-   * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
-   * Each key and value in the returned map has been decoded. If there is no
-   * query the empty map is returned.
-   *
-   * Keys are mapped to lists of their values. If a key occurs only once,
-   * its value is a singleton list. If a key occurs with no value, the
-   * empty string is used as the value for that occurrence.
-   *
-   * The returned map and the lists it contains are unmodifiable.
-   */
-  Map<String, List<String>> get queryParametersAll;
-
-  /**
-   * Returns whether the URI is absolute.
-   *
-   * A URI is an absolute URI in the sense of RFC 3986 if it has a scheme
-   * and no fragment.
-   */
-  bool get isAbsolute;
-
-  /**
-   * Returns whether the URI has a [scheme] component.
-   */
-  bool get hasScheme => scheme.isNotEmpty;
-
-  /**
-   * Returns whether the URI has an [authority] component.
-   */
-  bool get hasAuthority;
-
-  /**
-   * Returns whether the URI has an explicit port.
-   *
-   * If the port number is the default port number
-   * (zero for unrecognized schemes, with http (80) and https (443) being
-   * recognized),
-   * then the port is made implicit and omitted from the URI.
-   */
-  bool get hasPort;
-
-  /**
-   * Returns whether the URI has a query part.
-   */
-  bool get hasQuery;
-
-  /**
-   * Returns whether the URI has a fragment part.
-   */
-  bool get hasFragment;
-
-  /**
-   * Returns whether the URI has an empty path.
-   */
-  bool get hasEmptyPath;
-
-  /**
-   * Returns whether the URI has an absolute path (starting with '/').
-   */
-  bool get hasAbsolutePath;
-
-  /**
-   * Returns the origin of the URI in the form scheme://host:port for the
-   * schemes http and https.
-   *
-   * It is an error if the scheme is not "http" or "https".
-   *
-   * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
-   */
-  String get origin;
-
-  /**
-   * Returns the file path from a file URI.
-   *
-   * The returned path has either Windows or non-Windows
-   * semantics.
-   *
-   * For non-Windows semantics the slash ("/") is used to separate
-   * path segments.
-   *
-   * For Windows semantics the backslash ("\") separator is used to
-   * separate path segments.
-   *
-   * If the URI is absolute the path starts with a path separator
-   * unless Windows semantics is used and the first path segment is a
-   * drive letter. When Windows semantics is used a host component in
-   * the uri in interpreted as a file server and a UNC path is
-   * returned.
-   *
-   * The default for whether to use Windows or non-Windows semantics
-   * determined from the platform Dart is running on. When running in
-   * the standalone VM this is detected by the VM based on the
-   * operating system. When running in a browser non-Windows semantics
-   * is always used.
-   *
-   * To override the automatic detection of which semantics to use pass
-   * a value for [windows]. Passing `true` will use Windows
-   * semantics and passing `false` will use non-Windows semantics.
-   *
-   * If the URI ends with a slash (i.e. the last path component is
-   * empty) the returned file path will also end with a slash.
-   *
-   * With Windows semantics URIs starting with a drive letter cannot
-   * be relative to the current drive on the designated drive. That is
-   * for the URI `file:///c:abc` calling `toFilePath` will throw as a
-   * path segment cannot contain colon on Windows.
-   *
-   * Examples using non-Windows semantics (resulting of calling
-   * toFilePath in comment):
-   *
-   *     Uri.parse("xxx/yyy");  // xxx/yyy
-   *     Uri.parse("xxx/yyy/");  // xxx/yyy/
-   *     Uri.parse("file:///xxx/yyy");  // /xxx/yyy
-   *     Uri.parse("file:///xxx/yyy/");  // /xxx/yyy/
-   *     Uri.parse("file:///C:");  // /C:
-   *     Uri.parse("file:///C:a");  // /C:a
-   *
-   * Examples using Windows semantics (resulting URI in comment):
-   *
-   *     Uri.parse("xxx/yyy");  // xxx\yyy
-   *     Uri.parse("xxx/yyy/");  // xxx\yyy\
-   *     Uri.parse("file:///xxx/yyy");  // \xxx\yyy
-   *     Uri.parse("file:///xxx/yyy/");  // \xxx\yyy/
-   *     Uri.parse("file:///C:/xxx/yyy");  // C:\xxx\yyy
-   *     Uri.parse("file:C:xxx/yyy");  // Throws as a path segment
-   *                                   // cannot contain colon on Windows.
-   *     Uri.parse("file://server/share/file");  // \\server\share\file
-   *
-   * If the URI is not a file URI calling this throws
-   * [UnsupportedError].
-   *
-   * If the URI cannot be converted to a file path calling this throws
-   * [UnsupportedError].
-   */
-  // TODO(lrn): Deprecate and move functionality to File class or similar.
-  // The core libraries should not worry about the platform.
-  String toFilePath({bool windows});
-
-  /**
-   * Access the structure of a `data:` URI.
-   *
-   * Returns a [UriData] object for `data:` URIs and `null` for all other
-   * URIs.
-   * The [UriData] object can be used to access the media type and data
-   * of a `data:` URI.
-   */
-  UriData get data;
-
-  /// Returns a hash code computed as `toString().hashCode`.
-  ///
-  /// This guarantees that URIs with the same normalized
-  int get hashCode;
-
-  /// A URI is equal to another URI with the same normalized representation.
-  bool operator==(Object other);
-
-  /// Returns the normalized string representation of the URI.
-  String toString();
-
-  /**
-   * Returns a new `Uri` based on this one, but with some parts replaced.
-   *
-   * This method takes the same parameters as the [new Uri] constructor,
-   * and they have the same meaning.
-   *
-   * At most one of [path] and [pathSegments] must be provided.
-   * Likewise, at most one of [query] and [queryParameters] must be provided.
-   *
-   * Each part that is not provided will default to the corresponding
-   * value from this `Uri` instead.
-   *
-   * This method is different from [Uri.resolve] which overrides in a
-   * hierarchial manner,
-   * and can instead replace each part of a `Uri` individually.
-   *
-   * Example:
-   *
-   *     Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
-   *     Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
-   *     print(uri2);  // prints "A://b@c:4/D/E/E/?f#G"
-   *
-   * This method acts similarly to using the `new Uri` constructor with
-   * some of the arguments taken from this `Uri` . Example:
-   *
-   *     Uri uri3 = new Uri(
-   *         scheme: "A",
-   *         userInfo: uri1.userInfo,
-   *         host: uri1.host,
-   *         port: uri1.port,
-   *         path: "D/E/E",
-   *         query: uri1.query,
-   *         fragment: "G");
-   *     print(uri3);  // prints "A://b@c:4/D/E/E/?f#G"
-   *     print(uri2 == uri3);  // prints true.
-   *
-   * Using this method can be seen as a shorthand for the `Uri` constructor
-   * call above, but may also be slightly faster because the parts taken
-   * from this `Uri` need not be checked for validity again.
-   */
-  Uri replace({String scheme,
-               String userInfo,
-               String host,
-               int port,
-               String path,
-               Iterable<String> pathSegments,
-               String query,
-               Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
-               String fragment});
-
-  /**
-   * Returns a `Uri` that differs from this only in not having a fragment.
-   *
-   * If this `Uri` does not have a fragment, it is itself returned.
-   */
-  Uri removeFragment();
-
-  /**
-   * Resolve [reference] as an URI relative to `this`.
-   *
-   * First turn [reference] into a URI using [Uri.parse]. Then resolve the
-   * resulting URI relative to `this`.
-   *
-   * Returns the resolved URI.
-   *
-   * See [resolveUri] for details.
-   */
-  Uri resolve(String reference);
-
-  /**
-   * Resolve [reference] as an URI relative to `this`.
-   *
-   * Returns the resolved URI.
-   *
-   * The algorithm "Transform Reference" for resolving a reference is described
-   * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
-   *
-   * Updated to handle the case where the base URI is just a relative path -
-   * that is: when it has no scheme or authority and the path does not start
-   * with a slash.
-   * In that case, the paths are combined without removing leading "..", and
-   * an empty path is not converted to "/".
-   */
-  Uri resolveUri(Uri reference);
-
-  /**
-   * Returns a URI where the path has been normalized.
-   *
-   * A normalized path does not contain `.` segments or non-leading `..`
-   * segments.
-   * Only a relative path with no scheme or authority may contain
-   * leading `..` segments,
-   * a path that starts with `/` will also drop any leading `..` segments.
-   *
-   * This uses the same normalization strategy as `new Uri().resolve(this)`.
-   *
-   * Does not change any part of the URI except the path.
-   *
-   * The default implementation of `Uri` always normalizes paths, so calling
-   * this function has no effect.
-   */
-  Uri normalizePath();
-
-  /**
-   * Creates a new `Uri` object by parsing a URI string.
-   *
-   * If [start] and [end] are provided, only the substring from `start`
-   * to `end` is parsed as a URI.
-   *
-   * If the string is not valid as a URI or URI reference,
-   * a [FormatException] is thrown.
-   */
-  static Uri parse(String uri, [int start = 0, int end]) {
-    // This parsing will not validate percent-encoding, IPv6, etc.
-    // When done splitting into parts, it will call, e.g., [_makeFragment]
-    // to do the final parsing.
-    //
-    // Important parts of the RFC 3986 used here:
-    // URI           = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
-    //
-    // hier-part     = "//" authority path-abempty
-    //               / path-absolute
-    //               / path-rootless
-    //               / path-empty
-    //
-    // URI-reference = URI / relative-ref
-    //
-    // absolute-URI  = scheme ":" hier-part [ "?" query ]
-    //
-    // relative-ref  = relative-part [ "?" query ] [ "#" fragment ]
-    //
-    // relative-part = "//" authority path-abempty
-    //               / path-absolute
-    //               / path-noscheme
-    //               / path-empty
-    //
-    // scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
-    //
-    // authority     = [ userinfo "@" ] host [ ":" port ]
-    // userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
-    // host          = IP-literal / IPv4address / reg-name
-    // port          = *DIGIT
-    // reg-name      = *( unreserved / pct-encoded / sub-delims )
-    //
-    // path          = path-abempty    ; begins with "/" or is empty
-    //               / path-absolute   ; begins with "/" but not "//"
-    //               / path-noscheme   ; begins with a non-colon segment
-    //               / path-rootless   ; begins with a segment
-    //               / path-empty      ; zero characters
-    //
-    // path-abempty  = *( "/" segment )
-    // path-absolute = "/" [ segment-nz *( "/" segment ) ]
-    // path-noscheme = segment-nz-nc *( "/" segment )
-    // path-rootless = segment-nz *( "/" segment )
-    // path-empty    = 0<pchar>
-    //
-    // segment       = *pchar
-    // segment-nz    = 1*pchar
-    // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
-    //               ; non-zero-length segment without any colon ":"
-    //
-    // pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
-    //
-    // query         = *( pchar / "/" / "?" )
-    //
-    // fragment      = *( pchar / "/" / "?" )
-    end ??= uri.length;
-
-    // Special case data:URIs. Ignore case when testing.
-    if (end >= start + 5) {
-      int dataDelta = _startsWithData(uri, start);
-      if (dataDelta == 0) {
-        // The case is right.
-        if (start > 0 || end < uri.length) uri = uri.substring(start, end);
-        return UriData._parse(uri, 5, null).uri;
-      } else if (dataDelta == 0x20) {
-        return UriData._parse(uri.substring(start + 5, end), 0, null).uri;
-      }
-      // Otherwise the URI doesn't start with "data:" or any case variant of it.
-    }
-
-    // The following index-normalization belongs with the scanning, but is
-    // easier to do here because we already have extracted variables from the
-    // indices list.
-    var indices = new List<int>(8);//new List<int>.filled(8, start - 1);
-
-    // Set default values for each position.
-    // The value will either be correct in some cases where it isn't set
-    // by the scanner, or it is clearly recognizable as an unset value.
-    indices
-      ..[0] = 0
-      ..[_schemeEndIndex] = start - 1
-      ..[_hostStartIndex] = start - 1
-      ..[_notSimpleIndex] = start - 1
-      ..[_portStartIndex] = start
-      ..[_pathStartIndex] = start
-      ..[_queryStartIndex] = end
-      ..[_fragmentStartIndex] = end;
-    var state = _scan(uri, start, end, _uriStart, indices);
-    // Some states that should be non-simple, but the URI ended early.
-    // Paths that end at a ".." must be normalized to end in "../".
-    if (state >= _nonSimpleEndStates) {
-      indices[_notSimpleIndex] = end;
-    }
-    int schemeEnd = indices[_schemeEndIndex];
-    if (schemeEnd >= start) {
-      // Rescan the scheme part now that we know it's not a path.
-      state = _scan(uri, start, schemeEnd, _schemeStart, indices);
-      if (state == _schemeStart) {
-        // Empty scheme.
-        indices[_notSimpleIndex] = schemeEnd;
-      }
-    }
-    // The returned positions are limited by the scanners ability to write only
-    // one position per character, and only the current position.
-    // Scanning from left to right, we only know whether something is a scheme
-    // or a path when we see a `:` or `/`, and likewise we only know if the first
-    // `/` is part of the path or is leading an authority component when we see
-    // the next character.
-
-    int hostStart     = indices[_hostStartIndex] + 1;
-    int portStart     = indices[_portStartIndex];
-    int pathStart     = indices[_pathStartIndex];
-    int queryStart    = indices[_queryStartIndex];
-    int fragmentStart = indices[_fragmentStartIndex];
-
-    // We may discover scheme while handling special cases.
-    String scheme;
-
-    // Derive some positions that weren't set to normalize the indices.
-    // If pathStart isn't set (it's before scheme end or host start), then
-    // the path is empty.
-    if (fragmentStart < queryStart) queryStart = fragmentStart;
-    if (pathStart < hostStart || pathStart <= schemeEnd) {
-      pathStart = queryStart;
-    }
-    // If there is an authority with no port, set the port position
-    // to be at the end of the authority (equal to pathStart).
-    // This also handles a ":" in a user-info component incorrectly setting
-    // the port start position.
-    if (portStart < hostStart) portStart = pathStart;
-
-    assert(hostStart == start || schemeEnd <= hostStart);
-    assert(hostStart <= portStart);
-    assert(schemeEnd <= pathStart);
-    assert(portStart <= pathStart);
-    assert(pathStart <= queryStart);
-    assert(queryStart <= fragmentStart);
-
-    bool isSimple = indices[_notSimpleIndex] < start;
-
-    if (isSimple) {
-      // Check/do normalizations that weren't detected by the scanner.
-      // This includes removal of empty port or userInfo,
-      // or scheme specific port and path normalizations.
-      if (hostStart > schemeEnd + 3) {
-        // Always be non-simple if URI contains user-info.
-        // The scanner doesn't set the not-simple position in this case because
-        // it's setting the host-start position instead.
-        isSimple = false;
-      } else if (portStart > start && portStart + 1 == pathStart) {
-        // If the port is empty, it should be omitted.
-        // Pathological case, don't bother correcting it.
-        isSimple = false;
-      } else if (queryStart < end &&
-                 (queryStart == pathStart + 2 &&
-                  uri.startsWith("..", pathStart)) ||
-                 (queryStart > pathStart + 2 &&
-                  uri.startsWith("/..", queryStart - 3))) {
-        // The path ends in a ".." segment. This should be normalized to "../".
-        // We didn't detect this while scanning because a query or fragment was
-        // detected at the same time (which is why we only need to check this
-        // if there is something after the path).
-        isSimple = false;
-      } else {
-        // There are a few scheme-based normalizations that
-        // the scanner couldn't check.
-        // That means that the input is very close to simple, so just do
-        // the normalizations.
-        if (schemeEnd == start + 4) {
-          // Do scheme based normalizations for file, http.
-          if (uri.startsWith("file", start)) {
-            scheme = "file";
-            if (hostStart <= start) {
-              // File URIs should have an authority.
-              // Paths after an authority should be absolute.
-              String schemeAuth = "file://";
-              int delta = 2;
-              if (!uri.startsWith("/", pathStart)) {
-                schemeAuth = "file:///";
-                delta = 3;
-              }
-              uri = schemeAuth + uri.substring(pathStart, end);
-              schemeEnd -= start;
-              hostStart = 7;
-              portStart = 7;
-              pathStart = 7;
-              queryStart += delta - start;
-              fragmentStart += delta - start;
-              start = 0;
-              end = uri.length;
-            } else if (pathStart == queryStart) {
-              // Uri has authority and empty path. Add "/" as path.
-              if (start == 0 && end == uri.length) {
-                uri = uri.replaceRange(pathStart, queryStart, "/");
-                queryStart += 1;
-                fragmentStart += 1;
-                end += 1;
-              } else {
-                uri = "${uri.substring(start, pathStart)}/"
-                      "${uri.substring(queryStart, end)}";
-                schemeEnd -= start;
-                hostStart -= start;
-                portStart -= start;
-                pathStart -= start;
-                queryStart += 1 - start;
-                fragmentStart += 1 - start;
-                start = 0;
-                end = uri.length;
-              }
-            }
-          } else if (uri.startsWith("http", start)) {
-            scheme = "http";
-            // HTTP URIs should not have an explicit port of 80.
-            if (portStart > start && portStart + 3 == pathStart &&
-                uri.startsWith("80", portStart + 1)) {
-              if (start == 0 && end == uri.length) {
-                uri = uri.replaceRange(portStart, pathStart, "");
-                pathStart -= 3;
-                queryStart -= 3;
-                fragmentStart -= 3;
-                end -= 3;
-              } else {
-                uri = uri.substring(start, portStart) +
-                      uri.substring(pathStart, end);
-                schemeEnd -= start;
-                hostStart -= start;
-                portStart -= start;
-                pathStart -= 3 + start;
-                queryStart -= 3 + start;
-                fragmentStart -= 3 + start;
-                start = 0;
-                end = uri.length;
-              }
-            }
-          }
-        } else if (schemeEnd == start + 5 && uri.startsWith("https", start)) {
-          scheme = "https";
-          // HTTPS URIs should not have an explicit port of 443.
-          if (portStart > start && portStart + 4 == pathStart &&
-              uri.startsWith("443", portStart + 1)) {
-            if (start == 0 && end == uri.length) {
-              uri = uri.replaceRange(portStart, pathStart, "");
-              pathStart -= 4;
-              queryStart -= 4;
-              fragmentStart -= 4;
-              end -= 3;
-            } else {
-              uri = uri.substring(start, portStart) +
-                    uri.substring(pathStart, end);
-              schemeEnd -= start;
-              hostStart -= start;
-              portStart -= start;
-              pathStart -= 4 + start;
-              queryStart -= 4 + start;
-              fragmentStart -= 4 + start;
-              start = 0;
-              end = uri.length;
-            }
-          }
-        }
-      }
-    }
-
-    if (isSimple) {
-      if (start > 0 || end < uri.length) {
-        uri = uri.substring(start, end);
-        schemeEnd -= start;
-        hostStart -= start;
-        portStart -= start;
-        pathStart -= start;
-        queryStart -= start;
-        fragmentStart -= start;
-      }
-      return new _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart,
-                            queryStart, fragmentStart, scheme);
-
-    }
-
-    return new _Uri.notSimple(uri, start, end, schemeEnd, hostStart, portStart,
-                              pathStart, queryStart, fragmentStart, scheme);
-  }
-
-  /**
-   * Encode the string [component] using percent-encoding to make it
-   * safe for literal use as a URI component.
-   *
-   * All characters except uppercase and lowercase letters, digits and
-   * the characters `-_.!~*'()` are percent-encoded. This is the
-   * set of characters specified in RFC 2396 and the which is
-   * specified for the encodeUriComponent in ECMA-262 version 5.1.
-   *
-   * When manually encoding path segments or query components remember
-   * to encode each part separately before building the path or query
-   * string.
-   *
-   * For encoding the query part consider using
-   * [encodeQueryComponent].
-   *
-   * To avoid the need for explicitly encoding use the [pathSegments]
-   * and [queryParameters] optional named arguments when constructing
-   * a [Uri].
-   */
-  static String encodeComponent(String component) {
-    return _Uri._uriEncode(_Uri._unreserved2396Table, component, UTF8, false);
-  }
-
-  /**
-   * Encode the string [component] according to the HTML 4.01 rules
-   * for encoding the posting of a HTML form as a query string
-   * component.
-   *
-   * Encode the string [component] according to the HTML 4.01 rules
-   * for encoding the posting of a HTML form as a query string
-   * component.
-
-   * The component is first encoded to bytes using [encoding].
-   * The default is to use [UTF8] encoding, which preserves all
-   * the characters that don't need encoding.
-
-   * Then the resulting bytes are "percent-encoded". This transforms
-   * spaces (U+0020) to a plus sign ('+') and all bytes that are not
-   * the ASCII decimal digits, letters or one of '-._~' are written as
-   * a percent sign '%' followed by the two-digit hexadecimal
-   * representation of the byte.
-
-   * Note that the set of characters which are percent-encoded is a
-   * superset of what HTML 4.01 requires, since it refers to RFC 1738
-   * for reserved characters.
-   *
-   * When manually encoding query components remember to encode each
-   * part separately before building the query string.
-   *
-   * To avoid the need for explicitly encoding the query use the
-   * [queryParameters] optional named arguments when constructing a
-   * [Uri].
-   *
-   * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
-   * details.
-   */
-  static String encodeQueryComponent(String component,
-                                     {Encoding encoding: UTF8}) {
-    return _Uri._uriEncode(_Uri._unreservedTable, component, encoding, true);
-  }
-
-  /**
-   * Decodes the percent-encoding in [encodedComponent].
-   *
-   * Note that decoding a URI component might change its meaning as
-   * some of the decoded characters could be characters with are
-   * delimiters for a given URI componene type. Always split a URI
-   * component using the delimiters for the component before decoding
-   * the individual parts.
-   *
-   * For handling the [path] and [query] components consider using
-   * [pathSegments] and [queryParameters] to get the separated and
-   * decoded component.
-   */
-  static String decodeComponent(String encodedComponent) {
-    return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
-                           UTF8, false);
-  }
-
-  /**
-   * Decodes the percent-encoding in [encodedComponent], converting
-   * pluses to spaces.
-   *
-   * It will create a byte-list of the decoded characters, and then use
-   * [encoding] to decode the byte-list to a String. The default encoding is
-   * UTF-8.
-   */
-  static String decodeQueryComponent(
-      String encodedComponent,
-      {Encoding encoding: UTF8}) {
-    return _Uri._uriDecode(encodedComponent, 0, encodedComponent.length,
-                           encoding, true);
-  }
-
-  /**
-   * Encode the string [uri] using percent-encoding to make it
-   * safe for literal use as a full URI.
-   *
-   * All characters except uppercase and lowercase letters, digits and
-   * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
-   * is the set of characters specified in in ECMA-262 version 5.1 for
-   * the encodeURI function .
-   */
-  static String encodeFull(String uri) {
-    return _Uri._uriEncode(_Uri._encodeFullTable, uri, UTF8, false);
-  }
-
-  /**
-   * Decodes the percent-encoding in [uri].
-   *
-   * Note that decoding a full URI might change its meaning as some of
-   * the decoded characters could be reserved characters. In most
-   * cases an encoded URI should be parsed into components using
-   * [Uri.parse] before decoding the separate components.
-   */
-  static String decodeFull(String uri) {
-    return _Uri._uriDecode(uri, 0, uri.length, UTF8, false);
-  }
-
-  /**
-   * Returns the [query] split into a map according to the rules
-   * specified for FORM post in the [HTML 4.01 specification section
-   * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
-   * Each key and value in the returned map has been decoded. If the [query]
-   * is the empty string an empty map is returned.
-   *
-   * Keys in the query string that have no value are mapped to the
-   * empty string.
-   *
-   * Each query component will be decoded using [encoding]. The default encoding
-   * is UTF-8.
-   */
-  static Map<String, String> splitQueryString(String query,
-                                              {Encoding encoding: UTF8}) {
-    return query.split("&").fold({}, (map, element) {
-      int index = element.indexOf("=");
-      if (index == -1) {
-        if (element != "") {
-          map[decodeQueryComponent(element, encoding: encoding)] = "";
-        }
-      } else if (index != 0) {
-        var key = element.substring(0, index);
-        var value = element.substring(index + 1);
-        map[decodeQueryComponent(key, encoding: encoding)] =
-            decodeQueryComponent(value, encoding: encoding);
-      }
-      return map;
-    });
-  }
-
-
-  /**
-   * Parse the [host] as an IP version 4 (IPv4) address, returning the address
-   * as a list of 4 bytes in network byte order (big endian).
-   *
-   * Throws a [FormatException] if [host] is not a valid IPv4 address
-   * representation.
-   */
-  static List<int> parseIPv4Address(String host) =>
-       _parseIPv4Address(host, 0, host.length);
-
-  /// Implementation of [parseIPv4Address] that can work on a substring.
-  static List<int> _parseIPv4Address(String host, int start, int end) {
-    void error(String msg, int position) {
-      throw new FormatException('Illegal IPv4 address, $msg', host, position);
-    }
-
-    var result = new Uint8List(4);
-    int partIndex = 0;
-    int partStart = start;
-    for (int i = start; i < end; i++) {
-      int char = host.codeUnitAt(i);
-      if (char != _DOT) {
-        if (char ^ 0x30 > 9) {
-          // Fail on a non-digit character.
-          error("invalid character", i);
-        }
-      } else {
-        if (partIndex == 3) {
-          error('IPv4 address should contain exactly 4 parts', i);
-        }
-        int part = int.parse(host.substring(partStart, i));
-        if (part > 255) {
-          error("each part must be in the range 0..255", partStart);
-        }
-        result[partIndex++] = part;
-        partStart = i + 1;
-      }
-    }
-
-    if (partIndex != 3) {
-      error('IPv4 address should contain exactly 4 parts', end);
-    }
-
-    int part = int.parse(host.substring(partStart, end));
-    if (part > 255) {
-      error("each part must be in the range 0..255", partStart);
-    }
-    result[partIndex] = part;
-
-    return result;
-  }
-
-  /**
-   * Parse the [host] as an IP version 6 (IPv6) address, returning the address
-   * as a list of 16 bytes in network byte order (big endian).
-   *
-   * Throws a [FormatException] if [host] is not a valid IPv6 address
-   * representation.
-   *
-   * Acts on the substring from [start] to [end]. If [end] is omitted, it
-   * defaults ot the end of the string.
-   *
-   * Some examples of IPv6 addresses:
-   *  * ::1
-   *  * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
-   *  * 3ffe:2a00:100:7031::1
-   *  * ::FFFF:129.144.52.38
-   *  * 2010:836B:4179::836B:4179
-   */
-  static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
-    if (end == null) end = host.length;
-    // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, separated
-    // by `:`'s, with the following exceptions:
-    //
-    //  - One (and only one) wildcard (`::`) may be present, representing a fill
-    //    of 0's. The IPv6 `::` is thus 16 bytes of `0`.
-    //  - The last two parts may be replaced by an IPv4 "dotted-quad" address.
-
-    // Helper function for reporting a badly formatted IPv6 address.
-    void error(String msg, [position]) {
-      throw new FormatException('Illegal IPv6 address, $msg', host, position);
-    }
-
-    // Parse a hex block.
-    int parseHex(int start, int end) {
-      if (end - start > 4) {
-        error('an IPv6 part can only contain a maximum of 4 hex digits', start);
-      }
-      int value = int.parse(host.substring(start, end), radix: 16);
-      if (value < 0 || value > 0xFFFF) {
-        error('each part must be in the range of `0x0..0xFFFF`', start);
-      }
-      return value;
-    }
-
-    if (host.length < 2) error('address is too short');
-    List<int> parts = [];
-    bool wildcardSeen = false;
-    // Set if seeing a ".", suggesting that there is an IPv4 address.
-    bool seenDot = false;
-    int partStart = start;
-    // Parse all parts, except a potential last one.
-    for (int i = start; i < end; i++) {
-      int char = host.codeUnitAt(i);
-      if (char == _COLON) {
-        if (i == start) {
-          // If we see a `:` in the beginning, expect wildcard.
-          i++;
-          if (host.codeUnitAt(i) != _COLON) {
-            error('invalid start colon.', i);
-          }
-          partStart = i;
-        }
-        if (i == partStart) {
-          // Wildcard. We only allow one.
-          if (wildcardSeen) {
-            error('only one wildcard `::` is allowed', i);
-          }
-          wildcardSeen = true;
-          parts.add(-1);
-        } else {
-          // Found a single colon. Parse [partStart..i] as a hex entry.
-          parts.add(parseHex(partStart, i));
-        }
-        partStart = i + 1;
-      } else if (char == _DOT) {
-        seenDot = true;
-      }
-    }
-    if (parts.length == 0) error('too few parts');
-    bool atEnd = (partStart == end);
-    bool isLastWildcard = (parts.last == -1);
-    if (atEnd && !isLastWildcard) {
-      error('expected a part after last `:`', end);
-    }
-    if (!atEnd) {
-      if (!seenDot) {
-        parts.add(parseHex(partStart, end));
-      } else {
-        List<int> last = _parseIPv4Address(host, partStart, end);
-        parts.add(last[0] << 8 | last[1]);
-        parts.add(last[2] << 8 | last[3]);
-      }
-    }
-    if (wildcardSeen) {
-      if (parts.length > 7) {
-        error('an address with a wildcard must have less than 7 parts');
-      }
-    } else if (parts.length != 8) {
-      error('an address without a wildcard must contain exactly 8 parts');
-    }
-    List<int> bytes = new Uint8List(16);
-    for (int i = 0, index = 0; i < parts.length; i++) {
-      int value = parts[i];
-      if (value == -1) {
-        int wildCardLength = 9 - parts.length;
-        for (int j = 0; j < wildCardLength; j++) {
-          bytes[index] = 0;
-          bytes[index + 1] = 0;
-          index += 2;
-        }
-      } else {
-        bytes[index] = value >> 8;
-        bytes[index + 1] = value & 0xff;
-        index += 2;
-      }
-    }
-    return bytes;
-  }
-}
-
-class _Uri implements Uri {
-  // We represent the missing scheme as an empty string.
-  // A valid scheme cannot be empty.
-  final String scheme;
-
-  /**
-   * The user-info part of the authority.
-   *
-   * Does not distinguish between an empty user-info and an absent one.
-   * The value is always non-null.
-   * Is considered absent if [_host] is `null`.
-   */
-  final String _userInfo;
-
-  /**
-   * The host name of the URI.
-   *
-   * Set to `null` if there is no authority in the URI.
-   * The host name is the only mandatory part of an authority, so we use
-   * it to mark whether an authority part was present or not.
-   */
-  final String _host;
-
-  /**
-   * The port number part of the authority.
-   *
-   * The port. Set to null if there is no port. Normalized to null if
-   * the port is the default port for the scheme.
-   */
-  int _port;
-
-  /**
-   * The path of the URI.
-   *
-   * Always non-null.
-   */
-  String _path;
-
-  // The query content, or null if there is no query.
-  final String _query;
-
-  // The fragment content, or null if there is no fragment.
-  final String _fragment;
-
-  /**
-   * Cache the computed return value of [pathSegements].
-   */
-  List<String> _pathSegments;
-
-  /**
-   * Cache of the full normalized text representation of the URI.
-   */
-  String _text;
-
-  /**
-   * Cache of the hashCode of [_text].
-   *
-   * Is null until computed.
-   */
-  int _hashCodeCache;
-
-  /**
-   * Cache the computed return value of [queryParameters].
-   */
-  Map<String, String> _queryParameters;
-  Map<String, List<String>> _queryParameterLists;
-
-  /// Internal non-verifying constructor. Only call with validated arguments.
-  _Uri._internal(this.scheme,
-                 this._userInfo,
-                 this._host,
-                 this._port,
-                 this._path,
-                 this._query,
-                 this._fragment);
-
-  /// Create a [_Uri] from parts of [uri].
-  ///
-  /// The parameters specify the start/end of particular components of the URI.
-  /// The [scheme] may contain a string representing a normalized scheme
-  /// component if one has already been discovered.
-  factory _Uri.notSimple(String uri, int start, int end, int schemeEnd,
-                        int hostStart, int portStart, int pathStart,
-                        int queryStart, int fragmentStart, String scheme) {
-    if (scheme == null) {
-      scheme = "";
-      if (schemeEnd > start) {
-        scheme = _makeScheme(uri, start, schemeEnd);
-      } else if (schemeEnd == start) {
-        _fail(uri, start, "Invalid empty scheme");
-      }
-    }
-    String userInfo = "";
-    String host;
-    int port;
-    if (hostStart > start) {
-      int userInfoStart = schemeEnd + 3;
-      if (userInfoStart < hostStart) {
-        userInfo = _makeUserInfo(uri, userInfoStart, hostStart - 1);
-      }
-      host = _makeHost(uri, hostStart, portStart, false);
-      if (portStart + 1 < pathStart) {
-        // Should throw because invalid.
-        port = int.parse(uri.substring(portStart + 1, pathStart), onError: (_) {
-          throw new FormatException("Invalid port", uri, portStart + 1);
-        });
-        port = _makePort(port, scheme);
-      }
-    }
-    String path = _makePath(uri, pathStart, queryStart, null,
-                            scheme, host != null);
-    String query;
-    if (queryStart < fragmentStart) {
-      query = _makeQuery(uri, queryStart + 1, fragmentStart, null);
-    }
-    String fragment;
-    if (fragmentStart < end) {
-      fragment = _makeFragment(uri, fragmentStart + 1, end);
-    }
-    return new _Uri._internal(scheme,
-                              userInfo,
-                              host,
-                              port,
-                              path,
-                              query,
-                              fragment);
-  }
-
-  /// Implementation of [Uri.Uri].
-  factory _Uri({String scheme,
-                String userInfo,
-                String host,
-                int port,
-                String path,
-                Iterable<String> pathSegments,
-                String query,
-                Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
-                String fragment}) {
-    scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
-    userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
-    host = _makeHost(host, 0, _stringOrNullLength(host), false);
-    // Special case this constructor for backwards compatibility.
-    if (query == "") query = null;
-    query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
-    fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
-    port = _makePort(port, scheme);
-    bool isFile = (scheme == "file");
-    if (host == null &&
-        (userInfo.isNotEmpty || port != null || isFile)) {
-      host = "";
-    }
-    bool hasAuthority = (host != null);
-    path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
-                     scheme, hasAuthority);
-    if (scheme.isEmpty && host == null && !path.startsWith('/')) {
-      path = _normalizeRelativePath(path);
-    } else {
-      path = _removeDotSegments(path);
-    }
-    return new _Uri._internal(scheme, userInfo, host, port,
-                              path, query, fragment);
-  }
-
-  /// Implementation of [Uri.http].
-  factory _Uri.http(String authority,
-                    String unencodedPath,
-                    [Map<String, String> queryParameters]) {
-    return _makeHttpUri("http", authority, unencodedPath, queryParameters);
-  }
-
-  /// Implementation of [Uri.https].
-  factory _Uri.https(String authority,
-                     String unencodedPath,
-                     [Map<String, String> queryParameters]) {
-    return _makeHttpUri("https", authority, unencodedPath, queryParameters);
-  }
-
-  String get authority {
-    if (!hasAuthority) return "";
-    var sb = new StringBuffer();
-    _writeAuthority(sb);
-    return sb.toString();
-  }
-
-  String get userInfo => _userInfo;
-
-  String get host {
-    if (_host == null) return "";
-    if (_host.startsWith('[')) {
-      return _host.substring(1, _host.length - 1);
-    }
-    return _host;
-  }
-
-  int get port {
-    if (_port == null) return _defaultPort(scheme);
-    return _port;
-  }
-
-  // The default port for the scheme of this Uri.
-  static int _defaultPort(String scheme) {
-    if (scheme == "http") return 80;
-    if (scheme == "https") return 443;
-    return 0;
-  }
-
-  String get path => _path;
-
-  String get query => _query ?? "";
-
-  String get fragment => _fragment ?? "";
-
-  // Report a parse failure.
-  static void _fail(String uri, int index, String message) {
-    throw new FormatException(message, uri, index);
-  }
-
-  static Uri _makeHttpUri(String scheme,
-                          String authority,
-                          String unencodedPath,
-                          Map<String, String> queryParameters) {
-    var userInfo = "";
-    var host = null;
-    var port = null;
-
-    if (authority != null && authority.isNotEmpty) {
-      var hostStart = 0;
-      // Split off the user info.
-      bool hasUserInfo = false;
-      for (int i = 0; i < authority.length; i++) {
-        const int atSign = 0x40;
-        if (authority.codeUnitAt(i) == atSign) {
-          hasUserInfo = true;
-          userInfo = authority.substring(0, i);
-          hostStart = i + 1;
-          break;
-        }
-      }
-      var hostEnd = hostStart;
-      if (hostStart < authority.length &&
-          authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
-        // IPv6 host.
-        for (; hostEnd < authority.length; hostEnd++) {
-          if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
-        }
-        if (hostEnd == authority.length) {
-          throw new FormatException("Invalid IPv6 host entry.",
-                                    authority, hostStart);
-        }
-        Uri.parseIPv6Address(authority, hostStart + 1, hostEnd);
-        hostEnd++;  // Skip the closing bracket.
-        if (hostEnd != authority.length &&
-            authority.codeUnitAt(hostEnd) != _COLON) {
-          throw new FormatException("Invalid end of authority",
-                                    authority, hostEnd);
-        }
-      }
-      // Split host and port.
-      bool hasPort = false;
-      for (; hostEnd < authority.length; hostEnd++) {
-        if (authority.codeUnitAt(hostEnd) == _COLON) {
-          var portString = authority.substring(hostEnd + 1);
-          // We allow the empty port - falling back to initial value.
-          if (portString.isNotEmpty) port = int.parse(portString);
-          break;
-        }
-      }
-      host = authority.substring(hostStart, hostEnd);
-    }
-    return new Uri(scheme: scheme,
-                   userInfo: userInfo,
-                   host: host,
-                   port: port,
-                   pathSegments: unencodedPath.split("/"),
-                   queryParameters: queryParameters);
-  }
-
-  /// Implementation of [Uri.file].
-  factory _Uri.file(String path, {bool windows}) {
-    windows = (windows == null) ? _Uri._isWindows : windows;
-    return windows ? _makeWindowsFileUrl(path, false)
-                   : _makeFileUri(path, false);
-  }
-
-  /// Implementation of [Uri.directory].
-  factory _Uri.directory(String path, {bool windows}) {
-    windows = (windows == null) ? _Uri._isWindows : windows;
-    return windows ? _makeWindowsFileUrl(path, true)
-                   : _makeFileUri(path, true);
-  }
-
+  external static Uri get base;
 
-  /// Used internally in path-related constructors.
   external static bool get _isWindows;
 
   static _checkNonWindowsPathReservedCharacters(List<String> segments,
@@ -1740,6 +969,46 @@
     }
   }
 
+  /**
+   * Returns a new `Uri` based on this one, but with some parts replaced.
+   *
+   * This method takes the same parameters as the [new Uri] constructor,
+   * and they have the same meaning.
+   *
+   * At most one of [path] and [pathSegments] must be provided.
+   * Likewise, at most one of [query] and [queryParameters] must be provided.
+   *
+   * Each part that is not provided will default to the corresponding
+   * value from this `Uri` instead.
+   *
+   * This method is different from [Uri.resolve] which overrides in a
+   * hierarchial manner,
+   * and can instead replace each part of a `Uri` individually.
+   *
+   * Example:
+   *
+   *     Uri uri1 = Uri.parse("a://b@c:4/d/e?f#g");
+   *     Uri uri2 = uri1.replace(scheme: "A", path: "D/E/E", fragment: "G");
+   *     print(uri2);  // prints "A://b@c:4/D/E/E/?f#G"
+   *
+   * This method acts similarly to using the `new Uri` constructor with
+   * some of the arguments taken from this `Uri` . Example:
+   *
+   *     Uri uri3 = new Uri(
+   *         scheme: "A",
+   *         userInfo: uri1.userInfo,
+   *         host: uri1.host,
+   *         port: uri1.port,
+   *         path: "D/E/E",
+   *         query: uri1.query,
+   *         fragment: "G");
+   *     print(uri3);  // prints "A://b@c:4/D/E/E/?f#G"
+   *     print(uri2 == uri3);  // prints true.
+   *
+   * Using this method can be seen as a shorthand for the `Uri` constructor
+   * call above, but may also be slightly faster because the parts taken
+   * from this `Uri` need not be checked for validity again.
+   */
   Uri replace({String scheme,
                String userInfo,
                String host,
@@ -1806,16 +1075,29 @@
       fragment = this._fragment;
     }
 
-    return new _Uri._internal(
+    return new Uri._internal(
         scheme, userInfo, host, port, path, query, fragment);
   }
 
+  /**
+   * Returns a `Uri` that differs from this only in not having a fragment.
+   *
+   * If this `Uri` does not have a fragment, it is itself returned.
+   */
   Uri removeFragment() {
     if (!this.hasFragment) return this;
-    return new _Uri._internal(scheme, _userInfo, _host, _port,
+    return new Uri._internal(scheme, _userInfo, _host, _port,
                              _path, _query, null);
   }
 
+  /**
+   * Returns the URI path split into its segments. Each of the segments in the
+   * returned list have been decoded. If the path is empty the empty list will
+   * be returned. A leading slash `/` does not affect the segments returned.
+   *
+   * The returned list is unmodifiable and will throw [UnsupportedError] on any
+   * calls that would mutate it.
+   */
   List<String> get pathSegments {
     var result = _pathSegments;
     if (result != null) return result;
@@ -1832,14 +1114,43 @@
     return result;
   }
 
+  /**
+   * Returns the URI query split into a map according to the rules
+   * specified for FORM post in the [HTML 4.01 specification section
+   * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
+   * Each key and value in the returned map has been decoded.
+   * If there is no query the empty map is returned.
+   *
+   * Keys in the query string that have no value are mapped to the
+   * empty string.
+   * If a key occurs more than once in the query string, it is mapped to
+   * an arbitrary choice of possible value.
+   * The [queryParametersAll] getter can provide a map
+   * that maps keys to all of their values.
+   *
+   * The returned map is unmodifiable.
+   */
   Map<String, String> get queryParameters {
     if (_queryParameters == null) {
       _queryParameters =
-          new UnmodifiableMapView<String, String>(Uri.splitQueryString(query));
+          new UnmodifiableMapView<String, String>(splitQueryString(query));
     }
     return _queryParameters;
   }
 
+  /**
+   * Returns the URI query split into a map according to the rules
+   * specified for FORM post in the [HTML 4.01 specification section
+   * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
+   * Each key and value in the returned map has been decoded. If there is no
+   * query the empty map is returned.
+   *
+   * Keys are mapped to lists of their values. If a key occurs only once,
+   * its value is a singleton list. If a key occurs with no value, the
+   * empty string is used as the value for that occurrence.
+   *
+   * The returned map and the lists it contains are unmodifiable.
+   */
   Map<String, List<String>> get queryParametersAll {
     if (_queryParameterLists == null) {
       Map queryParameterLists = _splitQueryStringAll(query);
@@ -1853,6 +1164,22 @@
     return _queryParameterLists;
   }
 
+  /**
+   * Returns a URI where the path has been normalized.
+   *
+   * A normalized path does not contain `.` segments or non-leading `..`
+   * segments.
+   * Only a relative path with no scheme or authority may contain
+   * leading `..` segments,
+   * a path that starts with `/` will also drop any leading `..` segments.
+   *
+   * This uses the same normalization strategy as `new Uri().resolve(this)`.
+   *
+   * Does not change any part of the URI except the path.
+   *
+   * The default implementation of `Uri` always normalizes paths, so calling
+   * this function has no effect.
+   */
   Uri normalizePath() {
     String path = _normalizePath(_path, scheme, hasAuthority);
     if (identical(path, _path)) return this;
@@ -1885,7 +1212,7 @@
       if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
         _fail(host, start, 'Missing end `]` to match `[` in host');
       }
-      Uri.parseIPv6Address(host, start + 1, end - 1);
+      parseIPv6Address(host, start + 1, end - 1);
       // RFC 5952 requires hex digits to be lower case.
       return host.substring(start, end).toLowerCase();
     }
@@ -1893,7 +1220,7 @@
       // TODO(lrn): skip if too short to be a valid IPv6 address?
       for (int i = start; i < end; i++) {
         if (host.codeUnitAt(i) == _COLON) {
-          Uri.parseIPv6Address(host, start, end);
+          parseIPv6Address(host, start, end);
           return '[$host]';
         }
       }
@@ -2006,17 +1333,6 @@
     }
     scheme = scheme.substring(start, end);
     if (containsUpperCase) scheme = scheme.toLowerCase();
-    return _canonicalizeScheme(scheme);
-  }
-
-  // Canonicalize a few often-used scheme strings.
-  //
-  // This improves memory usage and makes comparison faster.
-  static String _canonicalizeScheme(String scheme) {
-    if (scheme == "http") return "http";
-    if (scheme == "file") return "file";
-    if (scheme == "https") return "https";
-    if (scheme == "package") return "package";
     return scheme;
   }
 
@@ -2103,6 +1419,8 @@
     return _normalize(fragment, start, end, _queryCharTable);
   }
 
+  static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
+
   /**
    * Performs RFC 3986 Percent-Encoding Normalization.
    *
@@ -2147,11 +1465,10 @@
   // Converts a UTF-16 code-unit to its value as a hex digit.
   // Returns -1 for non-hex digits.
   static int _parseHexDigit(int char) {
-    const int zeroDigit = 0x30;
-    int digit = char ^ zeroDigit;
+    int digit = char ^ Uri._ZERO;
     if (digit <= 9) return digit;
     int lowerCase = char | 0x20;
-    if (_LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
+    if (Uri._LOWER_CASE_A <= lowerCase && lowerCase <= _LOWER_CASE_F) {
       return lowerCase - (_LOWER_CASE_A - 10);
     }
     return -1;
@@ -2237,7 +1554,7 @@
             if (index + 1 < end) {
               int tail = component.codeUnitAt(index + 1);
               if ((tail & 0xFC00) == 0xDC00) {
-                // Tail surrogate.
+                // Tail surrogat.
                 sourceLength = 2;
                 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
               }
@@ -2386,10 +1703,34 @@
     return output.join("/");
   }
 
+  /**
+   * Resolve [reference] as an URI relative to `this`.
+   *
+   * First turn [reference] into a URI using [Uri.parse]. Then resolve the
+   * resulting URI relative to `this`.
+   *
+   * Returns the resolved URI.
+   *
+   * See [resolveUri] for details.
+   */
   Uri resolve(String reference) {
     return resolveUri(Uri.parse(reference));
   }
 
+  /**
+   * Resolve [reference] as an URI relative to `this`.
+   *
+   * Returns the resolved URI.
+   *
+   * The algorithm "Transform Reference" for resolving a reference is described
+   * in [RFC-3986 Section 5](http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
+   *
+   * Updated to handle the case where the base URI is just a relative path -
+   * that is: when it has no scheme or authority and the path does not start
+   * with a slash.
+   * In that case, the paths are combined without removing leading "..", and
+   * an empty path is not converted to "/".
+   */
   Uri resolveUri(Uri reference) {
     // From RFC 3986.
     String targetScheme;
@@ -2435,17 +1776,11 @@
           } else {
             // This is the RFC 3986 behavior for merging.
             if (this.hasEmptyPath) {
-              if (!this.hasAuthority) {
-                if (!this.hasScheme) {
-                  // Keep the path relative if no scheme or authority.
-                  targetPath = reference.path;
-                } else {
-                  // Remove leading dot-segments if the path is put
-                  // beneath a scheme.
-                  targetPath = _removeDotSegments(reference.path);
-                }
+              if (!this.hasScheme && !this.hasAuthority) {
+                // Keep the path relative if no scheme or authority.
+                targetPath = reference.path;
               } else {
-                // RFC algorithm for base with authority and empty path.
+                // Add path normalization on top of RFC algorithm.
                 targetPath = _removeDotSegments("/" + reference.path);
               }
             } else {
@@ -2453,9 +1788,8 @@
               if (this.hasScheme || this.hasAuthority || this.hasAbsolutePath) {
                 targetPath = _removeDotSegments(mergedPath);
               } else {
-                // Non-RFC 3986 behavior.
-                // If both base and reference are relative paths,
-                // allow the merged path to start with "..".
+                // Non-RFC 3986 beavior. If both base and reference are relative
+                // path, allow the merged path to start with "..".
                 // The RFC only specifies the case where the base has a scheme.
                 targetPath = _normalizeRelativePath(mergedPath);
               }
@@ -2466,29 +1800,63 @@
       }
     }
     String fragment = reference.hasFragment ? reference.fragment : null;
-    return new _Uri._internal(targetScheme,
-                              targetUserInfo,
-                              targetHost,
-                              targetPort,
-                              targetPath,
-                              targetQuery,
-                              fragment);
+    return new Uri._internal(targetScheme,
+                             targetUserInfo,
+                             targetHost,
+                             targetPort,
+                             targetPath,
+                             targetQuery,
+                             fragment);
   }
 
+  /**
+   * Returns whether the URI has a [scheme] component.
+   */
   bool get hasScheme => scheme.isNotEmpty;
 
+  /**
+   * Returns whether the URI has an [authority] component.
+   */
   bool get hasAuthority => _host != null;
 
+  /**
+   * Returns whether the URI has an explicit port.
+   *
+   * If the port number is the default port number
+   * (zero for unrecognized schemes, with http (80) and https (443) being
+   * recognized),
+   * then the port is made implicit and omitted from the URI.
+   */
   bool get hasPort => _port != null;
 
+  /**
+   * Returns whether the URI has a query part.
+   */
   bool get hasQuery => _query != null;
 
+  /**
+   * Returns whether the URI has a fragment part.
+   */
   bool get hasFragment => _fragment != null;
 
+  /**
+   * Returns whether the URI has an empty path.
+   */
   bool get hasEmptyPath => _path.isEmpty;
 
+  /**
+   * Returns whether the URI has an absolute path (starting with '/').
+   */
   bool get hasAbsolutePath => _path.startsWith('/');
 
+  /**
+   * Returns the origin of the URI in the form scheme://host:port for the
+   * schemes http and https.
+   *
+   * It is an error if the scheme is not "http" or "https".
+   *
+   * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
+   */
   String get origin {
     if (scheme == "" || _host == null || _host == "") {
       throw new StateError("Cannot use origin without a scheme: $this");
@@ -2501,6 +1869,69 @@
     return "$scheme://$_host:$_port";
   }
 
+  /**
+   * Returns the file path from a file URI.
+   *
+   * The returned path has either Windows or non-Windows
+   * semantics.
+   *
+   * For non-Windows semantics the slash ("/") is used to separate
+   * path segments.
+   *
+   * For Windows semantics the backslash ("\") separator is used to
+   * separate path segments.
+   *
+   * If the URI is absolute the path starts with a path separator
+   * unless Windows semantics is used and the first path segment is a
+   * drive letter. When Windows semantics is used a host component in
+   * the uri in interpreted as a file server and a UNC path is
+   * returned.
+   *
+   * The default for whether to use Windows or non-Windows semantics
+   * determined from the platform Dart is running on. When running in
+   * the standalone VM this is detected by the VM based on the
+   * operating system. When running in a browser non-Windows semantics
+   * is always used.
+   *
+   * To override the automatic detection of which semantics to use pass
+   * a value for [windows]. Passing `true` will use Windows
+   * semantics and passing `false` will use non-Windows semantics.
+   *
+   * If the URI ends with a slash (i.e. the last path component is
+   * empty) the returned file path will also end with a slash.
+   *
+   * With Windows semantics URIs starting with a drive letter cannot
+   * be relative to the current drive on the designated drive. That is
+   * for the URI `file:///c:abc` calling `toFilePath` will throw as a
+   * path segment cannot contain colon on Windows.
+   *
+   * Examples using non-Windows semantics (resulting of calling
+   * toFilePath in comment):
+   *
+   *     Uri.parse("xxx/yyy");  // xxx/yyy
+   *     Uri.parse("xxx/yyy/");  // xxx/yyy/
+   *     Uri.parse("file:///xxx/yyy");  // /xxx/yyy
+   *     Uri.parse("file:///xxx/yyy/");  // /xxx/yyy/
+   *     Uri.parse("file:///C:");  // /C:
+   *     Uri.parse("file:///C:a");  // /C:a
+   *
+   * Examples using Windows semantics (resulting URI in comment):
+   *
+   *     Uri.parse("xxx/yyy");  // xxx\yyy
+   *     Uri.parse("xxx/yyy/");  // xxx\yyy\
+   *     Uri.parse("file:///xxx/yyy");  // \xxx\yyy
+   *     Uri.parse("file:///xxx/yyy/");  // \xxx\yyy/
+   *     Uri.parse("file:///C:/xxx/yyy");  // C:\xxx\yyy
+   *     Uri.parse("file:C:xxx/yyy");  // Throws as a path segment
+   *                                   // cannot contain colon on Windows.
+   *     Uri.parse("file://server/share/file");  // \\server\share\file
+   *
+   * If the URI is not a file URI calling this throws
+   * [UnsupportedError].
+   *
+   * If the URI cannot be converted to a file path calling this throws
+   * [UnsupportedError].
+   */
   String toFilePath({bool windows}) {
     if (scheme != "" && scheme != "file") {
       throw new UnsupportedError(
@@ -2515,27 +1946,25 @@
           "Cannot extract a file path from a URI with a fragment component");
     }
     if (windows == null) windows = _isWindows;
-    return windows ? _toWindowsFilePath(this) : _toFilePath();
+    return windows ? _toWindowsFilePath() : _toFilePath();
   }
 
   String _toFilePath() {
-    if (hasAuthority && host != "") {
+    if (host != "") {
       throw new UnsupportedError(
           "Cannot extract a non-Windows file path from a file URI "
           "with an authority");
     }
-    // Use path segments to have any escapes unescaped.
-    var pathSegments = this.pathSegments;
     _checkNonWindowsPathReservedCharacters(pathSegments, false);
     var result = new StringBuffer();
-    if (hasAbsolutePath) result.write("/");
+    if (_isPathAbsolute) result.write("/");
     result.writeAll(pathSegments, "/");
     return result.toString();
   }
 
-  static String _toWindowsFilePath(Uri uri) {
+  String _toWindowsFilePath() {
     bool hasDriveLetter = false;
-    var segments = uri.pathSegments;
+    var segments = pathSegments;
     if (segments.length > 0 &&
         segments[0].length == 2 &&
         segments[0].codeUnitAt(1) == _COLON) {
@@ -2543,25 +1972,23 @@
       _checkWindowsPathReservedCharacters(segments, false, 1);
       hasDriveLetter = true;
     } else {
-      _checkWindowsPathReservedCharacters(segments, false, 0);
+      _checkWindowsPathReservedCharacters(segments, false);
     }
     var result = new StringBuffer();
-    if (uri.hasAbsolutePath && !hasDriveLetter) result.write(r"\");
-    if (uri.hasAuthority) {
-      var host = uri.host;
-      if (host.isNotEmpty) {
-        result.write(r"\");
-        result.write(host);
-        result.write(r"\");
-      }
+    if (_isPathAbsolute && !hasDriveLetter) result.write("\\");
+    if (host != "") {
+      result.write("\\");
+      result.write(host);
+      result.write("\\");
     }
-    result.writeAll(segments, r"\");
-    if (hasDriveLetter && segments.length == 1) result.write(r"\");
+    result.writeAll(segments, "\\");
+    if (hasDriveLetter && segments.length == 1) result.write("\\");
     return result.toString();
   }
 
   bool get _isPathAbsolute {
-    return _path != null && _path.startsWith('/');
+    if (path == null || path.isEmpty) return false;
+    return path.startsWith('/');
   }
 
   void _writeAuthority(StringSink ss) {
@@ -2587,13 +2014,8 @@
   UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
 
   String toString() {
-    return _text ??= _initializeText();
-  }
-
-  String _initializeText() {
-    assert(_text == null);
     StringBuffer sb = new StringBuffer();
-    if (scheme.isNotEmpty) sb..write(scheme)..write(":");
+    _addIfNonEmpty(sb, scheme, scheme, ':');
     if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
       // File URIS always have the authority, even if it is empty.
       // The empty URI means "localhost".
@@ -2601,31 +2023,192 @@
       _writeAuthority(sb);
     }
     sb.write(path);
-    if (_query != null) sb..write("?")..write(_query);
-    if (_fragment != null) sb..write("#")..write(_fragment);
+    if (_query != null) { sb..write("?")..write(_query); }
+    if (_fragment != null) { sb..write("#")..write(_fragment); }
     return sb.toString();
   }
 
   bool operator==(other) {
-    if (identical(this, other)) return true;
-    if (other is Uri) {
-      Uri uri = other;
-      return scheme       == uri.scheme       &&
-             hasAuthority == uri.hasAuthority &&
-             userInfo     == uri.userInfo     &&
-             host         == uri.host         &&
-             port         == uri.port         &&
-             path         == uri.path         &&
-             hasQuery     == uri.hasQuery     &&
-             query        == uri.query        &&
-             hasFragment  == uri.hasFragment  &&
-             fragment     == uri.fragment;
-    }
-    return false;
+    if (other is! Uri) return false;
+    Uri uri = other;
+    return scheme       == uri.scheme       &&
+           hasAuthority == uri.hasAuthority &&
+           userInfo     == uri.userInfo     &&
+           host         == uri.host         &&
+           port         == uri.port         &&
+           path         == uri.path         &&
+           hasQuery     == uri.hasQuery     &&
+           query        == uri.query        &&
+           hasFragment  == uri.hasFragment  &&
+           fragment     == uri.fragment;
   }
 
   int get hashCode {
-    return _hashCodeCache ??= toString().hashCode;
+    int combine(part, current) {
+      // The sum is truncated to 30 bits to make sure it fits into a Smi.
+      return (current * 31 + part.hashCode) & 0x3FFFFFFF;
+    }
+    return combine(scheme, combine(userInfo, combine(host, combine(port,
+        combine(path, combine(query, combine(fragment, 1)))))));
+  }
+
+  static void _addIfNonEmpty(StringBuffer sb, String test,
+                             String first, String second) {
+    if ("" != test) {
+      sb.write(first);
+      sb.write(second);
+    }
+  }
+
+  /**
+   * Encode the string [component] using percent-encoding to make it
+   * safe for literal use as a URI component.
+   *
+   * All characters except uppercase and lowercase letters, digits and
+   * the characters `-_.!~*'()` are percent-encoded. This is the
+   * set of characters specified in RFC 2396 and the which is
+   * specified for the encodeUriComponent in ECMA-262 version 5.1.
+   *
+   * When manually encoding path segments or query components remember
+   * to encode each part separately before building the path or query
+   * string.
+   *
+   * For encoding the query part consider using
+   * [encodeQueryComponent].
+   *
+   * To avoid the need for explicitly encoding use the [pathSegments]
+   * and [queryParameters] optional named arguments when constructing
+   * a [Uri].
+   */
+  static String encodeComponent(String component) {
+    return _uriEncode(_unreserved2396Table, component, UTF8, false);
+  }
+
+  /**
+   * Encode the string [component] according to the HTML 4.01 rules
+   * for encoding the posting of a HTML form as a query string
+   * component.
+   *
+   * Encode the string [component] according to the HTML 4.01 rules
+   * for encoding the posting of a HTML form as a query string
+   * component.
+
+   * The component is first encoded to bytes using [encoding].
+   * The default is to use [UTF8] encoding, which preserves all
+   * the characters that don't need encoding.
+
+   * Then the resulting bytes are "percent-encoded". This transforms
+   * spaces (U+0020) to a plus sign ('+') and all bytes that are not
+   * the ASCII decimal digits, letters or one of '-._~' are written as
+   * a percent sign '%' followed by the two-digit hexadecimal
+   * representation of the byte.
+
+   * Note that the set of characters which are percent-encoded is a
+   * superset of what HTML 4.01 requires, since it refers to RFC 1738
+   * for reserved characters.
+   *
+   * When manually encoding query components remember to encode each
+   * part separately before building the query string.
+   *
+   * To avoid the need for explicitly encoding the query use the
+   * [queryParameters] optional named arguments when constructing a
+   * [Uri].
+   *
+   * See http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 for more
+   * details.
+   */
+  static String encodeQueryComponent(String component,
+                                     {Encoding encoding: UTF8}) {
+    return _uriEncode(_unreservedTable, component, encoding, true);
+  }
+
+  /**
+   * Decodes the percent-encoding in [encodedComponent].
+   *
+   * Note that decoding a URI component might change its meaning as
+   * some of the decoded characters could be characters with are
+   * delimiters for a given URI componene type. Always split a URI
+   * component using the delimiters for the component before decoding
+   * the individual parts.
+   *
+   * For handling the [path] and [query] components consider using
+   * [pathSegments] and [queryParameters] to get the separated and
+   * decoded component.
+   */
+  static String decodeComponent(String encodedComponent) {
+    return _uriDecode(encodedComponent, 0, encodedComponent.length,
+                      UTF8, false);
+  }
+
+  /**
+   * Decodes the percent-encoding in [encodedComponent], converting
+   * pluses to spaces.
+   *
+   * It will create a byte-list of the decoded characters, and then use
+   * [encoding] to decode the byte-list to a String. The default encoding is
+   * UTF-8.
+   */
+  static String decodeQueryComponent(
+      String encodedComponent,
+      {Encoding encoding: UTF8}) {
+    return _uriDecode(encodedComponent, 0, encodedComponent.length,
+                      encoding, true);
+  }
+
+  /**
+   * Encode the string [uri] using percent-encoding to make it
+   * safe for literal use as a full URI.
+   *
+   * All characters except uppercase and lowercase letters, digits and
+   * the characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This
+   * is the set of characters specified in in ECMA-262 version 5.1 for
+   * the encodeURI function .
+   */
+  static String encodeFull(String uri) {
+    return _uriEncode(_encodeFullTable, uri, UTF8, false);
+  }
+
+  /**
+   * Decodes the percent-encoding in [uri].
+   *
+   * Note that decoding a full URI might change its meaning as some of
+   * the decoded characters could be reserved characters. In most
+   * cases an encoded URI should be parsed into components using
+   * [Uri.parse] before decoding the separate components.
+   */
+  static String decodeFull(String uri) {
+    return _uriDecode(uri, 0, uri.length, UTF8, false);
+  }
+
+  /**
+   * Returns the [query] split into a map according to the rules
+   * specified for FORM post in the [HTML 4.01 specification section
+   * 17.13.4](http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4 "HTML 4.01 section 17.13.4").
+   * Each key and value in the returned map has been decoded. If the [query]
+   * is the empty string an empty map is returned.
+   *
+   * Keys in the query string that have no value are mapped to the
+   * empty string.
+   *
+   * Each query component will be decoded using [encoding]. The default encoding
+   * is UTF-8.
+   */
+  static Map<String, String> splitQueryString(String query,
+                                              {Encoding encoding: UTF8}) {
+    return query.split("&").fold({}, (map, element) {
+      int index = element.indexOf("=");
+      if (index == -1) {
+        if (element != "") {
+          map[decodeQueryComponent(element, encoding: encoding)] = "";
+        }
+      } else if (index != 0) {
+        var key = element.substring(0, index);
+        var value = element.substring(index + 1);
+        map[Uri.decodeQueryComponent(key, encoding: encoding)] =
+            decodeQueryComponent(value, encoding: encoding);
+      }
+      return map;
+    });
   }
 
   static List _createList() => [];
@@ -2668,6 +2251,175 @@
     return result;
   }
 
+  /**
+   * Parse the [host] as an IP version 4 (IPv4) address, returning the address
+   * as a list of 4 bytes in network byte order (big endian).
+   *
+   * Throws a [FormatException] if [host] is not a valid IPv4 address
+   * representation.
+   */
+  static List<int> parseIPv4Address(String host) {
+    void error(String msg) {
+      throw new FormatException('Illegal IPv4 address, $msg');
+    }
+    var bytes = host.split('.');
+    if (bytes.length != 4) {
+      error('IPv4 address should contain exactly 4 parts');
+    }
+    // TODO(ajohnsen): Consider using Uint8List.
+    return bytes
+        .map((byteString) {
+          int byte = int.parse(byteString);
+          if (byte < 0 || byte > 255) {
+            error('each part must be in the range of `0..255`');
+          }
+          return byte;
+        })
+        .toList();
+  }
+
+  /**
+   * Parse the [host] as an IP version 6 (IPv6) address, returning the address
+   * as a list of 16 bytes in network byte order (big endian).
+   *
+   * Throws a [FormatException] if [host] is not a valid IPv6 address
+   * representation.
+   *
+   * Acts on the substring from [start] to [end]. If [end] is omitted, it
+   * defaults ot the end of the string.
+   *
+   * Some examples of IPv6 addresses:
+   *  * ::1
+   *  * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
+   *  * 3ffe:2a00:100:7031::1
+   *  * ::FFFF:129.144.52.38
+   *  * 2010:836B:4179::836B:4179
+   */
+  static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
+    if (end == null) end = host.length;
+    // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
+    // by `:`'s, with the following exceptions:
+    //
+    //  - One (and only one) wildcard (`::`) may be present, representing a fill
+    //    of 0's. The IPv6 `::` is thus 16 bytes of `0`.
+    //  - The last two parts may be replaced by an IPv4 address.
+    void error(String msg, [position]) {
+      throw new FormatException('Illegal IPv6 address, $msg', host, position);
+    }
+    int parseHex(int start, int end) {
+      if (end - start > 4) {
+        error('an IPv6 part can only contain a maximum of 4 hex digits', start);
+      }
+      int value = int.parse(host.substring(start, end), radix: 16);
+      if (value < 0 || value > (1 << 16) - 1) {
+        error('each part must be in the range of `0x0..0xFFFF`', start);
+      }
+      return value;
+    }
+    if (host.length < 2) error('address is too short');
+    List<int> parts = [];
+    bool wildcardSeen = false;
+    int partStart = start;
+    // Parse all parts, except a potential last one.
+    for (int i = start; i < end; i++) {
+      if (host.codeUnitAt(i) == _COLON) {
+        if (i == start) {
+          // If we see a `:` in the beginning, expect wildcard.
+          i++;
+          if (host.codeUnitAt(i) != _COLON) {
+            error('invalid start colon.', i);
+          }
+          partStart = i;
+        }
+        if (i == partStart) {
+          // Wildcard. We only allow one.
+          if (wildcardSeen) {
+            error('only one wildcard `::` is allowed', i);
+          }
+          wildcardSeen = true;
+          parts.add(-1);
+        } else {
+          // Found a single colon. Parse [partStart..i] as a hex entry.
+          parts.add(parseHex(partStart, i));
+        }
+        partStart = i + 1;
+      }
+    }
+    if (parts.length == 0) error('too few parts');
+    bool atEnd = (partStart == end);
+    bool isLastWildcard = (parts.last == -1);
+    if (atEnd && !isLastWildcard) {
+      error('expected a part after last `:`', end);
+    }
+    if (!atEnd) {
+      try {
+        parts.add(parseHex(partStart, end));
+      } catch (e) {
+        // Failed to parse the last chunk as hex. Try IPv4.
+        try {
+          List<int> last = parseIPv4Address(host.substring(partStart, end));
+          parts.add(last[0] << 8 | last[1]);
+          parts.add(last[2] << 8 | last[3]);
+        } catch (e) {
+          error('invalid end of IPv6 address.', partStart);
+        }
+      }
+    }
+    if (wildcardSeen) {
+      if (parts.length > 7) {
+        error('an address with a wildcard must have less than 7 parts');
+      }
+    } else if (parts.length != 8) {
+      error('an address without a wildcard must contain exactly 8 parts');
+    }
+    List<int> bytes = new Uint8List(16);
+    for (int i = 0, index = 0; i < parts.length; i++) {
+      int value = parts[i];
+      if (value == -1) {
+        int wildCardLength = 9 - parts.length;
+        for (int j = 0; j < wildCardLength; j++) {
+          bytes[index] = 0;
+          bytes[index + 1] = 0;
+          index += 2;
+        }
+      } else {
+        bytes[index] = value >> 8;
+        bytes[index + 1] = value & 0xff;
+        index += 2;
+      }
+    }
+    return bytes;
+  }
+
+  // Frequently used character codes.
+  static const int _SPACE = 0x20;
+  static const int _DOUBLE_QUOTE = 0x22;
+  static const int _NUMBER_SIGN = 0x23;
+  static const int _PERCENT = 0x25;
+  static const int _ASTERISK = 0x2A;
+  static const int _PLUS = 0x2B;
+  static const int _DOT = 0x2E;
+  static const int _SLASH = 0x2F;
+  static const int _ZERO = 0x30;
+  static const int _NINE = 0x39;
+  static const int _COLON = 0x3A;
+  static const int _LESS = 0x3C;
+  static const int _GREATER = 0x3E;
+  static const int _QUESTION = 0x3F;
+  static const int _AT_SIGN = 0x40;
+  static const int _UPPER_CASE_A = 0x41;
+  static const int _UPPER_CASE_F = 0x46;
+  static const int _UPPER_CASE_Z = 0x5A;
+  static const int _LEFT_BRACKET = 0x5B;
+  static const int _BACKSLASH = 0x5C;
+  static const int _RIGHT_BRACKET = 0x5D;
+  static const int _LOWER_CASE_A = 0x61;
+  static const int _LOWER_CASE_F = 0x66;
+  static const int _LOWER_CASE_Z = 0x7A;
+  static const int _BAR = 0x7C;
+
+  static const String _hexDigits = "0123456789ABCDEF";
+
   external static String _uriEncode(List<int> canonicalTable,
                                     String text,
                                     Encoding encoding,
@@ -3189,13 +2941,13 @@
         throw new ArgumentError.value(mimeType, "mimeType",
                                       "Invalid MIME type");
       }
-      buffer.write(_Uri._uriEncode(_tokenCharTable,
-                                   mimeType.substring(0, slashIndex),
-                                   UTF8, false));
+      buffer.write(Uri._uriEncode(_tokenCharTable,
+                                  mimeType.substring(0, slashIndex),
+                                  UTF8, false));
       buffer.write("/");
-      buffer.write(_Uri._uriEncode(_tokenCharTable,
-                                   mimeType.substring(slashIndex + 1),
-                                   UTF8, false));
+      buffer.write(Uri._uriEncode(_tokenCharTable,
+                                  mimeType.substring(slashIndex + 1),
+                                  UTF8, false));
     }
     if (charsetName != null) {
       if (indices != null) {
@@ -3203,7 +2955,7 @@
                ..add(buffer.length + 8);
       }
       buffer.write(";charset=");
-      buffer.write(_Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
+      buffer.write(Uri._uriEncode(_tokenCharTable, charsetName, UTF8, false));
     }
     parameters?.forEach((var key, var value) {
       if (key.isEmpty) {
@@ -3216,10 +2968,10 @@
       if (indices != null) indices.add(buffer.length);
       buffer.write(';');
       // Encode any non-RFC2045-token character and both '%' and '#'.
-      buffer.write(_Uri._uriEncode(_tokenCharTable, key, UTF8, false));
+      buffer.write(Uri._uriEncode(_tokenCharTable, key, UTF8, false));
       if (indices != null) indices.add(buffer.length);
       buffer.write('=');
-      buffer.write(_Uri._uriEncode(_tokenCharTable, value, UTF8, false));
+      buffer.write(Uri._uriEncode(_tokenCharTable, value, UTF8, false));
     });
   }
 
@@ -3236,7 +2988,7 @@
     int slashIndex = -1;
     for (int i = 0; i < mimeType.length; i++) {
       var char = mimeType.codeUnitAt(i);
-      if (char != _SLASH) continue;
+      if (char != Uri._SLASH) continue;
       if (slashIndex < 0) {
         slashIndex = i;
         continue;
@@ -3256,7 +3008,7 @@
    * ````
    *
    * where `type`, `subtype`, `attribute` and `value` are specified in RFC-2045,
-   * and `data` is a sequence of URI-characters (RFC-2396 `uric`).
+   * and `data` is a sequnce of URI-characters (RFC-2396 `uric`).
    *
    * This means that all the characters must be ASCII, but the URI may contain
    * percent-escapes for non-ASCII byte values that need an interpretation
@@ -3267,22 +3019,13 @@
    * and `,` delimiters.
    *
    * Accessing the individual parts may fail later if they turn out to have
-   * content that can't be decoded successfully as a string.
+   * content that can't be decoded sucessfully as a string.
    */
   static UriData parse(String uri) {
-    if (uri.length >= 5) {
-      int dataDelta = _startsWithData(uri, 0);
-      if (dataDelta == 0) {
-        // Exact match on "data:".
-        return _parse(uri, 5, null);
-      }
-      if (dataDelta == 0x20) {
-        // Starts with a non-normalized "data" scheme containing upper-case
-        // letters. Parse anyway, but throw away the scheme.
-        return _parse(uri.substring(5), 0, null);
-      }
+    if (!uri.startsWith("data:")) {
+      throw new FormatException("Does not start with 'data:'", uri, 0);
     }
-    throw new FormatException("Does not start with 'data:'", uri, 0);
+    return _parse(uri, 5, null);
   }
 
   /**
@@ -3307,7 +3050,7 @@
     // That's perfectly reasonable - data URIs are not hierarchical,
     // but it may make some consumers stumble.
     // Should we at least do escape normalization?
-    _uriCache = new _Uri._internal("data", "", null, null, path, query, null);
+    _uriCache = new Uri._internal("data", "", null, null, path, query, null);
     return _uriCache;
   }
 
@@ -3332,7 +3075,7 @@
     int start = _separatorIndices[0] + 1;
     int end = _separatorIndices[1];
     if (start == end) return "text/plain";
-    return _Uri._uriDecode(_text, start, end, UTF8, false);
+    return Uri._uriDecode(_text, start, end, UTF8, false);
   }
 
   /**
@@ -3353,8 +3096,8 @@
       var keyStart = _separatorIndices[i] + 1;
       var keyEnd = _separatorIndices[i + 1];
       if (keyEnd == keyStart + 7 && _text.startsWith("charset", keyStart)) {
-        return _Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
-                               UTF8, false);
+        return Uri._uriDecode(_text, keyEnd + 1, _separatorIndices[i + 2],
+                              UTF8, false);
       }
     }
     return "US-ASCII";
@@ -3412,8 +3155,8 @@
         result[index++] = codeUnit;
       } else {
         if (i + 2 < text.length) {
-          var digit1 = _Uri._parseHexDigit(text.codeUnitAt(i + 1));
-          var digit2 = _Uri._parseHexDigit(text.codeUnitAt(i + 2));
+          var digit1 = Uri._parseHexDigit(text.codeUnitAt(i + 1));
+          var digit2 = Uri._parseHexDigit(text.codeUnitAt(i + 2));
           if (digit1 >= 0 && digit2 >= 0) {
             int byte = digit1 * 16 + digit2;
             result[index++] = byte;
@@ -3434,7 +3177,7 @@
    * If the content is Base64 encoded, it will be decoded to bytes and then
    * decoded to a string using [encoding].
    * If encoding is omitted, the value of a `charset` parameter is used
-   * if it is recognized by [Encoding.getByName], otherwise it defaults to
+   * if it is recongized by [Encoding.getByName], otherwise it defaults to
    * the [ASCII] encoding, which is the default encoding for data URIs
    * that do not specify an encoding.
    *
@@ -3456,7 +3199,7 @@
       var converter = BASE64.decoder.fuse(encoding.decoder);
       return converter.convert(text.substring(start));
     }
-    return _Uri._uriDecode(text, start, text.length, encoding, false);
+    return Uri._uriDecode(text, start, text.length, encoding, false);
   }
 
   /**
@@ -3479,8 +3222,8 @@
       var start = _separatorIndices[i - 2] + 1;
       var equals = _separatorIndices[i - 1];
       var end = _separatorIndices[i];
-      String key = _Uri._uriDecode(_text, start, equals, UTF8, false);
-      String value = _Uri._uriDecode(_text,equals + 1, end, UTF8, false);
+      String key = Uri._uriDecode(_text, start, equals, UTF8, false);
+      String value = Uri._uriDecode(_text,equals + 1, end, UTF8, false);
       result[key] = value;
     }
     return result;
@@ -3563,9 +3306,9 @@
           ((canonicalTable[byte >> 4] & (1 << (byte & 0x0f))) != 0)) {
         buffer.writeCharCode(byte);
       } else {
-        buffer.writeCharCode(_PERCENT);
-        buffer.writeCharCode(_hexDigits.codeUnitAt(byte >> 4));
-        buffer.writeCharCode(_hexDigits.codeUnitAt(byte & 0x0f));
+        buffer.writeCharCode(Uri._PERCENT);
+        buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte >> 4));
+        buffer.writeCharCode(Uri._hexDigits.codeUnitAt(byte & 0x0f));
       }
     }
     if ((byteOr & ~0xFF) != 0) {
@@ -3614,852 +3357,5 @@
   //  mark        =  "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
   //
   // This is the same characters as in a URI query (which is URI pchar plus '?')
-  static const _uricTable = _Uri._queryCharTable;
+  static const _uricTable = Uri._queryCharTable;
 }
-
-// --------------------------------------------------------------------
-// Constants used to read the scanner result.
-// The indices points into the table filled by [_scan] which contains
-// recognized positions in the scanned URI.
-// The `0` index is only used internally.
-
-/// Index of the position of that `:` after a scheme.
-const int _schemeEndIndex     = 1;
-/// Index of the position of the character just before the host name.
-const int _hostStartIndex     = 2;
-/// Index of the position of the `:` before a port value.
-const int _portStartIndex     = 3;
-/// Index of the position of the first character of a path.
-const int _pathStartIndex     = 4;
-/// Index of the position of the `?` before a query.
-const int _queryStartIndex    = 5;
-/// Index of the position of the `#` before a fragment.
-const int _fragmentStartIndex = 6;
-/// Index of a position where the URI was determined to be "non-simple".
-const int _notSimpleIndex     = 7;
-
-// Initial state for scanner.
-const int _uriStart           = 00;
-
-// If scanning of a URI terminates in this state or above,
-// consider the URI non-simple
-const int _nonSimpleEndStates = 14;
-
-// Initial state for scheme validation.
-const int _schemeStart        = 20;
-
-/// Transition tables used to scan a URI to determine its structure.
-///
-/// The tables represent a state machine with output.
-///
-/// To scan the URI, start in the [_uriStart] state, then read each character
-/// of the URI in order, from start to end, and for each character perform a
-/// transition to a new state while writing the current position into the output
-/// buffer at a designated index.
-///
-/// Each state, represented by an integer which is an index into
-/// [_scannerTables], has a set of transitions, one for each character.
-/// The transitions are encoded as a 5-bit integer representing the next state
-/// and a 3-bit index into the output table.
-///
-/// For URI scanning, only characters in the range U+0020 through U+007E are
-/// interesting, all characters outside that range are treated the same.
-/// The tables only contain 96 entries, representing that characters in the
-/// interesting range, plus one more to represent all values outside the range.
-/// The character entries are stored in one `Uint8List` per state, with the
-/// transition for a character at position `character ^ 0x60`,
-/// which maps the range U+0020 .. U+007F into positions 0 .. 95.
-/// All remaining characters are mapped to position 31 (`0x7f ^ 0x60`) which
-/// represents the transition for all remaining characters.
-final List<Uint8List> _scannerTables = _createTables();
-
-// ----------------------------------------------------------------------
-// Code to create the URI scanner table.
-
-/// Creates the tables for [_scannerTables] used by [Uri.parse].
-///
-/// See [_scannerTables] for the generated format.
-///
-/// The concrete tables are chosen as a trade-off between the number of states
-/// needed and the precision of the result.
-/// This allows definitely recognizing the general structure of the URI
-/// (presence and location of scheme, user-info, host, port, path, query and
-/// fragment) while at the same time detecting that some components are not
-/// in canonical form (anything containing a `%`, a host-name containing a
-/// capital letter). Since the scanner doesn't know whether something is a
-/// scheme or a path until it sees `:`, or user-info or host until it sees
-/// a `@`, a second pass is needed to validate the scheme and any user-info
-/// is considered non-canonical by default.
-///
-/// The states (starting from [_uriStart]) write positions while scanning
-/// a string from `start` to `end` as follows:
-///
-/// - [_schemeEndIndex]: Should be initialized to `start-1`.
-///   If the URI has a scheme, it is set to the position of the `:` after
-///   the scheme.
-/// - [_hostStartIndex]: Should be initialized to `start - 1`.
-///   If the URI has an authority, it is set to the character before the
-///   host name - either the second `/` in the `//` leading the authority,
-///   or the `@` after a user-info. Comparing this value to the scheme end
-///   position can be used to detect that there is a user-info component.
-/// - [_portStartIndex]: Should be initialized to `start`.
-///   Set to the position of the last `:` in an authority, and unchanged
-///   if there is no authority or no `:` in an authority.
-///   If this position is after the host start, there is a port, otherwise it
-///   is just marking a colon in the user-info component.
-/// - [_pathStartIndex]: Should be initialized to `start`.
-///   Is set to the first path character unless the path is empty.
-///   If the path is empty, the position is either unchanged (`start`) or
-///   the first slash of an authority. So, if the path start is before a
-///   host start or scheme end, the path is empty.
-/// - [_queryStartIndex]: Should be initialized to `end`.
-///   The position of the `?` leading a query if the URI contains a query.
-/// - [_fragmentStartIndex]: Should be initialized to `end`.
-///   The position of the `#` leading a fragment if the URI contains a fragment.
-/// - [_notSimpleIndex]: Should be initialized to `start - 1`.
-///   Set to another value if the URI is considered "not simple".
-///   This is elaborated below.
-///
-/// # Simple URIs
-/// A URI is considered "simple" if it is in a normalized form containing no
-/// escapes. This allows us to skip normalization and checking whether escapes
-/// are valid, and to extract components without worrying about unescaping.
-///
-/// The scanner computes a conservative approximation of being "simple".
-/// It rejects any URI with an escape, with a user-info component (mainly
-/// because they are rare and would increase the number of states in the
-/// scanner significantly), with an IPV6 host or with a capital letter in
-/// the scheme or host name (the scheme is handled in a second scan using
-/// a separate two-state table).
-/// Further, paths containing `..` or `.` path segments are considered
-/// non-simple except for pure relative paths (no scheme or authority) starting
-/// with a sequence of "../" segments.
-///
-/// The transition tables cannot detect a trailing ".." in the path,
-/// followed by a query or fragment, because the segment is not known to be
-/// complete until we are past it, and we then need to store the query/fragment
-/// start instead. This cast is checked manually post-scanning (such a path
-/// needs to be normalized to end in "../", so the URI shouldn't be considered
-/// simple).
-List<Uint8List> _createTables() {
-  // TODO(lrn): Use a precomputed table.
-
-  // Total number of states for the scanner.
-  const int stateCount         = 22;
-
-  // States used to scan a URI from scratch.
-  const int schemeOrPath       = 01;
-  const int authOrPath         = 02;
-  const int authOrPathSlash    = 03;
-  const int uinfoOrHost0       = 04;
-  const int uinfoOrHost        = 05;
-  const int uinfoOrPort0       = 06;
-  const int uinfoOrPort        = 07;
-  const int ipv6Host           = 08;
-  const int relPathSeg         = 09;
-  const int pathSeg            = 10;
-  const int path               = 11;
-  const int query              = 12;
-  const int fragment           = 13;
-  const int schemeOrPathDot    = 14;
-  const int schemeOrPathDot2   = 15;
-  const int relPathSegDot      = 16;
-  const int relPathSegDot2     = 17;
-  const int pathSegDot         = 18;
-  const int pathSegDot2        = 19;
-
-  // States used to validate a scheme after its end position has been found.
-  const int scheme0            = _schemeStart;
-  const int scheme             = 21;
-
-  // Constants encoding the write-index for the state transition into the top 5
-  // bits of a byte.
-  const int schemeEnd          = _schemeEndIndex     << 5;
-  const int hostStart          = _hostStartIndex     << 5;
-  const int portStart          = _portStartIndex     << 5;
-  const int pathStart          = _pathStartIndex     << 5;
-  const int queryStart         = _queryStartIndex    << 5;
-  const int fragmentStart      = _fragmentStartIndex << 5;
-  const int notSimple          = _notSimpleIndex     << 5;
-
-  /// The `unreserved` characters of RFC 3986.
-  const unreserved =
-      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~"  ;
-  /// The `sub-delim` characters of RFC 3986.
-  const subDelims = r"!$&'()*+,;=";
-  // The `pchar` characters of RFC 3986: characters that may occur in a path,
-  // excluding escapes.
-  const pchar = "$unreserved$subDelims";
-
-  var tables = new List<Uint8List>.generate(stateCount,
-      (_) => new Uint8List(96));
-
-  // Helper function which initialize the table for [state] with a default
-  // transition and returns the table.
-  Uint8List build(state, defaultTransition) =>
-      tables[state]..fillRange(0, 96, defaultTransition);
-
-  // Helper function which sets the transition for each character in [chars]
-  // to [transition] in the [target] table.
-  // The [chars] string must contain only characters in the U+0020 .. U+007E
-  // range.
-  void setChars(Uint8List target, String chars, int transition) {
-    for (int i = 0; i < chars.length; i++) {
-      var char = chars.codeUnitAt(i);
-      target[char ^ 0x60] = transition;
-    }
-  }
-
-  /// Helper function which sets the transition for all characters in the
-  /// range from `range[0]` to `range[1]` to [transition] in the [target] table.
-  ///
-  /// The [range] must be a two-character string where both characters are in
-  /// the U+0020 .. U+007E range and the former character must have a lower
-  /// code point than the latter.
-  void setRange(Uint8List target, String range, int transition) {
-    for (int i = range.codeUnitAt(0), n = range.codeUnitAt(1); i <= n; i++) {
-      target[i ^ 0x60] = transition;
-    }
-  }
-
-  // Create the transitions for each state.
-  var b;
-
-  // Validate as path, if it is a scheme, we handle it later.
-  b = build(_uriStart, schemeOrPath | notSimple);
-  setChars(b, pchar, schemeOrPath);
-  setChars(b, ".", schemeOrPathDot);
-  setChars(b, ":", authOrPath | schemeEnd);  // Handle later.
-  setChars(b, "/", authOrPathSlash);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(schemeOrPathDot, schemeOrPath | notSimple);
-  setChars(b, pchar, schemeOrPath);
-  setChars(b, ".", schemeOrPathDot2);
-  setChars(b, ':', authOrPath | schemeEnd);
-  setChars(b, "/", pathSeg | notSimple);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(schemeOrPathDot2, schemeOrPath | notSimple);
-  setChars(b, pchar, schemeOrPath);
-  setChars(b, "%", schemeOrPath | notSimple);
-  setChars(b, ':', authOrPath | schemeEnd);
-  setChars(b, "/", relPathSeg);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(schemeOrPath, schemeOrPath | notSimple);
-  setChars(b, pchar, schemeOrPath);
-  setChars(b, ':', authOrPath | schemeEnd);
-  setChars(b, "/", pathSeg);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(authOrPath, path | notSimple);
-  setChars(b, pchar, path | pathStart);
-  setChars(b, "/", authOrPathSlash | pathStart);
-  setChars(b, ".", pathSegDot | pathStart);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(authOrPathSlash, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, "/", uinfoOrHost0 | hostStart);
-  setChars(b, ".", pathSegDot);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(uinfoOrHost0, uinfoOrHost | notSimple);
-  setChars(b, pchar, uinfoOrHost);
-  setRange(b, "AZ", uinfoOrHost | notSimple);
-  setChars(b, ":", uinfoOrPort0 | portStart);
-  setChars(b, "@", uinfoOrHost0 | hostStart);
-  setChars(b, "[", ipv6Host | notSimple);
-  setChars(b, "/", pathSeg | pathStart);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(uinfoOrHost, uinfoOrHost | notSimple);
-  setChars(b, pchar, uinfoOrHost);
-  setRange(b, "AZ", uinfoOrHost | notSimple);
-  setChars(b, ":", uinfoOrPort0 | portStart);
-  setChars(b, "@", uinfoOrHost0 | hostStart);
-  setChars(b, "/", pathSeg | pathStart);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(uinfoOrPort0, uinfoOrPort | notSimple);
-  setRange(b, "19", uinfoOrPort);
-  setChars(b, "@", uinfoOrHost0 | hostStart);
-  setChars(b, "/", pathSeg | pathStart);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(uinfoOrPort, uinfoOrPort | notSimple);
-  setRange(b, "09", uinfoOrPort);
-  setChars(b, "@", uinfoOrHost0 | hostStart);
-  setChars(b, "/", pathSeg | pathStart);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(ipv6Host, ipv6Host);
-  setChars(b, "]", uinfoOrHost);
-
-  b = build(relPathSeg, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, ".", relPathSegDot);
-  setChars(b, "/", pathSeg | notSimple);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(relPathSegDot, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, ".", relPathSegDot2);
-  setChars(b, "/", pathSeg | notSimple);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(relPathSegDot2, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, "/", relPathSeg);
-  setChars(b, "?", query | queryStart);  // This should be non-simple.
-  setChars(b, "#", fragment | fragmentStart);  // This should be non-simple.
-
-  b = build(pathSeg, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, ".", pathSegDot);
-  setChars(b, "/", pathSeg | notSimple);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(pathSegDot, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, ".", pathSegDot2);
-  setChars(b, "/", pathSeg | notSimple);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(pathSegDot2, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, "/", pathSeg | notSimple);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(path, path | notSimple);
-  setChars(b, pchar, path);
-  setChars(b, "/", pathSeg);
-  setChars(b, "?", query | queryStart);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(query, query | notSimple);
-  setChars(b, pchar, query);
-  setChars(b, "?", query);
-  setChars(b, "#", fragment | fragmentStart);
-
-  b = build(fragment, fragment | notSimple);
-  setChars(b, pchar, fragment);
-  setChars(b, "?", fragment);
-
-  // A separate two-state validator for lower-case scheme names.
-  // Any non-scheme character or upper-case letter is marked as non-simple.
-  b = build(scheme0, scheme | notSimple);
-  setRange(b, "az", scheme);
-
-  b = build(scheme, scheme | notSimple);
-  setRange(b, "az", scheme);
-  setRange(b, "09", scheme);
-  setChars(b, "+-.", scheme);
-
-  return tables;
-}
-
-// --------------------------------------------------------------------
-// Code that uses the URI scanner table.
-
-/// Scan a string using the [_scannerTables] state machine.
-///
-/// Scans [uri] from [start] to [end], startig in state [state] and
-/// writing output into [indices].
-///
-/// Returns the final state.
-int _scan(String uri, int start, int end, int state, List<int> indices) {
-  var tables = _scannerTables;
-  assert(end <= uri.length);
-  for (int i = start; i < end; i++) {
-    var table = tables[state];
-    // Xor with 0x60 to move range 0x20-0x7f into 0x00-0x5f
-    int char = uri.codeUnitAt(i) ^ 0x60;
-    // Use 0x1f (nee 0x7f) to represent all unhandled characters.
-    if (char > 0x5f) char = 0x1f;
-    int transition = table[char];
-    state = transition & 0x1f;
-    indices[transition >> 5] = i;
-  }
-  return state;
-}
-
-class _SimpleUri implements Uri {
-  final String _uri;
-  final int _schemeEnd;
-  final int _hostStart;
-  final int _portStart;
-  final int _pathStart;
-  final int _queryStart;
-  final int _fragmentStart;
-  /// The scheme is often used to distinguish URIs.
-  /// To make comparisons more efficient, we cache the value, and
-  /// canonicalize a few known types.
-  String _schemeCache;
-  int _hashCodeCache;
-
-  _SimpleUri(
-      this._uri,
-      this._schemeEnd,
-      this._hostStart,
-      this._portStart,
-      this._pathStart,
-      this._queryStart,
-      this._fragmentStart,
-      this._schemeCache);
-
-  bool get hasScheme => _schemeEnd > 0;
-  bool get hasAuthority => _hostStart > 0;
-  bool get hasUserInfo => _hostStart > _schemeEnd + 4;
-  bool get hasPort => _hostStart > 0 && _portStart + 1 < _pathStart;
-  bool get hasQuery => _queryStart < _fragmentStart;
-  bool get hasFragment => _fragmentStart < _uri.length;
-
-  bool get _isFile => _schemeEnd == 4 && _uri.startsWith("file");
-  bool get _isHttp => _schemeEnd == 4 && _uri.startsWith("http");
-  bool get _isHttps => _schemeEnd == 5 && _uri.startsWith("https");
-  bool get _isPackage => _schemeEnd == 7 && _uri.startsWith("package");
-  bool _isScheme(String scheme) =>
-    _schemeEnd == scheme.length && _uri.startsWith(scheme);
-
-  bool get hasAbsolutePath => _uri.startsWith("/", _pathStart);
-  bool get hasEmptyPath => _pathStart == _queryStart;
-
-  bool get isAbsolute => hasScheme && !hasFragment;
-
-  String get scheme {
-    if (_schemeEnd <= 0) return "";
-    if (_schemeCache != null) return _schemeCache;
-    if (_isHttp) {
-      _schemeCache = "http";
-    } else if (_isHttps) {
-      _schemeCache = "https";
-    } else if (_isFile) {
-      _schemeCache = "file";
-    } else if (_isPackage) {
-      _schemeCache = "package";
-    } else {
-      _schemeCache = _uri.substring(0, _schemeEnd);
-    }
-    return _schemeCache;
-  }
-  String get authority => _hostStart > 0 ?
-      _uri.substring(_schemeEnd + 3, _pathStart) : "";
-  String get userInfo => (_hostStart > _schemeEnd + 3) ?
-      _uri.substring(_schemeEnd + 3, _hostStart - 1) : "";
-  String get host =>
-      _hostStart > 0 ? _uri.substring(_hostStart, _portStart) : "";
-  int get port {
-    if (hasPort) return int.parse(_uri.substring(_portStart + 1, _pathStart));
-    if (_isHttp) return 80;
-    if (_isHttps) return 443;
-    return 0;
-  }
-  String get path =>_uri.substring(_pathStart, _queryStart);
-  String get query => (_queryStart < _fragmentStart) ?
-     _uri.substring(_queryStart + 1, _fragmentStart) : "";
-  String get fragment => (_fragmentStart < _uri.length) ?
-     _uri.substring(_fragmentStart + 1) : "";
-
-  String get origin {
-    // Check original behavior - W3C spec is wonky!
-    bool isHttp = _isHttp;
-    if (_schemeEnd < 0 || _hostStart == _portStart) {
-      throw new StateError("Cannot use origin without a scheme: $this");
-    }
-    if (!isHttp && !_isHttps) {
-      throw new StateError(
-        "Origin is only applicable schemes http and https: $this");
-    }
-    if (_hostStart == _schemeEnd + 3) {
-      return _uri.substring(0, _pathStart);
-    }
-    // Need to drop anon-empty userInfo.
-    return _uri.substring(0, _schemeEnd + 3) +
-           _uri.substring(_hostStart, _pathStart);
-  }
-
-  List<String> get pathSegments {
-    int start = _pathStart;
-    int end = _queryStart;
-    if (_uri.startsWith("/", start)) start++;
-    if (start == end) return const <String>[];
-    List<String> parts = [];
-    for (int i = start; i < end; i++) {
-      var char = _uri.codeUnitAt(i);
-      if (char == _SLASH) {
-        parts.add(_uri.substring(start, i));
-        start = i + 1;
-      }
-    }
-    parts.add(_uri.substring(start, end));
-    return new List<String>.unmodifiable(parts);
-  }
-
-  Map<String, String> get queryParameters {
-    if (!hasQuery) return const <String, String>{};
-    return new UnmodifiableMapView<String, String>(
-        Uri.splitQueryString(query));
-  }
-
-  Map<String, List<String>> get queryParametersAll {
-    if (!hasQuery) return const <String, List<String>>{};
-    Map queryParameterLists = _Uri._splitQueryStringAll(query);
-    for (var key in queryParameterLists.keys) {
-      queryParameterLists[key] =
-          new List<String>.unmodifiable(queryParameterLists[key]);
-    }
-    return new Map<String, List<String>>.unmodifiable(queryParameterLists);
-  }
-
-  bool _isPort(String port) {
-    int portDigitStart = _portStart + 1;
-    return portDigitStart + port.length == _pathStart &&
-           _uri.startsWith(port, portDigitStart);
-  }
-
-  Uri normalizePath() => this;
-
-  Uri removeFragment() {
-    if (!hasFragment) return this;
-    return new _SimpleUri(
-      _uri.substring(0, _fragmentStart),
-      _schemeEnd, _hostStart, _portStart,
-      _pathStart, _queryStart, _fragmentStart, _schemeCache);
-  }
-
-  Uri replace({String scheme,
-               String userInfo,
-               String host,
-               int port,
-               String path,
-               Iterable<String> pathSegments,
-               String query,
-               Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
-               String fragment}) {
-    bool schemeChanged = false;
-    if (scheme != null) {
-      scheme = _Uri._makeScheme(scheme, 0, scheme.length);
-      schemeChanged = !_isScheme(scheme);
-    } else {
-      scheme = this.scheme;
-    }
-    bool isFile = (scheme == "file");
-    if (userInfo != null) {
-      userInfo = _Uri._makeUserInfo(userInfo, 0, userInfo.length);
-    } else if (_hostStart > 0) {
-      userInfo = _uri.substring(_schemeEnd + 3, _hostStart);
-    } else {
-      userInfo = "";
-    }
-    if (port != null) {
-      port = _Uri._makePort(port, scheme);
-    } else {
-      port = this.hasPort ? this.port : null;
-      if (schemeChanged) {
-        // The default port might have changed.
-        port = _Uri._makePort(port, scheme);
-      }
-    }
-    if (host != null) {
-      host = _Uri._makeHost(host, 0, host.length, false);
-    } else if (_hostStart > 0) {
-      host = _uri.substring(_hostStart, _portStart);
-    } else if (userInfo.isNotEmpty || port != null || isFile) {
-      host = "";
-    }
-
-    bool hasAuthority = host != null;
-    if (path != null || pathSegments != null) {
-      path = _Uri._makePath(path, 0, _stringOrNullLength(path), pathSegments,
-                           scheme, hasAuthority);
-    } else {
-      path = _uri.substring(_pathStart, _queryStart);
-      if ((isFile || (hasAuthority && !path.isEmpty)) &&
-          !path.startsWith('/')) {
-        path = "/" + path;
-      }
-    }
-
-    if (query != null || queryParameters != null) {
-      query = _Uri._makeQuery(
-          query, 0, _stringOrNullLength(query), queryParameters);
-    } else if (_queryStart < _fragmentStart) {
-      query = _uri.substring(_queryStart, _fragmentStart);
-    }
-
-    if (fragment != null) {
-      fragment = _Uri._makeFragment(fragment, 0, fragment.length);
-    } else if (_fragmentStart < _uri.length) {
-      fragment = _uri.substring(_fragmentStart + 1);
-    }
-
-    return new _Uri._internal(
-        scheme, userInfo, host, port, path, query, fragment);
-  }
-
-  Uri resolve(String reference) {
-    return resolveUri(Uri.parse(reference));
-  }
-
-  Uri resolveUri(Uri reference) {
-    if (reference is _SimpleUri) {
-      return _simpleMerge(this, reference);
-    }
-    return _toNonSimple().resolveUri(reference);
-  }
-
-  // Merge two simple URIs. This should always result in a prefix of
-  // one concatentated with a suffix of the other, possibly with a `/` in
-  // the middle of two merged paths, which is again simple.
-  // In a few cases, there might be a need for extra normalization, when
-  // resolving on top of a known scheme.
-  Uri _simpleMerge(_SimpleUri base, _SimpleUri ref) {
-    if (ref.hasScheme) return ref;
-    if (ref.hasAuthority) {
-      if (!base.hasScheme) return ref;
-      bool isSimple = true;
-      if (base._isFile) {
-        isSimple = !ref.hasEmptyPath;
-      } else if (base._isHttp) {
-        isSimple = !ref._isPort("80");
-      } else if (base._isHttps) {
-        isSimple = !ref._isPort("443");
-      }
-      if (isSimple) {
-        var delta = base._schemeEnd + 1;
-        var newUri = base._uri.substring(0, base._schemeEnd + 1) +
-                     ref._uri.substring(ref._schemeEnd + 1);
-        return new _SimpleUri(newUri,
-           base._schemeEnd,
-           ref._hostStart + delta,
-           ref._portStart + delta,
-           ref._pathStart + delta,
-           ref._queryStart + delta,
-           ref._fragmentStart + delta,
-           base._schemeCache);
-      } else {
-        // This will require normalization, so use the _Uri implementation.
-        return _toNonSimple().resolveUri(ref);
-      }
-    }
-    if (ref.hasEmptyPath) {
-      if (ref.hasQuery) {
-        int delta = base._queryStart - ref._queryStart;
-        var newUri = base._uri.substring(0, base._queryStart) +
-                     ref._uri.substring(ref._queryStart);
-        return new _SimpleUri(newUri,
-           base._schemeEnd,
-           base._hostStart,
-           base._portStart,
-           base._pathStart,
-           ref._queryStart + delta,
-           ref._fragmentStart + delta,
-           base._schemeCache);
-      }
-      if (ref.hasFragment) {
-        int delta = base._fragmentStart - ref._fragmentStart;
-        var newUri = base._uri.substring(0, base._fragmentStart) +
-                     ref._uri.substring(ref._fragmentStart);
-        return new _SimpleUri(newUri,
-           base._schemeEnd,
-           base._hostStart,
-           base._portStart,
-           base._pathStart,
-           base._queryStart,
-           ref._fragmentStart + delta,
-           base._schemeCache);
-      }
-      return base.removeFragment();
-    }
-    if (ref.hasAbsolutePath) {
-      var delta = base._pathStart - ref._pathStart;
-      var newUri = base._uri.substring(0, base._pathStart) +
-                   ref._uri.substring(ref._pathStart);
-      return new _SimpleUri(newUri,
-        base._schemeEnd,
-        base._hostStart,
-        base._portStart,
-        base._pathStart,
-        ref._queryStart + delta,
-        ref._fragmentStart + delta,
-        base._schemeCache);
-    }
-    if (base.hasEmptyPath && base.hasAuthority) {
-      // ref has relative non-empty path.
-      // Add a "/" in front, then leading "/../" segments are folded to "/".
-      int refStart = ref._pathStart;
-      while (ref._uri.startsWith("../", refStart)) {
-        refStart += 3;
-      }
-      var delta = base._pathStart - refStart + 1;
-      var newUri = "${base._uri.substring(0, base._pathStart)}/"
-                   "${ref._uri.substring(refStart)}";
-      return new _SimpleUri(newUri,
-        base._schemeEnd,
-        base._hostStart,
-        base._portStart,
-        base._pathStart,
-        ref._queryStart + delta,
-        ref._fragmentStart + delta,
-        base._schemeCache);
-    }
-    // Merge paths.
-    if (base._uri.startsWith("../", base._pathStart)) {
-      // Complex rare case, go slow.
-      return _toNonSimple().resolveUri(ref);
-    }
-
-    // The RFC 3986 algorithm merges the base path without its final segment
-    // (anything after the final "/", or everything if the base path doesn't
-    // contain any "/"), and the reference path.
-    // Then it removes "." and ".." segments using the remove-dot-segment
-    // algorithm.
-    // This code combines the two steps. It is simplified by knowing that
-    // the base path contains no "." or ".." segments, and the reference
-    // path can only contain leading ".." segments.
-
-    String baseUri = base._uri;
-    String refUri = ref._uri;
-    int baseStart = base._pathStart;
-    int baseEnd = base._queryStart;
-    int refStart = ref._pathStart;
-    int refEnd = ref._queryStart;
-    int backCount = 1;
-
-    int slashCount = 0;
-
-    // Count leading ".." segments in reference path.
-    while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) {
-      refStart += 3;
-      backCount += 1;
-    }
-
-    // Extra slash inserted between base and reference path parts if
-    // the base path contains any slashes.
-    // (We could use a slash from the base path in most cases, but not if
-    // we remove the entire base path).
-    String insert = "";
-    while (baseEnd > baseStart) {
-      baseEnd--;
-      int char = baseUri.codeUnitAt(baseEnd);
-      if (char == _SLASH) {
-        insert = "/";
-        backCount--;
-        if (backCount == 0) break;
-      }
-    }
-    // If the base URI has no scheme or authority (`_pathStart == 0`)
-    // and a relative path, and we reached the beginning of the path,
-    // we have a special case.
-    if (baseEnd == 0 && !base.hasAbsolutePath) {
-      // Non-RFC 3986 behavior when resolving a purely relative path on top of
-      // another relative path: Don't make the result absolute.
-      insert = "";
-    }
-
-    var delta = baseEnd - refStart + insert.length;
-    var newUri = "${base._uri.substring(0, baseEnd)}$insert"
-               "${ref._uri.substring(refStart)}";
-
-    return new _SimpleUri(newUri,
-      base._schemeEnd,
-      base._hostStart,
-      base._portStart,
-      base._pathStart,
-      ref._queryStart + delta,
-      ref._fragmentStart + delta,
-      base._schemeCache);
-  }
-
-  String toFilePath({bool windows}) {
-    if (_schemeEnd >= 0 && !_isFile) {
-      throw new UnsupportedError(
-          "Cannot extract a file path from a $scheme URI");
-    }
-    if (_queryStart < _uri.length) {
-      if (_queryStart < _fragmentStart) {
-        throw new UnsupportedError(
-            "Cannot extract a file path from a URI with a query component");
-      }
-      throw new UnsupportedError(
-          "Cannot extract a file path from a URI with a fragment component");
-    }
-    if (windows == null) windows = _Uri._isWindows;
-    return windows ? _Uri._toWindowsFilePath(this) : _toFilePath();
-  }
-
-  String _toFilePath() {
-    if (_hostStart < _portStart) {
-      // Has authority and non-empty host.
-      throw new UnsupportedError(
-        "Cannot extract a non-Windows file path from a file URI "
-        "with an authority");
-    }
-    return this.path;
-  }
-
-  UriData get data {
-    assert(scheme != "data");
-    return null;
-  }
-
-  int get hashCode => _hashCodeCache ??= _uri.hashCode;
-
-  bool operator==(Object other) {
-    if (identical(this, other)) return true;
-    if (other is Uri) return _uri == other.toString();
-    return false;
-  }
-
-  Uri _toNonSimple() {
-    return new _Uri._internal(
-      this.scheme,
-      this.userInfo,
-      this.hasAuthority ? this.host: null,
-      this.hasPort ? this.port : null,
-      this.path,
-      this.hasQuery ? this.query : null,
-      this.hasFragment ? this.fragment : null
-    );
-  }
-
-  String toString() => _uri;
-}
-
-/// Checks whether [text] starts with "data:" at position [start].
-///
-/// The text must be long enough to allow reading five characters
-/// from the [start] position.
-///
-/// Returns an integer value which is zero if text starts with all-lowercase
-/// "data:" and 0x20 if the text starts with "data:" that isn't all lower-case.
-/// All other values means the text starts with some other character.
-int _startsWithData(String text, int start) {
-  // Multiply by 3 to avoid a non-colon character making delta be 0x20.
-  int delta = (text.codeUnitAt(start + 4) ^ _COLON) * 3;
-  delta |= text.codeUnitAt(start)     ^ 0x64 /*d*/;
-  delta |= text.codeUnitAt(start + 1) ^ 0x61 /*a*/;
-  delta |= text.codeUnitAt(start + 2) ^ 0x74 /*t*/;
-  delta |= text.codeUnitAt(start + 3) ^ 0x61 /*a*/;
-  return delta;
-}
-
-/// Helper function returning the length of a string, or `0` for `null`.
-int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 0bc3749..b4f835b 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -22889,7 +22889,7 @@
 
   @DomName('MediaStream.getAudioTracks')
   @DocsEditable()
-  @Creates('JSExtendableArray')
+  @Creates('JSExtendableArray|MediaStreamTrack')
   @Returns('JSExtendableArray')
   List<MediaStreamTrack> getAudioTracks() native;
 
@@ -22904,7 +22904,7 @@
 
   @DomName('MediaStream.getVideoTracks')
   @DocsEditable()
-  @Creates('JSExtendableArray')
+  @Creates('JSExtendableArray|MediaStreamTrack')
   @Returns('JSExtendableArray')
   List<MediaStreamTrack> getVideoTracks() native;
 
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index bc92d6d..68ec664 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -37662,10 +37662,10 @@
     if ((blob_OR_source_OR_stream is Blob || blob_OR_source_OR_stream == null)) {
       return _blink.BlinkURL.instance.createObjectURL_Callback_1_(blob_OR_source_OR_stream);
     }
-    if ((blob_OR_source_OR_stream is MediaStream)) {
+    if ((blob_OR_source_OR_stream is MediaSource)) {
       return _blink.BlinkURL.instance.createObjectURL_Callback_1_(blob_OR_source_OR_stream);
     }
-    if ((blob_OR_source_OR_stream is MediaSource)) {
+    if ((blob_OR_source_OR_stream is MediaStream)) {
       return _blink.BlinkURL.instance.createObjectURL_Callback_1_(blob_OR_source_OR_stream);
     }
     throw new ArgumentError("Incorrect number or type of arguments");
diff --git a/tests/compiler/dart2js/compiler_helper.dart b/tests/compiler/dart2js/compiler_helper.dart
index 2c1684e..e365f3b 100644
--- a/tests/compiler/dart2js/compiler_helper.dart
+++ b/tests/compiler/dart2js/compiler_helper.dart
@@ -179,7 +179,7 @@
 
 Future compileSources(Map<String, String> sources,
                check(MockCompiler compiler)) {
-  Uri base = new Uri(scheme: 'source', path: '/');
+  Uri base = new Uri(scheme: 'source');
   Uri mainUri = base.resolve('main.dart');
   String mainCode = sources['main.dart'];
   Expect.isNotNull(mainCode, 'No source code found for "main.dart"');
diff --git a/tests/compiler/dart2js/mirrors_used_test.dart b/tests/compiler/dart2js/mirrors_used_test.dart
index 4b9c31a..fc08b6b 100644
--- a/tests/compiler/dart2js/mirrors_used_test.dart
+++ b/tests/compiler/dart2js/mirrors_used_test.dart
@@ -69,7 +69,7 @@
     // 2. Some code was refactored, and there are more methods.
     // Either situation could be problematic, but in situation 2, it is often
     // acceptable to increase [expectedMethodCount] a little.
-    int expectedMethodCount = 466;
+    int expectedMethodCount = 432;
     Expect.isTrue(
         generatedCode.length <= expectedMethodCount,
         'Too many compiled methods: '
diff --git a/tests/corelib/data_uri_test.dart b/tests/corelib/data_uri_test.dart
index 41f2290..1a21be1 100644
--- a/tests/corelib/data_uri_test.dart
+++ b/tests/corelib/data_uri_test.dart
@@ -18,13 +18,6 @@
   testRoundTrip("blåbærgrød", UTF8);
   testRoundTrip("blåbærgrød", LATIN1);
 
-  testUriEquals("data:,abc?d#e");
-  testUriEquals("DATA:,ABC?D#E");
-  testUriEquals("data:,a%20bc?d#e");
-  testUriEquals("DATA:,A%20BC?D#E");
-  testUriEquals("data:,a%62c?d#e");
-  testUriEquals("DATA:,A%42C?D#E");
-
   testUtf8Encoding("\u1000\uffff");
   testBytes();
   testInvalidCharacters();
@@ -257,11 +250,3 @@
   Expect.equals(expect.hasFragment, actual.hasFragment, "hasFragment");
   Expect.equals(expect.fragment, actual.fragment, "fragment");
 }
-
-void testUriEquals(String uriText) {
-  var data = UriData.parse(uriText);
-  var uri = Uri.parse(uriText);
-  Expect.equals(data.uri, uri);
-  Expect.equals(data.toString(), uri.data.toString());
-  Expect.equals(data.toString(), uri.toString());
-}
diff --git a/tests/corelib/uri_test.dart b/tests/corelib/uri_test.dart
index 3d24e2d..ef7a9a3 100644
--- a/tests/corelib/uri_test.dart
+++ b/tests/corelib/uri_test.dart
@@ -10,12 +10,6 @@
 testUri(String uriText, bool isAbsolute) {
   var uri = Uri.parse(uriText);
 
-  // Test that parsing a substring works the same as parsing the string.
-  String wrapper = "://@[]:/%?#";
-  var embeddedUri = Uri.parse(
-       "$wrapper$uri$wrapper", wrapper.length, uriText.length + wrapper.length);
-
-  Expect.equals(uri, embeddedUri);
   Expect.equals(isAbsolute, uri.isAbsolute);
   Expect.stringEquals(uriText, uri.toString());
 
@@ -88,8 +82,7 @@
   final urisSample = "http://a/b/c/d;p?q";
   Uri base = Uri.parse(urisSample);
   testResolve(expect, relative) {
-    String name = "$base << $relative";
-    Expect.stringEquals(expect, base.resolve(relative).toString(), name);
+    Expect.stringEquals(expect, base.resolve(relative).toString());
   }
 
   // From RFC 3986.
@@ -139,232 +132,19 @@
   // Additional tests (not from RFC 3986).
   testResolve("http://a/b/g;p/h;s",    "../g;p/h;s");
 
-  base = Uri.parse("s:a/b");
-  testResolve("s:a/c", "c");
-  testResolve("s:/c", "../c");
-
-  base = Uri.parse("S:a/b");
-  testResolve("s:a/c", "c");
-  testResolve("s:/c", "../c");
-
-  base = Uri.parse("s:foo");
-  testResolve("s:bar", "bar");
-  testResolve("s:bar", "../bar");
-
-  base = Uri.parse("S:foo");
-  testResolve("s:bar", "bar");
-  testResolve("s:bar", "../bar");
-
-  // Special-case (deliberate non-RFC behavior).
-  base = Uri.parse("foo/bar");
-  testResolve("foo/baz", "baz");
-  testResolve("baz", "../baz");
-
-  base = Uri.parse("s:/foo");
-  testResolve("s:/bar", "bar");
-  testResolve("s:/bar", "../bar");
-
-  base = Uri.parse("S:/foo");
-  testResolve("s:/bar", "bar");
-  testResolve("s:/bar", "../bar");
-
   // Test non-URI base (no scheme, no authority, relative path).
   base = Uri.parse("a/b/c?_#_");
   testResolve("a/b/g?q#f", "g?q#f");
   testResolve("./", "../..");
   testResolve("../", "../../..");
   testResolve("a/b/", ".");
-  testResolve("c", "../../c");  // Deliberate non-RFC behavior.
+  testResolve("c", "../../c");
   base = Uri.parse("../../a/b/c?_#_");  // Initial ".." in base url.
   testResolve("../../a/d", "../d");
   testResolve("../../../d", "../../../d");
 
-  base = Uri.parse("s://h/p?q#f");  // A simple base.
-  // Simple references:
-  testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F");
-  testResolve("s://h2/P?Q#F", "//h2/P?Q#F");
-  testResolve("s://h/P?Q#F", "/P?Q#F");
-  testResolve("s://h/p?Q#F", "?Q#F");
-  testResolve("s://h/p?q#F", "#F");
-  testResolve("s://h/p?q", "");
-  // Non-simple references:
-  testResolve("s2://I@h2/P?Q#F%20", "s2://I@h2/P?Q#F%20");
-  testResolve("s://I@h2/P?Q#F%20", "//I@h2/P?Q#F%20");
-  testResolve("s://h2/P?Q#F%20", "//h2/P?Q#F%20");
-  testResolve("s://h/P?Q#F%20", "/P?Q#F%20");
-  testResolve("s://h/p?Q#F%20", "?Q#F%20");
-  testResolve("s://h/p?q#F%20", "#F%20");
-
-  base = Uri.parse("s://h/p1/p2/p3");  // A simple base with a path.
-  testResolve("s://h/p1/p2/", ".");
-  testResolve("s://h/p1/p2/", "./");
-  testResolve("s://h/p1/", "..");
-  testResolve("s://h/p1/", "../");
-  testResolve("s://h/", "../..");
-  testResolve("s://h/", "../../");
-  testResolve("s://h/p1/%20", "../%20");
-  testResolve("s://h/", "../../../..");
-  testResolve("s://h/", "../../../../");
-
-  base = Uri.parse("s://h/p?q#f%20");  // A non-simpe base.
-  // Simple references:
-  testResolve("s2://h2/P?Q#F", "s2://h2/P?Q#F");
-  testResolve("s://h2/P?Q#F", "//h2/P?Q#F");
-  testResolve("s://h/P?Q#F", "/P?Q#F");
-  testResolve("s://h/p?Q#F", "?Q#F");
-  testResolve("s://h/p?q#F", "#F");
-  testResolve("s://h/p?q", "");
-  // Non-simple references:
-  testResolve("s2://I@h2/P?Q#F%20", "s2://I@h2/P?Q#F%20");
-  testResolve("s://I@h2/P?Q#F%20", "//I@h2/P?Q#F%20");
-  testResolve("s://h2/P?Q#F%20", "//h2/P?Q#F%20");
-  testResolve("s://h/P?Q#F%20", "/P?Q#F%20");
-  testResolve("s://h/p?Q#F%20", "?Q#F%20");
-  testResolve("s://h/p?q#F%20", "#F%20");
-
-  base = Uri.parse("S://h/p1/p2/p3");  // A non-simple base with a path.
-  testResolve("s://h/p1/p2/", ".");
-  testResolve("s://h/p1/p2/", "./");
-  testResolve("s://h/p1/", "..");
-  testResolve("s://h/p1/", "../");
-  testResolve("s://h/", "../..");
-  testResolve("s://h/", "../../");
-  testResolve("s://h/p1/%20", "../%20");
-  testResolve("s://h/", "../../../..");
-  testResolve("s://h/", "../../../../");
-
-  base = Uri.parse("../../../");  // A simple relative path.
-  testResolve("../../../a", "a");
-  testResolve("../../../../a", "../a");
-  testResolve("../../../a%20", "a%20");
-  testResolve("../../../../a%20", "../a%20");
-
-  // Tests covering the branches of the merge algorithm in RFC 3986
-  // with both simple and complex base URIs.
-  for (var b in ["s://a/pa/pb?q#f", "s://a/pa/pb?q#f%20"]) {
-    var origBase = Uri.parse(b);
-    base = origBase;
-
-    // if defined(R.scheme) then ...
-    testResolve("s2://a2/p2?q2#f2", "s2://a2/p2?q2#f2");
-    // else, if defined(R.authority) then ...
-    testResolve("s://a2/p2?q2#f2", "//a2/p2?q2#f2");
-    testResolve("s://a2/?q2#f2", "//a2/../?q2#f2");
-    testResolve("s://a2?q2#f2", "//a2?q2#f2");
-    testResolve("s://a2#f2", "//a2#f2");
-    testResolve("s://a2", "//a2");
-    // else, if (R.path == "") then ...
-    //   if defined(R.query) then
-    testResolve("s://a/pa/pb?q2#f2", "?q2#f2");
-    testResolve("s://a/pa/pb?q2", "?q2");
-    //   else
-    testResolve("s://a/pa/pb?q#f2", "#f2");
-    testResolve("s://a/pa/pb?q", "");
-    // else, if (R.path starts-with "/") then ...
-    testResolve("s://a/p2?q2#f2", "/p2?q2#f2");
-    testResolve("s://a/?q2#f2", "/?q2#f2");
-    testResolve("s://a/#f2", "/#f2");
-    testResolve("s://a/", "/");
-    testResolve("s://a/", "/../");
-    // else ... T.path = merge(Base.path, R.path)
-    // ... remove-dot-fragments(T.path) ...
-    // (Cover the merge function and the remove-dot-fragments functions too).
-
-    // If base has authority and empty path ...
-    var emptyPathBase = Uri.parse(b.replaceFirst("/pa/pb", ""));
-    base = emptyPathBase;
-    testResolve("s://a/p2?q2#f2", "p2?q2#f2");
-    testResolve("s://a/p2#f2", "p2#f2");
-    testResolve("s://a/p2", "p2");
-
-    base = origBase;
-    // otherwise
-    // (Cover both no authority and non-empty path and both).
-    var noAuthEmptyPathBase = Uri.parse(b.replaceFirst("//a/pa/pb", ""));
-    var noAuthAbsPathBase = Uri.parse(b.replaceFirst("//a", ""));
-    var noAuthRelPathBase = Uri.parse(b.replaceFirst("//a/", ""));
-    var noAuthRelSinglePathBase = Uri.parse(b.replaceFirst("//a/pa/", ""));
-
-    testResolve("s://a/pa/p2?q2#f2", "p2?q2#f2");
-    testResolve("s://a/pa/p2#f2", "p2#f2");
-    testResolve("s://a/pa/p2", "p2");
-
-    base = noAuthEmptyPathBase;
-    testResolve("s:p2?q2#f2", "p2?q2#f2");
-    testResolve("s:p2#f2", "p2#f2");
-    testResolve("s:p2", "p2");
-
-    base = noAuthAbsPathBase;
-    testResolve("s:/pa/p2?q2#f2", "p2?q2#f2");
-    testResolve("s:/pa/p2#f2", "p2#f2");
-    testResolve("s:/pa/p2", "p2");
-
-    base = noAuthRelPathBase;
-    testResolve("s:pa/p2?q2#f2", "p2?q2#f2");
-    testResolve("s:pa/p2#f2", "p2#f2");
-    testResolve("s:pa/p2", "p2");
-
-    base = noAuthRelSinglePathBase;
-    testResolve("s:p2?q2#f2", "p2?q2#f2");
-    testResolve("s:p2#f2", "p2#f2");
-    testResolve("s:p2", "p2");
-
-    // Then remove dot segments.
-
-    // A. if input buffer starts with "../" or "./".
-    // This only happens if base has only a single (may be empty) segment and
-    // no slash.
-    base = emptyPathBase;
-    testResolve("s://a/p2", "../p2");
-    testResolve("s://a/", "../");
-    testResolve("s://a/", "..");
-    testResolve("s://a/p2", "./p2");
-    testResolve("s://a/", "./");
-    testResolve("s://a/", ".");
-    testResolve("s://a/p2", "../../p2");
-    testResolve("s://a/p2", "../../././p2");
-
-    base = noAuthRelSinglePathBase;
-    testResolve("s:p2", "../p2");
-    testResolve("s:", "../");
-    testResolve("s:", "..");
-    testResolve("s:p2", "./p2");
-    testResolve("s:", "./");
-    testResolve("s:", ".");
-    testResolve("s:p2", "../../p2");
-    testResolve("s:p2", "../../././p2");
-
-    // B. if input buffer starts with "/./" or is "/.". replace with "/".
-    // (The URI implementation removes the "." path segments when parsing,
-    // so this case isn't handled by merge).
-    base = origBase;
-    testResolve("s://a/pa/p2", "./p2");
-
-    // C. if input buffer starts with "/../" or is "/..", replace with "/"
-    // and remove preceeding segment.
-    testResolve("s://a/p2", "../p2");
-    var longPathBase = Uri.parse(b.replaceFirst("/pb", "/pb/pc/pd"));
-    base = longPathBase;
-    testResolve("s://a/pa/pb/p2", "../p2");
-    testResolve("s://a/pa/p2", "../../p2");
-    testResolve("s://a/p2", "../../../p2");
-    testResolve("s://a/p2", "../../../../p2");
-    var noAuthRelLongPathBase =
-        Uri.parse(b.replaceFirst("//a/pa/pb", "pa/pb/pc/pd"));
-    base = noAuthRelLongPathBase;
-    testResolve("s:pa/pb/p2", "../p2");
-    testResolve("s:pa/p2", "../../p2");
-    testResolve("s:/p2", "../../../p2");
-    testResolve("s:/p2", "../../../../p2");
-
-    // D. if the input buffer contains only ".." or ".", remove it.
-    base = noAuthEmptyPathBase;
-    testResolve("s:", "..");
-    testResolve("s:", ".");
-    base = noAuthRelSinglePathBase;
-    testResolve("s:", "..");
-    testResolve("s:", ".");
-  }
+  base = Uri.parse("s:a/b");
+  testResolve("s:/c", "../c");
 }
 
 void testResolvePath(String expected, String path) {
@@ -718,11 +498,6 @@
                           query: null,
                           fragment: null).toString());
   Expect.stringEquals("file:///", Uri.parse("file:").toString());
-  Expect.stringEquals("file:///", Uri.parse("file:/").toString());
-  Expect.stringEquals("file:///", Uri.parse("file:").toString());
-  Expect.stringEquals("file:///foo", Uri.parse("file:foo").toString());
-  Expect.stringEquals("file:///foo", Uri.parse("file:/foo").toString());
-  Expect.stringEquals("file://foo/", Uri.parse("file://foo").toString());
 
   testResolvePath("/a/g", "/a/b/c/./../../g");
   testResolvePath("/a/g", "/a/b/c/./../../g");
diff --git a/tools/VERSION b/tools/VERSION
index 261629f..bc466cc 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 18
 PATCH 0
 PRERELEASE 4
-PRERELEASE_PATCH 3
+PRERELEASE_PATCH 4
diff --git a/tools/dom/scripts/dartmetadata.py b/tools/dom/scripts/dartmetadata.py
index 37877f1..2b2d594 100644
--- a/tools/dom/scripts/dartmetadata.py
+++ b/tools/dom/scripts/dartmetadata.py
@@ -255,12 +255,12 @@
     ],
 
     'MediaStream.getAudioTracks': [
-      "@Creates('JSExtendableArray')",
+      "@Creates('JSExtendableArray|MediaStreamTrack')",
       "@Returns('JSExtendableArray')",
     ],
 
     'MediaStream.getVideoTracks': [
-      "@Creates('JSExtendableArray')",
+      "@Creates('JSExtendableArray|MediaStreamTrack')",
       "@Returns('JSExtendableArray')",
     ],