Treat "package:" URLs as absolute. (#25)

Closes #22
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 67c849c..b29d2ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 1.4.2
 
+* Treat `package:` URLs as absolute.
+
 * Normalize `c:\foo\.` to `c:\foo`.
 
 ## 1.4.1
diff --git a/lib/src/style/url.dart b/lib/src/style/url.dart
index f4bab64..2e29aa7 100644
--- a/lib/src/style/url.dart
+++ b/lib/src/style/url.dart
@@ -41,19 +41,29 @@
     if (path.isEmpty) return 0;
     if (isSeparator(path.codeUnitAt(0))) return 1;
 
+    for (var i = 0; i < path.length; i++) {
+      var codeUnit = path.codeUnitAt(i);
+      if (isSeparator(codeUnit)) return 0;
+      if (codeUnit == chars.COLON) {
+        if (i == 0) return 0;
+
+        // The root part is up until the next '/', or the full path. Skip ':'
+        // (and '//' if it exists) and search for '/' after that.
+        if (path.startsWith('//', i + 1)) i += 3;
+        var index = path.indexOf('/', i);
+        if (index <= 0) return path.length;
+
+        // file: URLs sometimes consider Windows drive letters part of the root.
+        // See https://url.spec.whatwg.org/#file-slash-state.
+        if (!withDrive || path.length < index + 3) return index;
+        if (!path.startsWith('file://')) return index;
+        if (!isDriveLetter(path, index + 1)) return index;
+        return path.length == index + 3 ? index + 3 : index + 4;
+      }
+    }
+
     var index = path.indexOf("/");
     if (index > 0 && path.startsWith('://', index - 1)) {
-      // The root part is up until the next '/', or the full path. Skip
-      // '://' and search for '/' after that.
-      index = path.indexOf('/', index + 2);
-      if (index <= 0) return path.length;
-
-      // file: URLs sometimes consider Windows drive letters part of the root.
-      // See https://url.spec.whatwg.org/#file-slash-state.
-      if (!withDrive || path.length < index + 3) return index;
-      if (!path.startsWith('file://')) return index;
-      if (!isDriveLetter(path, index + 1)) return index;
-      return path.length == index + 3 ? index + 3 : index + 4;
     }
     return 0;
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index ff101d6..2c90e1c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: path
-version: 1.4.2-dev
+version: 1.4.2
 author: Dart Team <misc@dartlang.org>
 description: >
  A string-based path manipulation library. All of the path operations you know
diff --git a/test/url_test.dart b/test/url_test.dart
index e8f3413..b8f4e0f 100644
--- a/test/url_test.dart
+++ b/test/url_test.dart
@@ -40,6 +40,8 @@
     expect(context.rootPrefix('file://'), 'file://');
     expect(context.rootPrefix('/'), '/');
     expect(context.rootPrefix('foo/bar://'), '');
+    expect(context.rootPrefix('package:foo/bar.dart'), 'package:foo');
+    expect(context.rootPrefix('foo/bar:baz/qux'), '');
   });
 
   test('dirname', () {
@@ -136,8 +138,10 @@
     expect(context.isAbsolute('~'), false);
     expect(context.isAbsolute('.'), false);
     expect(context.isAbsolute('../a'), false);
-    expect(context.isAbsolute('C:/a'), false);
-    expect(context.isAbsolute(r'C:\a'), false);
+    expect(context.isAbsolute('C:/a'), true);
+    expect(context.isAbsolute(r'C:\a'), true);
+    expect(context.isAbsolute('package:foo/bar.dart'), true);
+    expect(context.isAbsolute('foo/bar:baz/qux'), false);
     expect(context.isAbsolute(r'\\a'), false);
   });
 
@@ -159,8 +163,10 @@
     expect(context.isRelative('~'), true);
     expect(context.isRelative('.'), true);
     expect(context.isRelative('../a'), true);
-    expect(context.isRelative('C:/a'), true);
-    expect(context.isRelative(r'C:\a'), true);
+    expect(context.isRelative('C:/a'), false);
+    expect(context.isRelative(r'C:\a'), false);
+    expect(context.isRelative(r'package:foo/bar.dart'), false);
+    expect(context.isRelative('foo/bar:baz/qux'), true);
     expect(context.isRelative(r'\\a'), true);
   });
 
