| Index: sdk/lib/core/uri.dart | 
| diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart | 
| index d7f911bfa55d98e782e3be82b6730191046c91b2..847521ac48b1f52cf89d2a3a09bbbed59e54a671 100644 | 
| --- a/sdk/lib/core/uri.dart | 
| +++ b/sdk/lib/core/uri.dart | 
| @@ -4323,10 +4323,6 @@ class _SimpleUri implements Uri { | 
| 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 | 
| @@ -4341,39 +4337,53 @@ class _SimpleUri implements Uri { | 
| String refUri = ref._uri; | 
| int baseStart = base._pathStart; | 
| int baseEnd = base._queryStart; | 
| +    while (baseUri.startsWith("../", baseStart)) baseStart += 3; | 
| int refStart = ref._pathStart; | 
| int refEnd = ref._queryStart; | 
| -    int backCount = 1; | 
| - | 
| -    int slashCount = 0; | 
|  | 
| -    // Count leading ".." segments in reference path. | 
| +    /// Count of leading ".." segments in reference path. | 
| +    /// The count is decremented when the segment is matched with a | 
| +    /// segment of the base path, and both are then omitted from the result. | 
| +    int backCount = 0; | 
| +    /// Count "../" segments and advance `refStart` to after the segments. | 
| 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. | 
| +    // the base path contains any slashes, or empty string if none. | 
| // (We could use a slash from the base path in most cases, but not if | 
| // we remove the entire base path). | 
| String insert = ""; | 
| + | 
| +    /// Remove segments from the base path. | 
| +    /// Start with the segment trailing the last slash, | 
| +    /// then remove segments for each leading "../" segment | 
| +    /// from the reference path, or as many of them as are available. | 
| while (baseEnd > baseStart) { | 
| baseEnd--; | 
| int char = baseUri.codeUnitAt(baseEnd); | 
| if (char == _SLASH) { | 
| insert = "/"; | 
| -        backCount--; | 
| if (backCount == 0) break; | 
| +        backCount--; | 
| } | 
| } | 
| -    // 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. | 
| + | 
| +    if (baseEnd == baseStart && !base.hasScheme && !base.hasAbsolutePath) { | 
| +      // If the base is *just* a relative path (no scheme or authority), | 
| +      // then merging with another relative path doesn't follow the | 
| +      // RFC-3986 behavior. | 
| +      // Don't need to check `base.hasAuthority` since the base path is | 
| +      // non-empty - if there is an authority, a non-empty path is absolute. | 
| + | 
| +      // We reached the start of the base path, and want to stay relative, | 
| +      // so don't insert a slash. | 
| insert = ""; | 
| +      // If we reached the start of the base path with more "../" left over | 
| +      // in the reference path, include those segments in the result. | 
| +      refStart -= backCount * 3; | 
| } | 
|  | 
| var delta = baseEnd - refStart + insert.length; | 
|  |