@@ -184,6 +190,8 @@
     expect(context.isRootRelative('../a'), false);
     expect(context.isRootRelative('C:/a'), false);
     expect(context.isRootRelative(r'C:\a'), false);
+    expect(context.isRootRelative(r'package:foo/bar.dart'), false);
+    expect(context.isRootRelative('foo/bar:baz/qux'), false);
     expect(context.isRootRelative(r'\\a'), false);
   });
 
@@ -216,7 +224,9 @@
               'a', 'http://google.com/b', 'http://dartlang.org/c', 'd'),
           'http://dartlang.org/c/d');
       expect(context.join('a', '/b', '/c', 'd'), '/c/d');
-      expect(context.join('a', r'c:\b', 'c', 'd'), r'a/c:\b/c/d');
+      expect(context.join('a', r'c:\b', 'c', 'd'), r'c:\b/c/d');
+      expect(context.join('a', 'package:foo/bar', 'c', 'd'),
+          r'package:foo/bar/c/d');
       expect(context.join('a', r'\\b', 'c', 'd'), r'a/\\b/c/d');
     });
 
@@ -225,6 +235,8 @@
           'http://dartlang.org/b/c');
       expect(context.join('file://', 'a', '/b', 'c'), 'file:///b/c');
       expect(context.join('file://', 'a', '/b', 'c', '/d'), 'file:///d');
+      expect(context.join('package:foo/bar.dart', '/baz.dart'),
+          'package:foo/baz.dart');
     });
 
     test('ignores trailing nulls', () {
@@ -294,7 +306,9 @@
         'd'
       ]), 'http://dartlang.org/c/d');
       expect(context.joinAll(['a', '/b', '/c', 'd']), '/c/d');
-      expect(context.joinAll(['a', r'c:\b', 'c', 'd']), r'a/c:\b/c/d');
+      expect(context.joinAll(['a', r'c:\b', 'c', 'd']), r'c:\b/c/d');
+      expect(context.joinAll(['a', 'package:foo/bar', 'c', 'd']),
+          r'package:foo/bar/c/d');
       expect(context.joinAll(['a', r'\\b', 'c', 'd']), r'a/\\b/c/d');
     });
 
@@ -405,8 +419,9 @@
           'http://dartlang.org/a');
       expect(context.normalize('file:///../../../a'), 'file:///a');
       expect(context.normalize('/../../../a'), '/a');
-      expect(context.normalize('c:/..'), '.');
-      expect(context.normalize('A:/../../..'), '../..');
+      expect(context.normalize('c:/..'), 'c:');
+      expect(context.normalize('package:foo/..'), 'package:foo');
+      expect(context.normalize('A:/../../..'), 'A:');
       expect(context.normalize('a/..'), '.');
       expect(context.normalize('a/b/..'), 'a');
       expect(context.normalize('a/../b'), 'b');
@@ -430,7 +445,8 @@
       expect(context.normalize('a/..'), '.');
       expect(context.normalize('../a'), '../a');
       expect(context.normalize('/../a'), '/a');
-      expect(context.normalize('c:/../a'), 'a');
+      expect(context.normalize('c:/../a'), 'c:/a');
+      expect(context.normalize('package:foo/../a'), 'package:foo/a');
       expect(context.normalize('/../a'), '/a');
       expect(context.normalize('a/b/..'), 'a');
       expect(context.normalize('../a/b/..'), '../a');
@@ -778,8 +794,7 @@
     test('ignores parts before an absolute path', () {
       expect(context.absolute('a', '/b', '/c', 'd'), 'http://dartlang.org/c/d');
       expect(context.absolute('a', '/b', 'file:///c', 'd'), 'file:///c/d');
-      expect(context.absolute('a', r'c:\b', 'c', 'd'),
-          r'http://dartlang.org/root/path/a/c:\b/c/d');
+      expect(context.absolute('a', r'c:\b', 'c', 'd'), r'c:\b/c/d');
       expect(context.absolute('a', r'\\b', 'c', 'd'),
           r'http://dartlang.org/root/path/a/\\b/c/d');
     });