Version 0.6.17.0 .

svn merge -r 25973:25988  https://dart.googlecode.com/svn/branches/bleeding_edge trunk

git-svn-id: http://dart.googlecode.com/svn/trunk@25990 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/pkg/barback/lib/barback.dart b/pkg/barback/lib/barback.dart
index d10827f..a211873 100644
--- a/pkg/barback/lib/barback.dart
+++ b/pkg/barback/lib/barback.dart
@@ -11,4 +11,5 @@
 export 'src/errors.dart';
 export 'src/package_provider.dart';
 export 'src/transform.dart' show Transform;
-export 'src/transformer.dart';
\ No newline at end of file
+export 'src/transform_logger.dart';
+export 'src/transformer.dart';
diff --git a/pkg/barback/lib/src/transform.dart b/pkg/barback/lib/src/transform.dart
index d61e15c..c254778 100644
--- a/pkg/barback/lib/src/transform.dart
+++ b/pkg/barback/lib/src/transform.dart
@@ -11,6 +11,7 @@
 import 'asset_node.dart';
 import 'asset_set.dart';
 import 'errors.dart';
+import 'transform_logger.dart';
 import 'transform_node.dart';
 import 'utils.dart';
 
@@ -45,6 +46,9 @@
   /// would be secondary inputs.
   AssetId get primaryId => _node.primary.id;
 
+  /// A logger so that the [Transformer] can report build details.
+  TransformLogger get logger => _logger;
+
   /// Gets the asset for the primary input.
   Future<Asset> get primaryInput => getInput(primaryId);
 
@@ -65,3 +69,6 @@
     _outputs.add(output);
   }
 }
+
+// TODO(sigmund,rnystrom): create a separate logger for each Transfom.
+final TransformLogger _logger = new TransformLogger(true);
diff --git a/pkg/barback/lib/src/transform_logger.dart b/pkg/barback/lib/src/transform_logger.dart
new file mode 100644
index 0000000..131be29
--- /dev/null
+++ b/pkg/barback/lib/src/transform_logger.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.transform_logger;
+
+import 'package:source_maps/span.dart';
+
+/// Object used to report warnings and errors encountered while running a
+/// transformer.
+class TransformLogger {
+
+  bool _shouldPrint;
+
+  TransformLogger(this._shouldPrint);
+
+  /// Logs a warning message.
+  ///
+  /// If present, [span] indicates the location in the input asset that caused
+  /// the warning.
+  void warning(String message, [Span span]) {
+    _printMessage('warning', message, span);
+  }
+
+  /// Logs an error message.
+  ///
+  /// If present, [span] indicates the location in the input asset that caused
+  /// the error.
+  // TODO(sigmund,nweiz): clarify when an error should be logged or thrown.
+  void error(String message, [Span span]) {
+    _printMessage('error', message, span);
+  }
+
+  // TODO(sigmund,rnystrom): do something better than printing.
+  _printMessage(String prefix, String message, Span span) {
+    if (!_shouldPrint) return;
+    print(span == null ? '$prefix $message'
+        : '$prefix ${span.getLocationMessage(message)}');
+  }
+}
diff --git a/pkg/barback/pubspec.yaml b/pkg/barback/pubspec.yaml
index e0786a8..9cebb0b 100644
--- a/pkg/barback/pubspec.yaml
+++ b/pkg/barback/pubspec.yaml
@@ -13,6 +13,7 @@
   responsiveness.
 dependencies:
   path: any
+  source_maps: any
   stack_trace: any
 dev_dependencies:
   scheduled_test: any
diff --git a/pkg/barback/test/package_graph/errors_test.dart b/pkg/barback/test/package_graph/errors_test.dart
index 973155b..6e7f2fb 100644
--- a/pkg/barback/test/package_graph/errors_test.dart
+++ b/pkg/barback/test/package_graph/errors_test.dart
@@ -38,7 +38,7 @@
     expectAsset("app|foo.c", "foo.c");
     buildShouldSucceed();
 
-    schedule(() => updateSources(["app|foo.b"]));
+    updateSources(["app|foo.b"]);
     buildShouldFail([isAssetCollisionException("app|foo.c")]);
   });
 
@@ -54,7 +54,7 @@
 
   test("reports an error for an unprovided package", () {
     initGraph();
-    expect(() => updateSources(["unknown|foo.txt"]), throwsArgumentError);
+    expect(() => updateSourcesSync(["unknown|foo.txt"]), throwsArgumentError);
   });
 
   test("reports an error for an unprovided source", () {
@@ -69,11 +69,9 @@
       [new ManyToOneTransformer("txt")]
     ]});
 
-    buildShouldFail([isMissingInputException("app|a.inc")]);
-
     updateSources(["app|a.txt"]);
-
     expectNoAsset("app|a.out");
+    buildShouldFail([isMissingInputException("app|a.inc")]);
   });
 
   test("reports an error if a transformer emits an asset for another package",
@@ -82,9 +80,8 @@
       "app": [[new CreateAssetTransformer("wrong|foo.txt")]]
     });
 
-    buildShouldFail([isInvalidOutputException("app", "wrong|foo.txt")]);
-
     updateSources(["app|foo.txt"]);
+    buildShouldFail([isInvalidOutputException("app", "wrong|foo.txt")]);
   });
 
   test("fails if a non-primary input is removed", () {
@@ -101,10 +98,7 @@
     expectAsset("app|a.out", "abc");
     buildShouldSucceed();
 
-    schedule(() {
-      removeSources(["app|b.inc"]);
-    });
-
+    removeSources(["app|b.inc"]);
     buildShouldFail([isMissingInputException("app|b.inc")]);
     expectNoAsset("app|a.out");
   });
@@ -114,12 +108,8 @@
       [new BadTransformer(["app|foo.out"])]
     ]});
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-    });
-
+    updateSources(["app|foo.txt"]);
     expectNoAsset("app|foo.out");
-
     buildShouldFail([equals(BadTransformer.ERROR)]);
   });
 
@@ -128,22 +118,15 @@
       [new BadTransformer(["app|foo.txt"])]
     ]});
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-    });
-
+    updateSources(["app|foo.txt"]);
     expectNoAsset("app|foo.txt");
   });
 
   test("catches errors even if nothing is waiting for process results", () {
     initGraph(["app|foo.txt"], {"app": [[new BadTransformer([])]]});
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-    });
-
+    updateSources(["app|foo.txt"]);
     // Note: No asset requests here.
-
     buildShouldFail([equals(BadTransformer.ERROR)]);
   });
 
@@ -152,10 +135,7 @@
       [new BadTransformer(["a.out", "b.out"])]
     ]});
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-    });
-
+    updateSources(["app|foo.txt"]);
     expectNoAsset("app|a.out");
   });
 
@@ -163,10 +143,7 @@
     initGraph(["pkg1|foo.txt", "pkg2|foo.txt"],
         {"pkg1": [[new BadTransformer([])]]});
 
-    schedule(() {
-      updateSources(["pkg1|foo.txt", "pkg2|foo.txt"]);
-    });
-
+    updateSources(["pkg1|foo.txt", "pkg2|foo.txt"]);
     expectAsset("pkg2|foo.txt", "foo");
     buildShouldFail([equals(BadTransformer.ERROR)]);
   });
@@ -177,10 +154,7 @@
       "pkg2": [[new BadTransformer([])]]
     });
 
-    schedule(() {
-      updateSources(["pkg1|foo.txt", "pkg2|foo.txt"]);
-    });
-
+    updateSources(["pkg1|foo.txt", "pkg2|foo.txt"]);
     buildShouldFail([
       equals(BadTransformer.ERROR),
       equals(BadTransformer.ERROR)
@@ -191,7 +165,7 @@
     initGraph(["app|foo.txt"]);
 
     setAssetError("app|foo.txt");
-    schedule(() => updateSources(["app|foo.txt"]));
+    updateSources(["app|foo.txt"]);
     expectNoAsset("app|foo.txt");
     buildShouldFail([isMockLoadException("app|foo.txt")]);
   });
diff --git a/pkg/barback/test/package_graph/source_test.dart b/pkg/barback/test/package_graph/source_test.dart
index e6627e7..4c47df3 100644
--- a/pkg/barback/test/package_graph/source_test.dart
+++ b/pkg/barback/test/package_graph/source_test.dart
@@ -61,10 +61,7 @@
     expectAsset("app|foo.txt");
     buildShouldSucceed();
 
-    schedule(() {
-      removeSources(["app|foo.txt"]);
-    });
-
+    removeSources(["app|foo.txt"]);
     expectNoAsset("app|foo.txt");
     buildShouldSucceed();
   });
@@ -75,26 +72,24 @@
 
     schedule(() {
       // Make a bunch of synchronous update calls.
-      updateSources(["app|foo.blub"]);
-      updateSources(["app|foo.blub"]);
-      updateSources(["app|foo.blub"]);
-      updateSources(["app|foo.blub"]);
+      updateSourcesSync(["app|foo.blub"]);
+      updateSourcesSync(["app|foo.blub"]);
+      updateSourcesSync(["app|foo.blub"]);
+      updateSourcesSync(["app|foo.blub"]);
     });
 
     expectAsset("app|foo.blab", "foo.blab");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(1));
-    });
+    expect(transformer.numRuns, completion(equals(1)));
   });
 
   test("a removal cancels out an update", () {
     initGraph(["app|foo.txt"]);
 
     schedule(() {
-      updateSources(["app|foo.txt"]);
-      removeSources(["app|foo.txt"]);
+      updateSourcesSync(["app|foo.txt"]);
+      removeSourcesSync(["app|foo.txt"]);
     });
 
     expectNoAsset("app|foo.txt");
@@ -105,8 +100,8 @@
     initGraph(["app|foo.txt"]);
 
     schedule(() {
-      removeSources(["app|foo.txt"]);
-      updateSources(["app|foo.txt"]);
+      removeSourcesSync(["app|foo.txt"]);
+      updateSourcesSync(["app|foo.txt"]);
     });
 
     expectAsset("app|foo.txt");
@@ -117,14 +112,12 @@
     initGraph({"app|foo.txt": "foo"});
 
     pauseProvider();
-    schedule(() {
-      // The mock provider synchronously loads the value of the assets, so this
-      // will kick off two loads with different values. The second one should
-      // win.
-      updateSources(["app|foo.txt"]);
-      modifyAsset("app|foo.txt", "bar");
-      updateSources(["app|foo.txt"]);
-    });
+    // The mock provider synchronously loads the value of the assets, so this
+    // will kick off two loads with different values. The second one should
+    // win.
+    updateSources(["app|foo.txt"]);
+    modifyAsset("app|foo.txt", "bar");
+    updateSources(["app|foo.txt"]);
 
     resumeProvider();
     expectAsset("app|foo.txt", "bar");
@@ -145,23 +138,16 @@
     // Make the provider slow to load a source.
     pauseProvider();
 
-    schedule(() {
-      // Update an asset that doesn't trigger any transformers.
-      updateSources(["app|other.bar"]);
-    });
+    // Update an asset that doesn't trigger any transformers.
+    updateSources(["app|other.bar"]);
 
-    schedule(() {
-      // Now update an asset that does trigger a transformer.
-      updateSources(["app|foo.txt"]);
-    });
+    // Now update an asset that does trigger a transformer.
+    updateSources(["app|foo.txt"]);
 
     resumeProvider();
 
     buildShouldSucceed();
-    waitForBuild();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(2));
-    });
+    expect(transformer.numRuns, completion(equals(2)));
   });
 }
diff --git a/pkg/barback/test/package_graph/transform_test.dart b/pkg/barback/test/package_graph/transform_test.dart
index c413528..c24bae8 100644
--- a/pkg/barback/test/package_graph/transform_test.dart
+++ b/pkg/barback/test/package_graph/transform_test.dart
@@ -95,9 +95,7 @@
     expectAsset("app|foo.b", "foo.b");
     expectAsset("app|foo.c", "foo.c");
     buildShouldSucceed();
-    schedule(() {
-      expect(transformer.numRuns, equals(1));
-    });
+    expect(transformer.numRuns, completion(equals(1)));
   });
 
   test("runs transforms in the same phase in parallel", () {
@@ -108,21 +106,17 @@
     transformerA.pauseApply();
     transformerB.pauseApply();
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
+    updateSources(["app|foo.txt"]);
 
-      // Wait for them both to start.
-      return Future.wait([transformerA.started, transformerB.started]);
-    });
+    transformerA.waitUntilStarted();
+    transformerB.waitUntilStarted();
 
-    schedule(() {
-      // They should both still be running.
-      expect(transformerA.isRunning, isTrue);
-      expect(transformerB.isRunning, isTrue);
+    // They should both still be running.
+    expect(transformerA.isRunning, completion(isTrue));
+    expect(transformerB.isRunning, completion(isTrue));
 
-      transformerA.resumeApply();
-      transformerB.resumeApply();
-    });
+    transformerA.resumeApply();
+    transformerB.resumeApply();
 
     expectAsset("app|foo.a", "foo.a");
     expectAsset("app|foo.b", "foo.b");
@@ -150,39 +144,26 @@
     expectAsset("app|foo.blab", "foo.blab");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(1));
-    });
+    expect(transformer.numRuns, completion(equals(1)));
   });
 
   test("reapplies a transform when its input is modified", () {
     var transformer = new RewriteTransformer("blub", "blab");
     initGraph(["app|foo.blub"], {"app": [[transformer]]});
 
-    schedule(() {
-      updateSources(["app|foo.blub"]);
-    });
-
+    updateSources(["app|foo.blub"]);
     expectAsset("app|foo.blab", "foo.blab");
     buildShouldSucceed();
 
-    schedule(() {
-      updateSources(["app|foo.blub"]);
-    });
-
+    updateSources(["app|foo.blub"]);
     expectAsset("app|foo.blab", "foo.blab");
     buildShouldSucceed();
 
-    schedule(() {
-      updateSources(["app|foo.blub"]);
-    });
-
+    updateSources(["app|foo.blub"]);
     expectAsset("app|foo.blab", "foo.blab");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(3));
-    });
+    expect(transformer.numRuns, completion(equals(3)));
   });
 
   test("does not reapply transform when a removed input is modified", () {
@@ -200,23 +181,18 @@
 
     // Remove the dependency on the non-primary input.
     modifyAsset("app|a.txt", "a.inc");
-    schedule(() => updateSources(["app|a.txt"]));
+    updateSources(["app|a.txt"]);
 
     // Process it again.
     expectAsset("app|a.out", "a");
     buildShouldSucceed();
 
     // Now touch the removed input. It should not trigger another build.
-    schedule(() {
-      updateSources(["app|b.inc"]);
-    });
-
+    updateSources(["app|b.inc"]);
     expectAsset("app|a.out", "a");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(2));
-    });
+    expect(transformer.numRuns, completion(equals(2)));
   });
 
   test("allows a transform to generate multiple outputs", () {
@@ -248,18 +224,13 @@
     expectAsset("app|foo.bbb", "foo.bb.bbb");
     buildShouldSucceed();
 
-    schedule(() {
-      updateSources(["app|foo.a"]);
-    });
-
+    updateSources(["app|foo.a"]);
     expectAsset("app|foo.aaa", "foo.aa.aaa");
     expectAsset("app|foo.bbb", "foo.bb.bbb");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(aa.numRuns, equals(2));
-      expect(bb.numRuns, equals(1));
-    });
+    expect(aa.numRuns, completion(equals(2)));
+    expect(bb.numRuns, completion(equals(1)));
   });
 
   test("doesn't get an output from a transform whose primary input is removed",
@@ -272,10 +243,7 @@
     expectAsset("app|foo.out", "foo.out");
     buildShouldSucceed();
 
-    schedule(() {
-      removeSources(["app|foo.txt"]);
-    });
-
+    removeSources(["app|foo.txt"]);
     expectNoAsset("app|foo.out");
     buildShouldSucceed();
   });
@@ -287,12 +255,10 @@
 
     rewrite.pauseApply();
     updateSources(["app|foo.txt"]);
-    schedule(() => rewrite.started);
-    schedule(() {
-      removeSources(["app|foo.txt"]);
-      rewrite.resumeApply();
-    });
+    rewrite.waitUntilStarted();
 
+    removeSources(["app|foo.txt"]);
+    rewrite.resumeApply();
     expectNoAsset("app|foo.out");
     buildShouldSucceed();
   });
@@ -308,7 +274,7 @@
     buildShouldSucceed();
 
     modifyAsset("app|a.inc", "after");
-    schedule(() => updateSources(["app|a.inc"]));
+    updateSources(["app|a.inc"]);
 
     expectAsset("app|a.out", "after");
     buildShouldSucceed();
@@ -324,7 +290,7 @@
     buildShouldSucceed();
 
     modifyAsset("app|foo.txt", "that");
-    schedule(() => updateSources(["app|foo.txt"]));
+    updateSources(["app|foo.txt"]);
 
     expectAsset("app|foo.txt", "that and the other");
     buildShouldSucceed();
@@ -344,10 +310,8 @@
     schedule(pumpEventQueue);
 
     modifyAsset("app|foo.txt", "second");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      check1.resumeIsPrimary("app|foo.txt");
-    });
+    updateSources(["app|foo.txt"]);
+    check1.resumeIsPrimary("app|foo.txt");
 
     expectAsset("app|foo.txt", "second#2");
     buildShouldSucceed();
@@ -366,12 +330,10 @@
     // Ensure that we're waiting on check1's isPrimary.
     schedule(pumpEventQueue);
 
-    schedule(() => removeSources(["app|foo.txt"]));
+    removeSources(["app|foo.txt"]);
     modifyAsset("app|foo.txt", "second");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      check1.resumeIsPrimary("app|foo.txt");
-    });
+    updateSources(["app|foo.txt"]);
+    check1.resumeIsPrimary("app|foo.txt");
 
     expectAsset("app|foo.txt", "second#2");
     buildShouldSucceed();
@@ -383,25 +345,17 @@
 
     transformer.pauseApply();
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
+    updateSources(["app|foo.txt"]);
+    transformer.waitUntilStarted();
 
-      // Wait for the transform to start.
-      return transformer.started;
-    });
-
-    schedule(() {
-      // Now update the graph during it.
-      updateSources(["app|foo.txt"]);
-      transformer.resumeApply();
-    });
+    // Now update the graph during it.
+    updateSources(["app|foo.txt"]);
+    transformer.resumeApply();
 
     expectAsset("app|foo.out", "foo.out");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(2));
-    });
+    expect(transformer.numRuns, completion(equals(2)));
   });
 
   test("aborts processing if the primary input is removed during processing",
@@ -411,25 +365,17 @@
 
     transformer.pauseApply();
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
+    updateSources(["app|foo.txt"]);
+    transformer.waitUntilStarted();
 
-      // Wait for the transform to start.
-      return transformer.started;
-    });
-
-    schedule(() {
-      // Now remove its primary input while it's running.
-      removeSources(["app|foo.txt"]);
-      transformer.resumeApply();
-    });
+    // Now remove its primary input while it's running.
+    removeSources(["app|foo.txt"]);
+    transformer.resumeApply();
 
     expectNoAsset("app|foo.out");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(1));
-    });
+    expect(transformer.numRuns, completion(equals(1)));
   });
 
   test("restarts processing if a change to a new secondary input occurs during "
@@ -443,27 +389,24 @@
     transformer.pauseApply();
 
     updateSources(["app|foo.txt", "app|bar.inc"]);
-    // Wait for the transform to start.
-    schedule(() => transformer.started);
+    transformer.waitUntilStarted();
 
     // Give the transform time to load bar.inc the first time.
     schedule(pumpEventQueue);
 
     // Now update the secondary input before the transform finishes.
     modifyAsset("app|bar.inc", "baz");
-    schedule(() => updateSources(["app|bar.inc"]));
+    updateSources(["app|bar.inc"]);
     // Give bar.inc enough time to be loaded and marked available before the
     // transformer completes.
     schedule(pumpEventQueue);
 
-    schedule(transformer.resumeApply);
+    transformer.resumeApply();
 
     expectAsset("app|foo.out", "baz");
     buildShouldSucceed();
 
-    schedule(() {
-      expect(transformer.numRuns, equals(2));
-    });
+    expect(transformer.numRuns, completion(equals(2)));
   });
 
   test("doesn't restart processing if a change to an old secondary input "
@@ -479,31 +422,25 @@
     expectAsset("app|foo.out", "bar");
     buildShouldSucceed();
 
-    schedule(transformer.pauseApply);
+    transformer.pauseApply();
     modifyAsset("app|foo.txt", "baz.inc");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      // Wait for the transform to start.
-      return transformer.started;
-    });
+    updateSources(["app|foo.txt"]);
+    transformer.waitUntilStarted();
 
     // Now update the old secondary input before the transform finishes.
     modifyAsset("app|bar.inc", "new bar");
-    schedule(() => updateSources(["app|bar.inc"]));
+    updateSources(["app|bar.inc"]);
     // Give bar.inc enough time to be loaded and marked available before the
     // transformer completes.
     schedule(pumpEventQueue);
 
-    schedule(transformer.resumeApply);
-
+    transformer.resumeApply();
     expectAsset("app|foo.out", "baz");
     buildShouldSucceed();
 
-    schedule(() {
-      // Should have run once the first time, then again when switching to
-      // baz.inc. Should not run a third time because of bar.inc being modified.
-      expect(transformer.numRuns, equals(2));
-    });
+    // Should have run once the first time, then again when switching to
+    // baz.inc. Should not run a third time because of bar.inc being modified.
+    expect(transformer.numRuns, completion(equals(2)));
   });
 
   test("handles an output moving from one transformer to another", () {
@@ -526,7 +463,7 @@
     // transformer.
     modifyAsset("app|a.a", "a.out");
     modifyAsset("app|b.b", "b.out,shared.out");
-    schedule(() => updateSources(["app|a.a", "app|b.b"]));
+    updateSources(["app|a.a", "app|b.b"]);
 
     expectAsset("app|a.out", "spread a");
     expectAsset("app|b.out", "spread b");
@@ -542,31 +479,20 @@
 
     txtToInt.pauseApply();
 
-    schedule(() {
-      updateSources(["app|foo.txt"]);
+    updateSources(["app|foo.txt"]);
+    txtToInt.waitUntilStarted();
 
-      // Wait for the first transform to start.
-      return txtToInt.started;
-    });
-
-    schedule(() {
-      // Now update the graph during it.
-      updateSources(["app|bar.txt"]);
-    });
-
-    schedule(() {
-      txtToInt.resumeApply();
-    });
+    // Now update the graph during it.
+    updateSources(["app|bar.txt"]);
+    txtToInt.resumeApply();
 
     expectAsset("app|foo.out", "foo.int.out");
     expectAsset("app|bar.out", "bar.int.out");
     buildShouldSucceed();
 
-    schedule(() {
-      // Should only have run each transform once for each primary.
-      expect(txtToInt.numRuns, equals(2));
-      expect(intToOut.numRuns, equals(2));
-    });
+    // Should only have run each transform once for each primary.
+    expect(txtToInt.numRuns, completion(equals(2)));
+    expect(intToOut.numRuns, completion(equals(2)));
   });
 
   test("applies transforms to the correct packages", () {
@@ -606,7 +532,7 @@
 
     pauseProvider();
     modifyAsset("app|foo.in", "new");
-    schedule(() => updateSources(["app|foo.in"]));
+    updateSources(["app|foo.in"]);
     expectAssetDoesNotComplete("app|foo.out");
     buildShouldNotBeDone();
 
@@ -623,7 +549,7 @@
     updateSources(["app|foo.txt"]);
     expectAssetDoesNotComplete("app|foo.txt");
 
-    schedule(rewrite.resumeApply);
+    rewrite.resumeApply();
     expectAsset("app|foo.txt", "foo.txt");
     buildShouldSucceed();
   });
@@ -637,7 +563,7 @@
     updateSources(["app|foo.a"]);
     expectAssetDoesNotComplete("app|foo.a");
 
-    schedule(() => rewrite.resumeIsPrimary("app|foo.a"));
+    rewrite.resumeIsPrimary("app|foo.a");
     expectAsset("app|foo.a", "foo");
     buildShouldSucceed();
   });
@@ -651,11 +577,11 @@
     expectAsset("app|foo.txt", "foo.txt");
     buildShouldSucceed();
 
-    schedule(() => rewrite.pauseIsPrimary("app|foo.txt"));
-    schedule(() => updateSources(["app|foo.txt"]));
+    rewrite.pauseIsPrimary("app|foo.txt");
+    updateSources(["app|foo.txt"]);
     expectAssetDoesNotComplete("app|foo.txt");
 
-    schedule(() => rewrite.resumeIsPrimary("app|foo.txt"));
+    rewrite.resumeIsPrimary("app|foo.txt");
     expectAsset("app|foo.txt", "foo.txt");
     buildShouldSucceed();
   });
@@ -669,10 +595,8 @@
     // Make sure we're waiting on isPrimary.
     schedule(pumpEventQueue);
 
-    schedule(() {
-      removeSources(["app|foo.txt"]);
-      rewrite.resumeIsPrimary("app|foo.txt");
-    });
+    removeSources(["app|foo.txt"]);
+    rewrite.resumeIsPrimary("app|foo.txt");
     expectNoAsset("app|foo.txt");
     buildShouldSucceed();
   });
@@ -690,10 +614,8 @@
     schedule(pumpEventQueue);
 
     modifyAsset("app|foo.txt", "don't");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      check.resumeIsPrimary("app|foo.txt");
-    });
+    updateSources(["app|foo.txt"]);
+    check.resumeIsPrimary("app|foo.txt");
 
     expectAsset("app|foo.txt", "don't");
     buildShouldSucceed();
@@ -712,10 +634,8 @@
     schedule(pumpEventQueue);
 
     modifyAsset("app|foo.txt", "do");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      check.resumeIsPrimary("app|foo.txt");
-    });
+    updateSources(["app|foo.txt"]);
+    check.resumeIsPrimary("app|foo.txt");
 
     expectAsset("app|foo.txt", "done");
     buildShouldSucceed();
@@ -732,10 +652,8 @@
     // Make sure we're waiting on the correct isPrimary.
     schedule(pumpEventQueue);
 
-    schedule(() {
-      removeSources(["app|foo.txt"]);
-      rewrite2.resumeIsPrimary("app|foo.md");
-    });
+    removeSources(["app|foo.txt"]);
+    rewrite2.resumeIsPrimary("app|foo.md");
     expectNoAsset("app|foo.txt");
     expectAsset("app|foo.md", "foo.md");
     buildShouldSucceed();
@@ -756,10 +674,8 @@
     schedule(pumpEventQueue);
 
     modifyAsset("app|foo.txt", "don't");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      rewrite.resumeIsPrimary("app|foo.md");
-    });
+    updateSources(["app|foo.txt"]);
+    rewrite.resumeIsPrimary("app|foo.md");
 
     expectAsset("app|foo.txt", "don't");
     expectAsset("app|foo.md", "foo.md");
@@ -781,10 +697,8 @@
     schedule(pumpEventQueue);
 
     modifyAsset("app|foo.txt", "do");
-    schedule(() {
-      updateSources(["app|foo.txt"]);
-      rewrite.resumeIsPrimary("app|foo.md");
-    });
+    updateSources(["app|foo.txt"]);
+    rewrite.resumeIsPrimary("app|foo.md");
 
     expectAsset("app|foo.txt", "done");
     expectAsset("app|foo.md", "foo.md");
@@ -802,7 +716,7 @@
     expectAsset("app|foo.out", "foo.mid.out");
     buildShouldSucceed();
 
-    schedule(() => removeSources(["app|foo.txt"]));
+    removeSources(["app|foo.txt"]);
     expectNoAsset("app|foo.out");
     buildShouldSucceed();
   });
@@ -819,7 +733,7 @@
     buildShouldSucceed();
 
     modifyAsset("app|foo.txt", "bar.mid");
-    schedule(() => updateSources(["app|foo.txt"]));
+    updateSources(["app|foo.txt"]);
     expectNoAsset('app|foo.out');
     expectAsset('app|bar.out', 'spread txt.out');
     buildShouldSucceed();
@@ -838,7 +752,7 @@
 
     pauseProvider();
     modifyAsset("app|foo.in", "new");
-    schedule(() => updateSources(["app|foo.in"]));
+    updateSources(["app|foo.in"]);
     expectAssetDoesNotComplete("app|foo.out");
     expectAsset("app|bar.out", "bar.out");
     buildShouldNotBeDone();
@@ -858,7 +772,7 @@
     buildShouldSucceed();
 
     pauseProvider();
-    schedule(() => updateSources(["app|foo.in"]));
+    updateSources(["app|foo.in"]);
     expectAssetDoesNotComplete("app|foo.out");
     expectAssetDoesNotComplete("app|non-existent.out");
     buildShouldNotBeDone();
@@ -885,7 +799,7 @@
     // pkg1 is still successful, but pkg2 is waiting on the provider, so the
     // overall build shouldn't finish.
     pauseProvider();
-    schedule(() => updateSources(["pkg2|foo.txt"]));
+    updateSources(["pkg2|foo.txt"]);
     expectAsset("pkg1|foo.out", "foo.out");
     buildShouldNotBeDone();
 
@@ -934,7 +848,7 @@
       buildShouldSucceed();
 
       modifyAsset("pkg2|a.inc", "new a");
-      schedule(() => updateSources(["pkg2|a.inc"]));
+      updateSources(["pkg2|a.inc"]);
       expectAsset("pkg1|a.out", "new a");
       buildShouldSucceed();
     });
@@ -954,7 +868,7 @@
       buildShouldSucceed();
 
       modifyAsset("pkg2|a.txt", "new a");
-      schedule(() => updateSources(["pkg2|a.txt"]));
+      updateSources(["pkg2|a.txt"]);
       expectAsset("pkg1|a.out", "new a.inc");
       buildShouldSucceed();
     });
@@ -982,7 +896,7 @@
       buildShouldSucceed();
 
       modifyAsset("pkg2|a.inc", "b,c.md");
-      schedule(() => updateSources(["pkg2|a.inc"]));
+      updateSources(["pkg2|a.inc"]);
       expectAsset("pkg1|b", "spread out");
       expectAsset("pkg1|c.done", "spread out.done");
       buildShouldSucceed();
@@ -1007,7 +921,7 @@
       buildShouldSucceed();
 
       modifyAsset("pkg2|a.inc", "b");
-      schedule(() => updateSources(["pkg2|a.inc"]));
+      updateSources(["pkg2|a.inc"]);
       expectAsset("pkg1|b", "spread out");
       expectNoAsset("pkg1|c.done");
       buildShouldSucceed();
diff --git a/pkg/barback/test/transformer/mock.dart b/pkg/barback/test/transformer/mock.dart
index b831cf7..0887002 100644
--- a/pkg/barback/test/transformer/mock.dart
+++ b/pkg/barback/test/transformer/mock.dart
@@ -8,6 +8,7 @@
 
 import 'package:barback/barback.dart';
 import 'package:barback/src/utils.dart';
+import 'package:scheduled_test/scheduled_test.dart';
 
 /// The abstract base class for transformers used to test barback.
 ///
@@ -20,7 +21,10 @@
 /// [getPrimary] rather than [transform.getInput] and [transform.primaryInput].
 abstract class MockTransformer extends Transformer {
   /// The number of times the transformer has been applied.
-  int get numRuns => _numRuns;
+  ///
+  /// This is scheduled. The Future will complete at the point in the schedule
+  /// that this is called.
+  Future<int> get numRuns => schedule(() => _numRuns);
   var _numRuns = 0;
 
   /// The number of currently running transforms.
@@ -37,56 +41,83 @@
   // the [Transform].
   final _getInput = new Map<AssetId, Completer>();
 
-  /// A future that completes when this transformer begins running.
+  /// A completer that completes once this transformer begins running.
   ///
   /// Once this transformer finishes running, this is reset to a new completer,
   /// so it can be used multiple times.
-  Future get started => _started.future;
   var _started = new Completer();
 
   /// `true` if any transforms are currently running.
-  bool get isRunning => _runningTransforms > 0;
+  ///
+  /// This is scheduled. The Future will complete at the point in the schedule
+  /// that this is called.
+  Future<bool> get isRunning => schedule(() => _runningTransforms > 0);
+
+  /// Pauses the schedule until this transformer begins running.
+  void waitUntilStarted() {
+    schedule(() => _started.future, "wait until $this starts");
+  }
 
   /// Causes the transformer to pause after running [apply] but before the
   /// returned Future completes.
   ///
-  /// This can be resumed by calling [resumeApply].
+  /// This can be resumed by calling [resumeApply]. This operation is scheduled.
   void pauseApply() {
-    _apply = new Completer();
+    schedule(() {
+      _apply = new Completer();
+    }, "pause apply for $this");
   }
 
   /// Resumes the transformer's [apply] call after [pauseApply] was called.
+  ///
+  /// This operation is scheduled.
   void resumeApply() {
-    _apply.complete();
-    _apply = null;
+    schedule(() {
+      _apply.complete();
+      _apply = null;
+    }, "resume apply for $this");
   }
 
   /// Causes the transformer to pause after running [isPrimary] on the asset
   /// with the given [name], but before the returned Future completes.
   ///
-  /// This can be resumed by calling [resumeIsPrimary].
+  /// This can be resumed by calling [resumeIsPrimary]. This operation is
+  /// scheduled.
   void pauseIsPrimary(String name) {
-    _isPrimary[new AssetId.parse(name)] = new Completer();
+    schedule(() {
+      _isPrimary[new AssetId.parse(name)] = new Completer();
+    }, "pause isPrimary($name) for $this");
   }
 
   /// Resumes the transformer's [isPrimary] call on the asset with the given
   /// [name] after [pauseIsPrimary] was called.
+  ///
+  /// This operation is scheduled.
   void resumeIsPrimary(String name) {
-    _isPrimary.remove(new AssetId.parse(name)).complete();
+    schedule(() {
+      _isPrimary.remove(new AssetId.parse(name)).complete();
+    }, "resume isPrimary($name) for $this");
   }
 
   /// Causes the transformer to pause while loading the input with the given
   /// [name]. This can be the primary input or a secondary input.
   ///
-  /// This can be resumed by calling [resumeGetInput].
+  /// This can be resumed by calling [resumeGetInput]. This operation is
+  /// scheduled.
   void pauseGetInput(String name) {
-    _getInput[new AssetId.parse(name)] = new Completer();
+    schedule(() {
+      _getInput[new AssetId.parse(name)] = new Completer();
+    }, "pause getInput($name) for $this");
   }
 
   /// Resumes the transformer's loading of the input with the given [name] after
   /// [pauseGetInput] was called.
+  ///
+  /// This operation is scheduled.
   void resumeGetInput(String name) {
-    _getInput.remove(new AssetId.parse(name)).complete();
+    schedule(() {
+      _getInput.remove(new AssetId.parse(name)).complete();
+    }, "resume getInput($name) for $this");
   }
 
   /// Like [Transform.getInput], but respects [pauseGetInput].
diff --git a/pkg/barback/test/utils.dart b/pkg/barback/test/utils.dart
index 57d9c43..1de4ac9 100644
--- a/pkg/barback/test/utils.dart
+++ b/pkg/barback/test/utils.dart
@@ -69,29 +69,45 @@
 /// Updates [assets] in the current [PackageProvider].
 ///
 /// Each item in the list may either be an [AssetId] or a string that can be
-/// parsed as one. Note that this method is not automatically scheduled.
+/// parsed as one.
 void updateSources(Iterable assets) {
-  // Allow strings as asset IDs.
-  assets = assets.map((asset) {
-    if (asset is String) return new AssetId.parse(asset);
-    return asset;
-  });
+  assets = _parseAssets(assets);
+  schedule(() => _barback.updateSources(assets),
+      "updating ${assets.join(', ')}");
+}
 
-  _barback.updateSources(assets);
+/// Updates [assets] in the current [PackageProvider].
+///
+/// Each item in the list may either be an [AssetId] or a string that can be
+/// parsed as one. Unlike [updateSources], this is not automatically scheduled
+/// and will be run synchronously when called.
+void updateSourcesSync(Iterable assets) =>
+    _barback.updateSources(_parseAssets(assets));
+
+/// Removes [assets] from the current [PackageProvider].
+///
+/// Each item in the list may either be an [AssetId] or a string that can be
+/// parsed as one.
+void removeSources(Iterable assets) {
+  assets = _parseAssets(assets);
+  schedule(() => _barback.removeSources(assets),
+      "removing ${assets.join(', ')}");
 }
 
 /// Removes [assets] from the current [PackageProvider].
 ///
 /// Each item in the list may either be an [AssetId] or a string that can be
-/// parsed as one. Note that this method is not automatically scheduled.
-void removeSources(Iterable assets) {
-  // Allow strings as asset IDs.
-  assets = assets.map((asset) {
+/// parsed as one. Unlike [removeSources], this is not automatically scheduled
+/// and will be run synchronously when called.
+void removeSourcesSync(Iterable assets) =>
+    _barback.removeSources(_parseAssets(assets));
+
+/// Parse a list of strings or [AssetId]s into a list of [AssetId]s.
+List<AssetId> _parseAssets(Iterable assets) {
+  return assets.map((asset) {
     if (asset is String) return new AssetId.parse(asset);
     return asset;
-  });
-
-  _barback.removeSources(assets);
+  }).toList();
 }
 
 /// Schedules a change to the contents of an asset identified by [name] to
@@ -143,7 +159,7 @@
 
 /// Expects that the next [BuildResult] is a build success.
 void buildShouldSucceed() {
-  expect(_getNextBuildResult().then((result) {
+  expect(_getNextBuildResult("build should succeed").then((result) {
     result.errors.forEach(currentSchedule.signalError);
     expect(result.succeeded, isTrue);
   }), completes);
@@ -155,7 +171,7 @@
 /// build to fail. Every matcher is expected to match an error, but the order of
 /// matchers is unimportant.
 void buildShouldFail(List matchers) {
-  expect(_getNextBuildResult().then((result) {
+  expect(_getNextBuildResult("build should fail").then((result) {
     expect(result.succeeded, isFalse);
     expect(result.errors.length, equals(matchers.length));
     for (var matcher in matchers) {
@@ -164,18 +180,10 @@
   }), completes);
 }
 
-Future<BuildResult> _getNextBuildResult() =>
-  _barback.results.elementAt(_nextBuildResult++);
-
-/// Pauses the schedule until the currently running build completes.
-///
-/// Validates that the build completed successfully.
-void waitForBuild() {
-  schedule(() {
-    return _barback.results.first.then((result) {
-      expect(result.succeeded, isTrue);
-    });
-  }, "wait for build");
+Future<BuildResult> _getNextBuildResult(String description) {
+  var result = currentSchedule.wrapFuture(
+      _barback.results.elementAt(_nextBuildResult++));
+  return schedule(() => result, description);
 }
 
 /// Schedules an expectation that the graph will deliver an asset matching
diff --git a/pkg/custom_element/lib/custom_element.dart b/pkg/custom_element/lib/custom_element.dart
index aa540e5..010b392 100644
--- a/pkg/custom_element/lib/custom_element.dart
+++ b/pkg/custom_element/lib/custom_element.dart
@@ -397,6 +397,9 @@
 
   bool matches(String selectors) => host.matches(selectors);
 
+  bool matchesWithAncestors(String selectors) =>
+      host.matchesWithAncestors(selectors);
+
   @deprecated
   void requestFullScreen(int flags) { requestFullscreen(); }
 
diff --git a/pkg/observe/lib/transform.dart b/pkg/observe/lib/transform.dart
new file mode 100644
index 0000000..99cb13b
--- /dev/null
+++ b/pkg/observe/lib/transform.dart
@@ -0,0 +1,338 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Code transform for @observable. The core transformation is relatively
+ * straightforward, and essentially like an editor refactoring.
+ */
+library observe.transform;
+
+import 'dart:async';
+import 'package:path/path.dart' as path;
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:analyzer_experimental/src/generated/error.dart';
+import 'package:analyzer_experimental/src/generated/parser.dart';
+import 'package:analyzer_experimental/src/generated/scanner.dart';
+import 'package:barback/barback.dart';
+import 'package:source_maps/refactor.dart';
+import 'package:source_maps/span.dart' show SourceFile;
+
+/**
+ * A [Transformer] that replaces observables based on dirty-checking with an
+ * implementation based on change notifications. 
+ *
+ * The transformation adds hooks for field setters and notifies the observation
+ * system of the change.
+ */
+class ObservableTransformer extends Transformer {
+
+  Future<bool> isPrimary(Asset input) {
+    if (input.id.extension != '.dart') return new Future.value(false);
+    // Note: technically we should parse the file to find accurately the
+    // observable annotation, but that seems expensive. It would require almost
+    // as much work as applying the transform. We rather have some false
+    // positives here, and then generate no outputs when we apply this
+    // transform.
+    return input.readAsString().then((c) => c.contains("@observable"));
+  }
+
+  Future apply(Transform transform) {
+    return transform.primaryInput
+        .then((input) => input.readAsString().then((content) {
+      var id = transform.primaryId;
+      // TODO(sigmund): improve how we compute this url
+      var url = id.path.startsWith('lib/')
+            ? 'package:${id.package}/${id.path.substring(4)}' : id.path;
+      var sourceFile = new SourceFile.text(url, content);
+      var transaction = _transformCompilationUnit(
+          content, sourceFile, transform.logger);
+      if (!transaction.hasEdits) {
+        transform.addOutput(input);
+        return;
+      }
+      var printer = transaction.commit();
+      // TODO(sigmund): emit source maps when barback supports it (see
+      // dartbug.com/12340)
+      printer.build(url);
+      transform.addOutput(new Asset.fromString(id, printer.text));
+    }));
+  }
+}
+
+TextEditTransaction _transformCompilationUnit(
+    String inputCode, SourceFile sourceFile, TransformLogger logger) {
+  var unit = _parseCompilationUnit(inputCode);
+  return transformObservables(unit, sourceFile, inputCode, logger);
+}
+
+// TODO(sigmund): make this private. This is currently public so it can be used
+// by the polymer.dart package which is not yet entirely migrated to use
+// pub-serve and pub-deploy.
+TextEditTransaction transformObservables(CompilationUnit unit,
+    SourceFile sourceFile, String content, TransformLogger logger) {
+  var code = new TextEditTransaction(content, sourceFile);
+  for (var directive in unit.directives) {
+    if (directive is LibraryDirective && _hasObservable(directive)) {
+      logger.warning('@observable on a library no longer has any effect. '
+          'It should be placed on individual fields.',
+          _getSpan(sourceFile, directive));
+      break;
+    }
+  }
+
+  for (var declaration in unit.declarations) {
+    if (declaration is ClassDeclaration) {
+      _transformClass(declaration, code, sourceFile, logger);
+    } else if (declaration is TopLevelVariableDeclaration) {
+      if (_hasObservable(declaration)) {
+        logger.warning('Top-level fields can no longer be observable. '
+            'Observable fields should be put in an observable objects.',
+            _getSpan(sourceFile, declaration));
+      }
+    }
+  }
+  return code;
+}
+
+/** Parse [code] using analyzer_experimental. */
+CompilationUnit _parseCompilationUnit(String code) {
+  var errorListener = new _ErrorCollector();
+  var scanner = new StringScanner(null, code, errorListener);
+  var token = scanner.tokenize();
+  var parser = new Parser(null, errorListener);
+  return parser.parseCompilationUnit(token);
+}
+
+class _ErrorCollector extends AnalysisErrorListener {
+  final errors = <AnalysisError>[];
+  onError(error) => errors.add(error);
+}
+
+_getSpan(SourceFile file, ASTNode node) => file.span(node.offset, node.end);
+
+/** True if the node has the `@observable` annotation. */
+bool _hasObservable(AnnotatedNode node) => _hasAnnotation(node, 'observable');
+
+bool _hasAnnotation(AnnotatedNode node, String name) {
+  // TODO(jmesserly): this isn't correct if the annotation has been imported
+  // with a prefix, or cases like that. We should technically be resolving, but
+  // that is expensive.
+  return node.metadata.any((m) => m.name.name == name &&
+      m.constructorName == null && m.arguments == null);
+}
+
+void _transformClass(ClassDeclaration cls, TextEditTransaction code,
+    SourceFile file, TransformLogger logger) {
+
+  if (_hasObservable(cls)) {
+    logger.warning('@observable on a class no longer has any effect. '
+        'It should be placed on individual fields.',
+        _getSpan(file, cls));
+  }
+
+  // We'd like to track whether observable was declared explicitly, otherwise
+  // report a warning later below. Because we don't have type analysis (only
+  // syntactic understanding of the code), we only report warnings that are
+  // known to be true.
+  var declaresObservable = false;
+  if (cls.extendsClause != null) {
+    var id = _getSimpleIdentifier(cls.extendsClause.superclass.name);
+    if (id.name == 'ObservableBase') {
+      code.edit(id.offset, id.end, 'ChangeNotifierBase');
+      declaresObservable = true;
+    } else if (id.name == 'ChangeNotifierBase') {
+      declaresObservable = true;
+    } else if (id.name != 'PolymerElement' && id.name != 'CustomElement'
+        && id.name != 'Object') {
+      // TODO(sigmund): this is conservative, consider using type-resolution to
+      // improve this check.
+      declaresObservable = true;
+    }
+  }
+
+  if (cls.withClause != null) {
+    for (var type in cls.withClause.mixinTypes) {
+      var id = _getSimpleIdentifier(type.name);
+      if (id.name == 'ObservableMixin') {
+        code.edit(id.offset, id.end, 'ChangeNotifierMixin');
+        declaresObservable = true;
+        break;
+      } else if (id.name == 'ChangeNotifierMixin') {
+        declaresObservable = true;
+        break;
+      } else {
+        // TODO(sigmund): this is conservative, consider using type-resolution
+        // to improve this check.
+        declaresObservable = true;
+      }
+    }
+  }
+
+  if (!declaresObservable && cls.implementsClause != null) {
+    // TODO(sigmund): consider adding type-resolution to give a more precise
+    // answer.
+    declaresObservable = true;
+  }
+
+  // Track fields that were transformed.
+  var instanceFields = new Set<String>();
+  var getters = new List<String>();
+  var setters = new List<String>();
+
+  for (var member in cls.members) {
+    if (member is FieldDeclaration) {
+      bool isStatic = _hasKeyword(member.keyword, Keyword.STATIC);
+      if (isStatic) {
+        if (_hasObservable(member)){
+          logger.warning('Static fields can no longer be observable. '
+              'Observable fields should be put in an observable objects.',
+              _getSpan(file, member));
+        }
+        continue;
+      }
+      if (_hasObservable(member)) {
+        if (!declaresObservable) {
+          logger.warning('Observable fields should be put in an observable'
+              ' objects. Please declare that this class extends from '
+              'ObservableBase, includes ObservableMixin, or implements '
+              'Observable.',
+              _getSpan(file, member));
+
+        }
+        _transformFields(member.fields, code, member.offset, member.end);
+
+        var names = member.fields.variables.map((v) => v.name.name);
+
+        getters.addAll(names);
+        if (!_isReadOnly(member.fields)) {
+          setters.addAll(names);
+          instanceFields.addAll(names);
+        }
+      }
+    }
+    // TODO(jmesserly): this is a temporary workaround until we can remove
+    // getValueWorkaround and setValueWorkaround.
+    if (member is MethodDeclaration) {
+      if (_hasKeyword(member.propertyKeyword, Keyword.GET)) {
+        getters.add(member.name.name);
+      } else if (_hasKeyword(member.propertyKeyword, Keyword.SET)) {
+        setters.add(member.name.name);
+      }
+    }
+  }
+
+  // If nothing was @observable, bail.
+  if (instanceFields.length == 0) return;
+
+  // Fix initializers, because they aren't allowed to call the setter.
+  for (var member in cls.members) {
+    if (member is ConstructorDeclaration) {
+      _fixConstructor(member, code, instanceFields);
+    }
+  }
+}
+
+SimpleIdentifier _getSimpleIdentifier(Identifier id) =>
+    id is PrefixedIdentifier ? (id as PrefixedIdentifier).identifier : id;
+
+
+bool _hasKeyword(Token token, Keyword keyword) =>
+    token is KeywordToken && (token as KeywordToken).keyword == keyword;
+
+String _getOriginalCode(TextEditTransaction code, ASTNode node) =>
+    code.original.substring(node.offset, node.end);
+
+void _fixConstructor(ConstructorDeclaration ctor, TextEditTransaction code,
+    Set<String> changedFields) {
+
+  // Fix normal initializers
+  for (var initializer in ctor.initializers) {
+    if (initializer is ConstructorFieldInitializer) {
+      var field = initializer.fieldName;
+      if (changedFields.contains(field.name)) {
+        code.edit(field.offset, field.end, '__\$${field.name}');
+      }
+    }
+  }
+
+  // Fix "this." initializer in parameter list. These are tricky:
+  // we need to preserve the name and add an initializer.
+  // Preserving the name is important for named args, and for dartdoc.
+  // BEFORE: Foo(this.bar, this.baz) { ... }
+  // AFTER:  Foo(bar, baz) : __$bar = bar, __$baz = baz { ... }
+
+  var thisInit = [];
+  for (var param in ctor.parameters.parameters) {
+    if (param is DefaultFormalParameter) {
+      param = param.parameter;
+    }
+    if (param is FieldFormalParameter) {
+      var name = param.identifier.name;
+      if (changedFields.contains(name)) {
+        thisInit.add(name);
+        // Remove "this." but keep everything else.
+        code.edit(param.thisToken.offset, param.period.end, '');
+      }
+    }
+  }
+
+  if (thisInit.length == 0) return;
+
+  // TODO(jmesserly): smarter formatting with indent, etc.
+  var inserted = thisInit.map((i) => '__\$$i = $i').join(', ');
+
+  int offset;
+  if (ctor.separator != null) {
+    offset = ctor.separator.end;
+    inserted = ' $inserted,';
+  } else {
+    offset = ctor.parameters.end;
+    inserted = ' : $inserted';
+  }
+
+  code.edit(offset, offset, inserted);
+}
+
+bool _isReadOnly(VariableDeclarationList fields) {
+  return _hasKeyword(fields.keyword, Keyword.CONST) ||
+      _hasKeyword(fields.keyword, Keyword.FINAL);
+}
+
+void _transformFields(VariableDeclarationList fields, TextEditTransaction code,
+    int begin, int end) {
+
+  if (_isReadOnly(fields)) return;
+
+  var indent = guessIndent(code.original, begin);
+  var replace = new StringBuffer();
+
+  // Unfortunately "var" doesn't work in all positions where type annotations
+  // are allowed, such as "var get name". So we use "dynamic" instead.
+  var type = 'dynamic';
+  if (fields.type != null) {
+    type = _getOriginalCode(code, fields.type);
+  }
+
+  for (var field in fields.variables) {
+    var initializer = '';
+    if (field.initializer != null) {
+      initializer = ' = ${_getOriginalCode(code, field.initializer)}';
+    }
+
+    var name = field.name.name;
+
+    // TODO(jmesserly): should we generate this one one line, so source maps
+    // don't break?
+    if (replace.length > 0) replace.write('\n\n$indent');
+    replace.write('''
+$type __\$$name$initializer;
+$type get $name => __\$$name;
+set $name($type value) {
+  __\$$name = notifyPropertyChange(const Symbol('$name'), __\$$name, value);
+}
+'''.replaceAll('\n', '\n$indent'));
+  }
+
+  code.edit(begin, end, '$replace');
+}
diff --git a/pkg/observe/pubspec.yaml b/pkg/observe/pubspec.yaml
index 77f2bc4..7324aef 100644
--- a/pkg/observe/pubspec.yaml
+++ b/pkg/observe/pubspec.yaml
@@ -8,6 +8,10 @@
   immediately assigned to the model.
 homepage: https://github.com/dart-lang/web-ui
 dependencies:
+  analyzer_experimental: any
+  barback: any
   logging: any
+  path: any
+  source_maps: any
 dev_dependencies:
   unittest: any
diff --git a/pkg/observe/test/transform_test.dart b/pkg/observe/test/transform_test.dart
new file mode 100644
index 0000000..13b67cd
--- /dev/null
+++ b/pkg/observe/test/transform_test.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'package:barback/barback.dart';
+import 'package:observe/transform.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+main() {
+  useCompactVMConfiguration();
+
+  group('replaces Observable for ChangeNotifier', () {
+    _testClause('extends ObservableBase', 'extends ChangeNotifierBase');
+    _testClause('extends Base with ObservableMixin',
+        'extends Base with ChangeNotifierMixin');
+    _testClause('extends Base<T> with ObservableMixin',
+        'extends Base<T> with ChangeNotifierMixin');
+    _testClause('extends Base with Mixin, ObservableMixin',
+        'extends Base with Mixin, ChangeNotifierMixin');
+    _testClause('extends Base with ObservableMixin, Mixin',
+        'extends Base with ChangeNotifierMixin, Mixin');
+    _testClause('extends Base with Mixin<T>, ObservableMixin',
+        'extends Base with Mixin<T>, ChangeNotifierMixin');
+    _testClause('extends Base with Mixin, ObservableMixin, Mixin2',
+        'extends Base with Mixin, ChangeNotifierMixin, Mixin2');
+    _testClause('extends ObservableBase implements Interface',
+        'extends ChangeNotifierBase implements Interface');
+    _testClause('extends ObservableBase implements Interface<T>',
+        'extends ChangeNotifierBase implements Interface<T>');
+    _testClause('extends Base with ObservableMixin implements Interface',
+        'extends Base with ChangeNotifierMixin implements Interface');
+    _testClause(
+        'extends Base with Mixin, ObservableMixin implements I1, I2',
+        'extends Base with Mixin, ChangeNotifierMixin implements I1, I2');
+  });
+
+  group('fixes contructor calls ', () {
+    _testInitializers('this.a', '(a) : __\$a = a');
+    _testInitializers('{this.a}', '({a}) : __\$a = a');
+    _testInitializers('[this.a]', '([a]) : __\$a = a');
+    _testInitializers('this.a, this.b', '(a, b) : __\$a = a, __\$b = b');
+    _testInitializers('{this.a, this.b}', '({a, b}) : __\$a = a, __\$b = b');
+    _testInitializers('[this.a, this.b]', '([a, b]) : __\$a = a, __\$b = b');
+    _testInitializers('this.a, [this.b]', '(a, [b]) : __\$a = a, __\$b = b');
+    _testInitializers('this.a, {this.b}', '(a, {b}) : __\$a = a, __\$b = b');
+  });
+}
+
+_testClause(String clauses, String expected) {
+  test(clauses, () {
+    var className = 'MyClass';
+    if (clauses.contains('<T>')) className += '<T>';
+    var code = '''
+      class $className $clauses {
+        @observable var field;
+      }''';
+
+    return _transform(code).then((output) {
+      var classPos = output.indexOf(className) + className.length;
+      var actualClauses = output.substring(classPos,
+        output.indexOf('{')).trim().replaceAll('  ', ' ');
+      expect(actualClauses, expected);
+    });
+  });
+}
+
+_testInitializers(String args, String expected) {
+  test(args, () {
+    var constructor = 'MyClass(';
+    var code = '''
+        class MyClass {
+          @observable var a;
+          @observable var b;
+          MyClass($args);
+        }''';
+
+    return _transform(code).then((output) {
+      var begin = output.indexOf(constructor) + constructor.length - 1;
+      var end = output.indexOf(';', begin);
+      if (end == -1) end = output.length;
+      var init = output.substring(begin, end).trim().replaceAll('  ', ' ');
+      expect(init, expected);
+    });
+  });
+}
+
+/** Helper that applies the transform by creating mock assets. */
+Future<String> _transform(String code) {
+  var id = new AssetId('foo', 'a/b/c.dart');
+  var asset = new Asset.fromString(id, code);
+  var transformer = new ObservableTransformer();
+  return transformer.isPrimary(asset).then((isPrimary) {
+    expect(isPrimary, isTrue);
+    var transform = new _MockTransform(asset);
+    return transformer.apply(transform).then((_) {
+      expect(transform.outs, hasLength(1));
+      expect(transform.outs[0].id, id);
+      return transform.outs.first.readAsString();
+    });
+  });
+}
+
+class _MockTransform implements Transform {
+  List<Asset> outs = [];
+  Asset _asset;
+  AssetId get primaryId => _asset.id;
+  TransformLogger logger = new TransformLogger(false);
+  Future<Asset> get primaryInput => new Future.value(_asset);
+
+  _MockTransform(this._asset);
+  Future<Asset> getInput(Asset id) {
+    if (id == primaryId) return primaryInput;
+    fail();
+  }
+
+  void addOutput(Asset output) {
+    outs.add(output);
+  }
+}
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 0c4304c..03873c1 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -119,6 +119,7 @@
 oauth2/test/client_test: Fail, OK # Uses dart:io.
 oauth2/test/credentials_test: Fail, OK # Uses dart:io.
 oauth2/test/handle_access_token_response_test: Fail, OK # Uses dart:io.
+observe/test/transform_test: Fail, OK # Uses dart:io.
 path/test/io_test: Fail, OK # Uses dart:io.
 watcher/test/*: Fail, OK # Uses dart:io.
 
@@ -185,7 +186,3 @@
 
 [ $runtime == safari || $runtime == chrome || $runtime == ie9 || $runtime == ff || $runtime == dartium || $runtime == drt ]
 docgen/test/single_library_test: Skip # Uses dart:io
-
-[ $compiler == none && ($runtime == drt || $runtime == dartium) ]
-# TODO(vsm): Triage from latest Blink merge
-custom_element/test/custom_element_test: Skip
diff --git a/pkg/source_maps/lib/printer.dart b/pkg/source_maps/lib/printer.dart
index 333aadc..95539d2 100644
--- a/pkg/source_maps/lib/printer.dart
+++ b/pkg/source_maps/lib/printer.dart
@@ -12,8 +12,8 @@
 const int _LF = 10;
 const int _CR = 13;
 
-/// A printer that keeps track of offset locations and records source maps
-/// locations.
+/// A simple printer that keeps track of offset locations and records source
+/// maps locations.
 class Printer {
   final String filename;
   final StringBuffer _buff = new StringBuffer();
@@ -87,3 +87,159 @@
     _loc = loc;
   }
 }
+
+/// A more advanced printer that keeps track of offset locations to record
+/// source maps, but additionally allows nesting of different kind of items,
+/// including [NestedPrinter]s, and it let's you automatically indent text.
+///
+/// This class is especially useful when doing code generation, where different
+/// peices of the code are generated independently on separate printers, and are
+/// finally put together in the end.
+class NestedPrinter implements NestedItem {
+
+  /// Items recoded by this printer, which can be [String] literals,
+  /// [NestedItem]s, and source map information like [Location] and [Span].
+  List _items = [];
+
+  /// Internal buffer to merge consecutive strings added to this printer.
+  StringBuffer _buff;
+
+  /// Current indentation, which can be updated from outside this class.
+  int indent;
+
+  /// Item used to indicate that the following item is copied from the original
+  /// source code, and hence we should preserve source-maps on every new line.
+  static final _ORIGINAL = new Object();
+
+  NestedPrinter([this.indent = 0]);
+
+  /// Adds [object] to this printer. [object] can be a [String],
+  /// [NestedPrinter], or anything implementing [NestedItem]. If [object] is a
+  /// [String], the value is appended directly, without doing any formatting
+  /// changes. If you wish to add a line of code with automatic indentation, use
+  /// [addLine] instead.  [NestedPrinter]s and [NestedItem]s are not processed
+  /// until [build] gets called later on. We ensure that [build] emits every
+  /// object in the order that they were added to this printer.
+  ///
+  /// The [location] and [span] parameters indicate the corresponding source map
+  /// location of [object] in the original input. Only one, [location] or
+  /// [span], should be provided at a time.
+  ///
+  /// Indicate [isOriginal] when [object] is copied directly from the user code.
+  /// Setting [isOriginal] will make this printer propagate source map locations
+  /// on every line-break.
+  void add(object, {Location location, Span span, bool isOriginal: false}) {
+    if (object is! String || location != null || span != null || isOriginal) {
+      _flush();
+      assert(location == null || span == null);
+      if (location != null) _items.add(location);
+      if (span != null) _items.add(span);
+      if (isOriginal) _items.add(_ORIGINAL);
+    }
+
+    if (object is String) {
+      _appendString(object);
+    } else {
+      _items.add(object);
+    }
+  }
+
+  /// Append `2 * indent` spaces to this printer.
+  void insertIndent() => _indent(indent);
+
+  /// Add a [line], autoindenting to the current value of [indent]. Note,
+  /// indentation is not inferred from the contents added to this printer. If a
+  /// line starts or ends an indentation block, you need to also update [indent]
+  /// accordingly. Also, indentation is not adapted for nested printers. If
+  /// you add a [NestedPrinter] to this printer, its indentation is set
+  /// separately and will not include any the indentation set here.
+  ///
+  /// The [location] and [span] parameters indicate the corresponding source map
+  /// location of [object] in the original input. Only one, [location] or
+  /// [span], should be provided at a time.
+  void addLine(String line, {Location location, Span span}) {
+    if (location != null || span != null) {
+      _flush();
+      assert(location == null || span == null);
+      if (location != null) _items.add(location);
+      if (span != null) _items.add(span);
+    }
+    if (line == null) return;
+    if (line != '') {
+      // We don't indent empty lines.
+      _indent(indent);
+      _appendString(line);
+    }
+    _appendString('\n');
+  }
+
+  /// Appends a string merging it with any previous strings, if possible.
+  void _appendString(String s) {
+    if (_buff == null) _buff = new StringBuffer();
+    _buff.write(s);
+  }
+
+  /// Adds all of the current [_buff] contents as a string item.
+  void _flush() {
+    if (_buff != null) {
+      _items.add(_buff.toString());
+      _buff = null;
+    }
+  }
+
+  void _indent(int indent) {
+    for (int i = 0; i < indent; i++) _appendString('  ');
+  }
+
+  /// Returns a string representation of all the contents appended to this
+  /// printer, including source map location tokens.
+  String toString() {
+    _flush();
+    return (new StringBuffer()..writeAll(_items)).toString();
+  }
+
+  /// [Printer] used during the last call to [build], if any.
+  Printer printer;
+
+  /// Returns the text produced after calling [build].
+  String get text => printer.text;
+
+  /// Returns the source-map information produced after calling [build].
+  String get map => printer.map;
+
+  /// Builds the output of this printer and source map information. After
+  /// calling this function, you can use [text] and [map] to retrieve the
+  /// geenrated code and source map information, respectively.
+  void build(String filename) {
+    writeTo(printer = new Printer(filename));
+  }
+
+  /// Implements the [NestedItem] interface.
+  void writeTo(Printer printer) {
+    _flush();
+    bool propagate = false;
+    for (var item in _items) {
+      if (item is NestedItem) {
+        item.writeTo(printer);
+      } else if (item is String) {
+        printer.add(item, projectMarks: propagate);
+        propagate = false;
+      } else if (item is Location || item is Span) {
+        printer.mark(item);
+      } else if (item == _ORIGINAL) {
+        // we insert booleans when we are about to quote text that was copied
+        // from the original source. In such case, we will propagate marks on
+        // every new-line.
+        propagate = true;
+      } else {
+        throw new UnsupportedError('Unknown item type: $item');
+      }
+    }
+  }
+}
+
+/// An item added to a [NestedPrinter].
+abstract class NestedItem {
+  /// Write the contents of this item into [printer].
+  void writeTo(Printer printer);
+}
diff --git a/pkg/source_maps/lib/refactor.dart b/pkg/source_maps/lib/refactor.dart
new file mode 100644
index 0000000..45fb069
--- /dev/null
+++ b/pkg/source_maps/lib/refactor.dart
@@ -0,0 +1,131 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Tools to help implement refactoring like transformations to Dart code.
+///
+/// [TextEditTransaction] supports making a series of changes to a text buffer.
+/// [guessIndent] helps to guess the appropriate indentiation for the new code.
+library source_maps.refactor;
+
+import 'span.dart';
+import 'printer.dart';
+
+/// Editable text transaction.
+///
+/// Applies a series of edits using original location
+/// information, and composes them into the edited string.
+class TextEditTransaction {
+  final SourceFile file;
+  final String original;
+  final _edits = <_TextEdit>[];
+
+  TextEditTransaction(this.original, this.file);
+
+  bool get hasEdits => _edits.length > 0;
+
+  /// Edit the original text, replacing text on the range [begin] and [end]
+  /// with the [replacement]. [replacement] can be either a string or a
+  /// [NestedPrinter].
+  void edit(int begin, int end, replacement) {
+    _edits.add(new _TextEdit(begin, end, replacement));
+  }
+
+  /// Create a source map [Location] for [offset].
+  Location _loc(int offset) =>
+      file != null ? file.location(offset) : null;
+
+  /// Applies all pending [edit]s and returns a [NestedPrinter] containing the
+  /// rewritten string and source map information. [filename] is given to the
+  /// underlying printer to indicate the name of the generated file that will
+  /// contains the source map information.
+  /// 
+  /// Throws [UnsupportedError] if the edits were overlapping. If no edits were
+  /// made, the printer simply contains the original string.
+  NestedPrinter commit() {
+    var printer = new NestedPrinter();
+    if (_edits.length == 0) {
+      return printer..add(original, location: _loc(0), isOriginal: true);
+    }
+
+    // Sort edits by start location.
+    _edits.sort();
+
+    int consumed = 0;
+    for (var edit in _edits) {
+      if (consumed > edit.begin) {
+        var sb = new StringBuffer();
+        sb..write(file.location(edit.begin).formatString)
+            ..write(': overlapping edits. Insert at offset ')
+            ..write(edit.begin)
+            ..write(' but have consumed ')
+            ..write(consumed)
+            ..write(' input characters. List of edits:');
+        for (var e in _edits) sb..write('\n    ')..write(e);
+        throw new UnsupportedError(sb.toString());
+      }
+
+      // Add characters from the original string between this edit and the last
+      // one, if any.
+      var betweenEdits = original.substring(consumed, edit.begin);
+      printer..add(betweenEdits, location: _loc(consumed), isOriginal: true)
+             ..add(edit.replace, location: _loc(edit.begin));
+      consumed = edit.end;
+    }
+
+    // Add any text from the end of the original string that was not replaced.
+    printer.add(original.substring(consumed),
+        location: _loc(consumed), isOriginal: true);
+    return printer;
+  }
+}
+
+class _TextEdit implements Comparable<_TextEdit> {
+  final int begin;
+  final int end;
+
+  /// The replacement used by the edit, can be a string or a [NestedPrinter].
+  final replace;
+
+  _TextEdit(this.begin, this.end, this.replace);
+
+  int get length => end - begin;
+
+  String toString() => '(Edit @ $begin,$end: "$replace")';
+
+  int compareTo(_TextEdit other) {
+    int diff = begin - other.begin;
+    if (diff != 0) return diff;
+    return end - other.end;
+  }
+}
+
+/// Returns all whitespace characters at the start of [charOffset]'s line.
+String guessIndent(String code, int charOffset) {
+  // Find the beginning of the line
+  int lineStart = 0;
+  for (int i = charOffset - 1; i >= 0; i--) {
+    var c = code.codeUnitAt(i);
+    if (c == _LF || c == _CR) {
+      lineStart = i + 1;
+      break;
+    }
+  }
+
+  // Grab all the whitespace
+  int whitespaceEnd = code.length;
+  for (int i = lineStart; i < code.length; i++) {
+    var c = code.codeUnitAt(i);
+    if (c != _SPACE && c != _TAB) {
+      whitespaceEnd = i;
+      break;
+    }
+  }
+
+  return code.substring(lineStart, whitespaceEnd);
+}
+
+const int _CR = 13;
+const int _LF = 10;
+const int _TAB = 9;
+const int _SPACE = 32;
diff --git a/pkg/source_maps/lib/source_maps.dart b/pkg/source_maps/lib/source_maps.dart
index 8fc48c9..2d4a4cc 100644
--- a/pkg/source_maps/lib/source_maps.dart
+++ b/pkg/source_maps/lib/source_maps.dart
@@ -39,4 +39,5 @@
 export "builder.dart";
 export "parser.dart";
 export "printer.dart";
+export "refactor.dart";
 export "span.dart";
diff --git a/pkg/source_maps/test/printer_test.dart b/pkg/source_maps/test/printer_test.dart
index d038ad5..eaeae2a 100644
--- a/pkg/source_maps/test/printer_test.dart
+++ b/pkg/source_maps/test/printer_test.dart
@@ -79,4 +79,44 @@
     expect(printer2.text, out);
     expect(printer2.map, printer.map);
   });
+
+  group('nested printer', () {
+    test('simple use', () {
+      var printer = new NestedPrinter();
+      printer..add('var ')
+             ..add('x = 3;\n', span: inputVar1)
+             ..add('f(', span: inputFunction)
+             ..add('y) => ', span: inputVar2)
+             ..add('x + y;\n', span: inputExpr)
+             ..build('output.dart');
+      expect(printer.text, OUTPUT);
+      expect(printer.map, json.stringify(EXPECTED_MAP));
+    });
+
+    test('nested use', () {
+      var printer = new NestedPrinter();
+      printer..add('var ')
+             ..add(new NestedPrinter()..add('x = 3;\n', span: inputVar1))
+             ..add('f(', span: inputFunction)
+             ..add(new NestedPrinter()..add('y) => ', span: inputVar2))
+             ..add('x + y;\n', span: inputExpr)
+             ..build('output.dart');
+      expect(printer.text, OUTPUT);
+      expect(printer.map, json.stringify(EXPECTED_MAP));
+    });
+
+    test('add indentation', () {
+      var out = INPUT.replaceAll('long', '_s');
+      var lines = INPUT.trim().split('\n');
+      expect(lines.length, 7);
+      var printer = new NestedPrinter();
+      for (int i = 0; i < lines.length; i++) {
+        if (i == 5) printer.indent++;
+        printer.addLine(lines[i].replaceAll('long', '_s').trim());
+        if (i == 5) printer.indent--;
+      }
+      printer.build('output.dart');
+      expect(printer.text, out);
+    });
+  });
 }
diff --git a/pkg/source_maps/test/refactor_test.dart b/pkg/source_maps/test/refactor_test.dart
new file mode 100644
index 0000000..c153d90
--- /dev/null
+++ b/pkg/source_maps/test/refactor_test.dart
@@ -0,0 +1,96 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.test.refactor_test;
+
+import 'package:unittest/unittest.dart';
+import 'package:source_maps/refactor.dart';
+import 'package:source_maps/span.dart';
+import 'package:source_maps/parser.dart' show parse, Mapping;
+
+main() {
+  group('conflict detection', () {
+    var original = "0123456789abcdefghij";
+    var file = new SourceFile.text('', original);
+
+    test('no conflict, in order', () {
+      var txn = new TextEditTransaction(original, file);
+      txn.edit(2, 4, '.');
+      txn.edit(5, 5, '|');
+      txn.edit(6, 6, '-');
+      txn.edit(6, 7, '_');
+      expect((txn.commit()..build('')).text, "01.4|5-_789abcdefghij");
+    });
+
+    test('no conflict, out of order', () {
+      var txn = new TextEditTransaction(original, file);
+      txn.edit(2, 4, '.');
+      txn.edit(5, 5, '|');
+
+      // Regresion test for issue #404: there is no conflict/overlap for edits
+      // that don't remove any of the original code.
+      txn.edit(6, 7, '_');
+      txn.edit(6, 6, '-');
+      expect((txn.commit()..build('')).text, "01.4|5-_789abcdefghij");
+
+    });
+
+    test('conflict', () {
+      var txn = new TextEditTransaction(original, file);
+      txn.edit(2, 4, '.');
+      txn.edit(3, 3, '-');
+      expect(() => txn.commit(), throwsA(predicate(
+            (e) => e.toString().contains('overlapping edits'))));
+    });
+  });
+
+  test('generated source maps', () {
+    var original =
+        "0123456789\n0*23456789\n01*3456789\nabcdefghij\nabcd*fghij\n";
+    var file = new SourceFile.text('', original);
+    var txn = new TextEditTransaction(original, file);
+    txn.edit(27, 29, '__\n    ');
+    txn.edit(34, 35, '___');
+    var printer = (txn.commit()..build(''));
+    var output = printer.text;
+    var map = parse(printer.map);
+    expect(output,
+        "0123456789\n0*23456789\n01*34__\n    789\na___cdefghij\nabcd*fghij\n");
+
+    // Line 1 and 2 are unmodified: mapping any column returns the beginning
+    // of the corresponding line:
+    expect(_span(1, 1, map, file), ":1:1: \n0123456789");
+    expect(_span(1, 5, map, file), ":1:1: \n0123456789");
+    expect(_span(2, 1, map, file), ":2:1: \n0*23456789");
+    expect(_span(2, 8, map, file), ":2:1: \n0*23456789");
+
+    // Line 3 is modified part way: mappings before the edits have the right
+    // mapping, after the edits the mapping is null.
+    expect(_span(3, 1, map, file), ":3:1: \n01*3456789");
+    expect(_span(3, 5, map, file), ":3:1: \n01*3456789");
+
+    // Start of edits map to beginning of the edit secion:
+    expect(_span(3, 6, map, file), ":3:6: \n01*3456789");
+    expect(_span(3, 7, map, file), ":3:6: \n01*3456789");
+
+    // Lines added have no mapping (they should inherit the last mapping),
+    // but the end of the edit region continues were we left off:
+    expect(_span(4, 1, map, file), isNull);
+    expect(_span(4, 5, map, file), ":3:8: \n01*3456789");
+
+    // Subsequent lines are still mapped correctly:
+    expect(_span(5, 1, map, file), ":4:1: \nabcdefghij"); // a (in a___cd...)
+    expect(_span(5, 2, map, file), ":4:2: \nabcdefghij"); // _ (in a___cd...)
+    expect(_span(5, 3, map, file), ":4:2: \nabcdefghij"); // _ (in a___cd...)
+    expect(_span(5, 4, map, file), ":4:2: \nabcdefghij"); // _ (in a___cd...)
+    expect(_span(5, 5, map, file), ":4:3: \nabcdefghij"); // c (in a___cd...)
+    expect(_span(6, 1, map, file), ":5:1: \nabcd*fghij");
+    expect(_span(6, 8, map, file), ":5:1: \nabcd*fghij");
+  });
+}
+
+String _span(int line, int column, Mapping map, SourceFile file) {
+  var span = map.spanFor(line - 1, column - 1, files: {'': file});
+  return span == null ? null : span.getLocationMessage('').trim();
+}
diff --git a/pkg/source_maps/test/run.dart b/pkg/source_maps/test/run.dart
index 9a19785..876cff7 100755
--- a/pkg/source_maps/test/run.dart
+++ b/pkg/source_maps/test/run.dart
@@ -13,6 +13,7 @@
 import 'end2end_test.dart' as end2end_test;
 import 'parser_test.dart' as parser_test;
 import 'printer_test.dart' as printer_test;
+import 'refactor_test.dart' as refactor_test;
 import 'span_test.dart' as span_test;
 import 'utils_test.dart' as utils_test;
 import 'vlq_test.dart' as vlq_test;
@@ -32,6 +33,7 @@
   addGroup('end2end_test.dart', end2end_test.main);
   addGroup('parser_test.dart', parser_test.main);
   addGroup('printer_test.dart', printer_test.main);
+  addGroup('refactor_test.dart', refactor_test.main);
   addGroup('span_test.dart', span_test.main);
   addGroup('utils_test.dart', utils_test.main);
   addGroup('vlq_test.dart', vlq_test.main);
diff --git a/runtime/vm/assembler_arm.cc b/runtime/vm/assembler_arm.cc
index e722032..7752441 100644
--- a/runtime/vm/assembler_arm.cc
+++ b/runtime/vm/assembler_arm.cc
@@ -1731,9 +1731,8 @@
 
   if (!CanEncodeBranchOffset(offset)) {
     ASSERT(!use_far_branches());
-    const Error& error = Error::Handle(LanguageError::New(
-        String::Handle(String::New("Branch offset overflow"))));
-    Isolate::Current()->long_jump_base()->Jump(1, error);
+    Isolate::Current()->long_jump_base()->Jump(
+        1, Object::branch_offset_error());
   }
 
   // Properly preserve only the bits supported in the instruction.
diff --git a/runtime/vm/assembler_mips.cc b/runtime/vm/assembler_mips.cc
index 7f06822..bb4a2c3 100644
--- a/runtime/vm/assembler_mips.cc
+++ b/runtime/vm/assembler_mips.cc
@@ -52,9 +52,8 @@
 int32_t Assembler::EncodeBranchOffset(int32_t offset, int32_t instr) {
   if (!CanEncodeBranchOffset(offset)) {
     ASSERT(!use_far_branches());
-    const Error& error = Error::Handle(LanguageError::New(
-        String::Handle(String::New("Branch offset overflow"))));
-    Isolate::Current()->long_jump_base()->Jump(1, error);
+    Isolate::Current()->long_jump_base()->Jump(
+        1, Object::branch_offset_error());
   }
 
   // Properly preserve only the bits supported in the instruction.
diff --git a/runtime/vm/class_table.cc b/runtime/vm/class_table.cc
index 3b34490..2eac4c9 100644
--- a/runtime/vm/class_table.cc
+++ b/runtime/vm/class_table.cc
@@ -29,7 +29,6 @@
       table_[i] = vm_class_table->At(i);
     }
     table_[kFreeListElement] = vm_class_table->At(kFreeListElement);
-    table_[kNullCid] = vm_class_table->At(kNullCid);
     table_[kDynamicCid] = vm_class_table->At(kDynamicCid);
     table_[kVoidCid] = vm_class_table->At(kVoidCid);
   }
diff --git a/runtime/vm/compiler.cc b/runtime/vm/compiler.cc
index 8adb292..73c6d63 100644
--- a/runtime/vm/compiler.cc
+++ b/runtime/vm/compiler.cc
@@ -548,21 +548,21 @@
       done = true;
     } else {
       // We bailed out.
-      const Error& bailout_error = Error::Handle(
-          isolate->object_store()->sticky_error());
 
-      ASSERT(bailout_error.IsLanguageError());
-      const LanguageError& le = LanguageError::CheckedHandle(
-          isolate->object_store()->sticky_error());
-      const String& msg = String::Handle(le.message());
-      if (msg.Equals("Branch offset overflow")) {
+      if (isolate->object_store()->sticky_error() ==
+          Object::branch_offset_error().raw()) {
+        // Compilation failed due to an out of range branch offset in the
+        // assembler. We try again (done = false) with far branches enabled.
         done = false;
         ASSERT(!use_far_branches);
         use_far_branches = true;
       } else {
-        // If not for a branch offset overflow, we only bail out from
-        // generating ssa code.
+        // If the error isn't due to an out of range branch offset, we don't
+        // try again (done = true), and indicate that we did not finish
+        // compiling (is_compiled = false).
         if (FLAG_trace_bailout) {
+          const Error& bailout_error = Error::Handle(
+              isolate->object_store()->sticky_error());
           OS::Print("%s\n", bailout_error.ToErrorCString());
         }
         done = true;
diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc
index 8be7d0a..dc3017c3 100644
--- a/runtime/vm/dart_api_impl_test.cc
+++ b/runtime/vm/dart_api_impl_test.cc
@@ -203,7 +203,7 @@
   {
     Isolate* isolate = Isolate::Current();
     DARTSCOPE(isolate);
-    Dart_Handle class1 = Api::NewHandle(isolate, Object::null_class());
+    Dart_Handle class1 = Api::NewHandle(isolate, Object::void_class());
     Dart_Handle class2 = Api::NewHandle(isolate, Object::class_class());
 
     EXPECT(Dart_IdentityEquals(class1, class1));
@@ -4464,6 +4464,7 @@
 
 TEST_CASE(GetType) {
   const char* kScriptChars =
+      "library testlib;\n"
       "class Class {\n"
       "  static var name = 'Class';\n"
       "}\n"
@@ -4495,7 +4496,7 @@
   // Lookup a class that does not exist.
   type = Dart_GetType(lib, NewString("DoesNotExist"), 0, NULL);
   EXPECT(Dart_IsError(type));
-  EXPECT_STREQ("Type 'DoesNotExist' not found in library 'dart:test-lib'.",
+  EXPECT_STREQ("Type 'DoesNotExist' not found in library 'testlib'.",
                Dart_GetError(type));
 
   // Lookup a class from an error library.  The error propagates.
@@ -4654,6 +4655,7 @@
 
 TEST_CASE(RootLibrary) {
   const char* kScriptChars =
+      "library testlib;"
       "main() {"
       "  return 12345;"
       "}";
@@ -4673,7 +4675,14 @@
   EXPECT(!Dart_IsNull(root_lib));
   const char* name_cstr = "";
   EXPECT_VALID(Dart_StringToCString(lib_name, &name_cstr));
-  EXPECT_STREQ(TestCase::url(), name_cstr);
+  EXPECT_STREQ("testlib", name_cstr);
+
+  Dart_Handle lib_uri = Dart_LibraryUrl(root_lib);
+  EXPECT_VALID(lib_uri);
+  EXPECT(!Dart_IsNull(lib_uri));
+  const char* uri_cstr = "";
+  EXPECT_VALID(Dart_StringToCString(lib_uri, &uri_cstr));
+  EXPECT_STREQ(TestCase::url(), uri_cstr);
 }
 
 
diff --git a/runtime/vm/intermediate_language_ia32.cc b/runtime/vm/intermediate_language_ia32.cc
index 6a90823..69e744c 100644
--- a/runtime/vm/intermediate_language_ia32.cc
+++ b/runtime/vm/intermediate_language_ia32.cc
@@ -4493,6 +4493,9 @@
       new LocationSummary(kNumInputs, kNumTemps, LocationSummary::kNoCall);
   summary->set_in(0, Location::RequiresFpuRegister());
   summary->set_out(Location::SameAsFirstInput());
+  if (FLAG_throw_on_javascript_int_overflow) {
+    summary->set_temp(0, Location::RequiresRegister());
+  }
   return summary;
 }
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 29c3262..a6d7364 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -88,10 +88,10 @@
 Bool* Object::bool_false_ = NULL;
 Smi* Object::smi_illegal_cid_ = NULL;
 LanguageError* Object::snapshot_writer_error_ = NULL;
+LanguageError* Object::branch_offset_error_ = NULL;
 
 RawObject* Object::null_ = reinterpret_cast<RawObject*>(RAW_NULL);
 RawClass* Object::class_class_ = reinterpret_cast<RawClass*>(RAW_NULL);
-RawClass* Object::null_class_ = reinterpret_cast<RawClass*>(RAW_NULL);
 RawClass* Object::dynamic_class_ = reinterpret_cast<RawClass*>(RAW_NULL);
 RawClass* Object::void_class_ = reinterpret_cast<RawClass*>(RAW_NULL);
 RawClass* Object::unresolved_class_class_ =
@@ -360,6 +360,8 @@
   bool_false_ = Bool::ReadOnlyHandle();
   smi_illegal_cid_ = Smi::ReadOnlyHandle();
   snapshot_writer_error_ = LanguageError::ReadOnlyHandle();
+  branch_offset_error_ = LanguageError::ReadOnlyHandle();
+
 
   // Allocate and initialize the null instance.
   // 'null_' must be the first object allocated as it is used in allocation to
@@ -413,7 +415,7 @@
   cls = Class::New<Instance>(kNullCid);
   cls.set_is_finalized();
   cls.set_is_type_finalized();
-  null_class_ = cls.raw();
+  isolate->object_store()->set_null_class(cls);
 
   // Allocate and initialize the free list element class.
   cls = Class::New<FreeListElement::FakeInstance>(kFreeListElement);
@@ -571,8 +573,12 @@
   *bool_false_ = Bool::New(false);
 
   *smi_illegal_cid_ = Smi::New(kIllegalCid);
-  *snapshot_writer_error_ =
-      LanguageError::New(String::Handle(String::New("SnapshotWriter Error")));
+
+  String& error_str = String::Handle();
+  error_str = String::New("SnapshotWriter Error");
+  *snapshot_writer_error_ = LanguageError::New(error_str);
+  error_str = String::New("Branch offset overflow");
+  *branch_offset_error_ = LanguageError::New(error_str);
 
   ASSERT(!null_object_->IsSmi());
   ASSERT(!null_array_->IsSmi());
@@ -600,6 +606,8 @@
   ASSERT(smi_illegal_cid_->IsSmi());
   ASSERT(!snapshot_writer_error_->IsSmi());
   ASSERT(snapshot_writer_error_->IsLanguageError());
+  ASSERT(!branch_offset_error_->IsSmi());
+  ASSERT(branch_offset_error_->IsLanguageError());
 }
 
 
@@ -612,7 +620,6 @@
 
   // Set up names for all VM singleton classes.
   SET_CLASS_NAME(class, Class);
-  SET_CLASS_NAME(null, Null);
   SET_CLASS_NAME(dynamic, Dynamic);
   SET_CLASS_NAME(void, Void);
   SET_CLASS_NAME(unresolved_class, UnresolvedClass);
@@ -833,6 +840,20 @@
   RegisterClass(cls, Symbols::Bool(), core_lib);
   pending_classes.Add(cls, Heap::kOld);
 
+  cls = Class::New<Instance>(kNullCid);
+  cls.set_name(Symbols::Null());
+  // We immediately mark Null as finalized because it has no corresponding
+  // source.
+  cls.set_is_finalized();
+  cls.set_is_type_finalized();
+  object_store->set_null_class(cls);
+  cls.set_library(core_lib);  // A sort of fiction for the mirrors.
+  // When/if we promote Null to an ordinary class, it should be added to the
+  // core library, given source and added to the list of classes pending
+  // finalization.
+  // RegisterClass(cls, Symbols::Null(), core_lib);
+  // pending_classes.Add(cls, Heap::kOld);
+
   cls = object_store->array_class();  // Was allocated above.
   RegisterPrivateClass(cls, Symbols::ObjectArray(), core_lib);
   pending_classes.Add(cls, Heap::kOld);
@@ -1091,22 +1112,25 @@
   type = Type::NewNonParameterizedType(cls);
   object_store->set_mint_type(type);
 
-  // The classes 'Null' and 'void' are not registered in the class dictionary,
-  // because their names are reserved keywords. Their names are not heap
-  // allocated, because the classes reside in the VM isolate.
+  // The class 'Null' is not register in the class dictionary because it is not
+  // The classes 'void' and 'dynamic' are phoney classes to make type checking
+  // more regular; they live in the VM isolate. The class 'void' is not
+  // registered in the class dictionary because its name is a reserved word.
+  // The class 'dynamic' is registered in the class dictionary because its name
+  // is a built-in identifier (this is wrong).
   // The corresponding types are stored in the object store.
-  cls = null_class();
+  cls = object_store->null_class();
   type = Type::NewNonParameterizedType(cls);
   object_store->set_null_type(type);
 
+  // Consider removing when/if Null becomes an ordinary class.
+  type = object_store->object_type();
+  cls.set_super_type(type);
+
   cls = void_class();
   type = Type::NewNonParameterizedType(cls);
   object_store->set_void_type(type);
 
-  // The class 'dynamic' is registered in the class dictionary because its name
-  // is a built-in identifier, rather than a reserved keyword. Its name is not
-  // heap allocated, because the class resides in the VM isolate.
-  // The corresponding type, the "unknown type", is stored in the object store.
   cls = dynamic_class();
   type = Type::NewNonParameterizedType(cls);
   object_store->set_dynamic_type(type);
@@ -1211,6 +1235,9 @@
   cls = Class::New<Bool>();
   object_store->set_bool_class(cls);
 
+  cls = Class::New<Instance>(kNullCid);
+  object_store->set_null_class(cls);
+
   cls = Class::New<Stacktrace>();
   object_store->set_stacktrace_class(cls);
 
@@ -7056,7 +7083,7 @@
 RawLibrary* Library::NewLibraryHelper(const String& url,
                                       bool import_core_lib) {
   const Library& result = Library::Handle(Library::New());
-  result.StorePointer(&result.raw_ptr()->name_, url.raw());
+  result.StorePointer(&result.raw_ptr()->name_, Symbols::Empty().raw());
   result.StorePointer(&result.raw_ptr()->url_, url.raw());
   result.raw_ptr()->private_key_ = Scanner::AllocatePrivateKey(result);
   result.raw_ptr()->dictionary_ = Object::empty_array().raw();
@@ -10434,6 +10461,12 @@
 }
 
 
+bool AbstractType::IsNullType() const {
+  return HasResolvedTypeClass() &&
+      (type_class() == Type::Handle(Type::NullType()).type_class());
+}
+
+
 bool AbstractType::IsBoolType() const {
   return HasResolvedTypeClass() &&
       (type_class() == Type::Handle(Type::BoolType()).type_class());
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index a1455845..ed73e6b 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -404,8 +404,12 @@
     return *snapshot_writer_error_;
   }
 
+  static const LanguageError& branch_offset_error() {
+    ASSERT(branch_offset_error_ != NULL);
+    return *branch_offset_error_;
+  }
+
   static RawClass* class_class() { return class_class_; }
-  static RawClass* null_class() { return null_class_; }
   static RawClass* dynamic_class() { return dynamic_class_; }
   static RawClass* void_class() { return void_class_; }
   static RawClass* unresolved_class_class() { return unresolved_class_class_; }
@@ -553,7 +557,6 @@
   static RawObject* null_;
 
   static RawClass* class_class_;  // Class of the Class vm object.
-  static RawClass* null_class_;  // Class of the null object.
   static RawClass* dynamic_class_;  // Class of the 'dynamic' type.
   static RawClass* void_class_;  // Class of the 'void' type.
   static RawClass* unresolved_class_class_;  // Class of UnresolvedClass.
@@ -604,6 +607,7 @@
   static Bool* bool_false_;
   static Smi* smi_illegal_cid_;
   static LanguageError* snapshot_writer_error_;
+  static LanguageError* branch_offset_error_;
 
   friend void ClassTable::Register(const Class& cls);
   friend void RawObject::Validate(Isolate* isolate) const;
@@ -3745,9 +3749,7 @@
   }
 
   // Check if this type represents the 'Null' type.
-  bool IsNullType() const {
-    return HasResolvedTypeClass() && (type_class() == Object::null_class());
-  }
+  bool IsNullType() const;
 
   // Check if this type represents the 'void' type.
   bool IsVoidType() const {
diff --git a/runtime/vm/object_store.cc b/runtime/vm/object_store.cc
index 11482f7..f6602b0 100644
--- a/runtime/vm/object_store.cc
+++ b/runtime/vm/object_store.cc
@@ -17,6 +17,7 @@
 ObjectStore::ObjectStore()
   : object_class_(Class::null()),
     object_type_(Type::null()),
+    null_class_(Class::null()),
     null_type_(Type::null()),
     dynamic_type_(Type::null()),
     void_type_(Type::null()),
diff --git a/runtime/vm/object_store.h b/runtime/vm/object_store.h
index 76a2bcf..1d51a8c 100644
--- a/runtime/vm/object_store.h
+++ b/runtime/vm/object_store.h
@@ -50,6 +50,12 @@
     object_type_ = value.raw();
   }
 
+  RawClass* null_class() const {
+    ASSERT(null_class_ != Object::null());
+    return null_class_;
+  }
+  void set_null_class(const Class& value) { null_class_ = value.raw(); }
+
   RawType* null_type() const { return null_type_; }
   void set_null_type(const Type& value) {
     null_type_ = value.raw();
@@ -427,6 +433,7 @@
   RawObject** from() { return reinterpret_cast<RawObject**>(&object_class_); }
   RawClass* object_class_;
   RawType* object_type_;
+  RawClass* null_class_;
   RawType* null_type_;
   RawType* dynamic_type_;
   RawType* void_type_;
diff --git a/runtime/vm/raw_object.cc b/runtime/vm/raw_object.cc
index 535c785..b8ecacb 100644
--- a/runtime/vm/raw_object.cc
+++ b/runtime/vm/raw_object.cc
@@ -24,7 +24,7 @@
 
 
 void RawObject::Validate(Isolate* isolate) const {
-  if (Object::null_class_ == reinterpret_cast<RawClass*>(kHeapObjectTag)) {
+  if (Object::void_class_ == reinterpret_cast<RawClass*>(kHeapObjectTag)) {
     // Validation relies on properly initialized class classes. Skip if the
     // VM is still being initialized.
     return;
diff --git a/runtime/vm/snapshot.cc b/runtime/vm/snapshot.cc
index 9e89eae..a6be317 100644
--- a/runtime/vm/snapshot.cc
+++ b/runtime/vm/snapshot.cc
@@ -470,7 +470,7 @@
   ASSERT(isolate()->no_gc_scope_depth() != 0);
   if (class_id < kNumPredefinedCids) {
     ASSERT((class_id >= kInstanceCid) &&
-           (class_id <= kExternalTypedDataFloat32x4ArrayCid));
+           (class_id <= kNullCid));
     return isolate()->class_table()->At(class_id);
   }
   cls_ = Object::class_class();
diff --git a/runtime/vm/snapshot_test.cc b/runtime/vm/snapshot_test.cc
index f3d8e43..c1781d2 100644
--- a/runtime/vm/snapshot_test.cc
+++ b/runtime/vm/snapshot_test.cc
@@ -460,7 +460,6 @@
   uint8_t* buffer;
   MessageWriter writer(&buffer, &malloc_allocator);
   writer.WriteObject(Object::class_class());
-  writer.WriteObject(Object::null_class());
   writer.WriteObject(Object::type_arguments_class());
   writer.WriteObject(Object::instantiated_type_arguments_class());
   writer.WriteObject(Object::function_class());
@@ -480,7 +479,6 @@
   SnapshotReader reader(buffer, buffer_len, Snapshot::kMessage,
                         Isolate::Current());
   EXPECT(Object::class_class() == reader.ReadObject());
-  EXPECT(Object::null_class() == reader.ReadObject());
   EXPECT(Object::type_arguments_class() == reader.ReadObject());
   EXPECT(Object::instantiated_type_arguments_class() == reader.ReadObject());
   EXPECT(Object::function_class() == reader.ReadObject());
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index dfaa5ed..643dd58 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -9316,30 +9316,30 @@
 
   /**
    * Checks if this element matches the CSS selectors.
-   *
-   * If `includeAncestors` is true, we examine all of this element's parent
-   * elements and also return true if any of its parent elements matches
-   * `selectors`.
    */
   @Experimental()
-  bool matches(String selectors, [includeAncestors = false]) {
+  bool matches(String selectors) {
+    if (JS('bool', '!!#.matches', this)) {
+      return JS('bool', '#.matches(#)', this, selectors);
+    } else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
+      return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
+    } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
+      return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
+    } else if (JS('bool', '!!#.msMatchesSelector', this)) {
+      return matches = JS('bool', '#.msMatchesSelector(#)', this, selectors);
+    } else {
+      throw new UnsupportedError("Not supported on this platform");
+    }
+  }
+
+  /** Checks if this element or any of its parents match the CSS selectors. */
+  @Experimental()
+  bool matchesWithAncestors(String selectors) {
     var elem = this;
     do {
-      bool matches = false;
-      if (JS('bool', '!!#.matches', elem)) {
-        matches = JS('bool', '#.matches(#)', elem, selectors);
-      } else if (JS('bool', '!!#.webkitMatchesSelector', elem)) {
-        matches = JS('bool', '#.webkitMatchesSelector(#)', elem, selectors);
-      } else if (JS('bool', '!!#.mozMatchesSelector', elem)) {
-        matches = JS('bool', '#.mozMatchesSelector(#)', elem, selectors);
-      } else if (JS('bool', '!!#.msMatchesSelector', elem)) {
-        matches = JS('bool', '#.msMatchesSelector(#)', elem, selectors);
-      } else {
-        throw new UnsupportedError("Not supported on this platform");
-      }
-      if (matches) return true;
+      if (elem.matches(selectors)) return true;
       elem = elem.parent;
-    } while(includeAncestors && elem != null);
+    } while(elem != null);
     return false;
   }
 
@@ -28810,7 +28810,7 @@
       super(target, eventType, useCapture);
 
   Stream<T> matches(String selector) =>
-      this.where((event) => event.target.matches(selector, true));
+      this.where((event) => event.target.matchesWithAncestors(selector));
 }
 
 /**
@@ -28832,7 +28832,7 @@
   }
 
   Stream<T> matches(String selector) =>
-      this.where((event) => event.target.matches(selector, true));
+      this.where((event) => event.target.matchesWithAncestors(selector));
 
   // Delegate all regular Stream behavor to our wrapped Stream.
   StreamSubscription<T> listen(void onData(T event),
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index 1524bc9..9e93000 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -9734,6 +9734,17 @@
   }
 
 
+  /** Checks if this element or any of its parents match the CSS selectors. */
+  @Experimental()
+  bool matchesWithAncestors(String selectors) {
+    var elem = this;
+    do {
+      if (elem.matches(selectors)) return true;
+      elem = elem.parent;
+    } while(elem != null);
+    return false;
+  }
+
   Element _templateInstanceRef;
 
   // Note: only used if `this is! TemplateElement`
@@ -30351,7 +30362,7 @@
       super(target, eventType, useCapture);
 
   Stream<T> matches(String selector) =>
-      this.where((event) => event.target.matches(selector, true));
+      this.where((event) => event.target.matchesWithAncestors(selector));
 }
 
 /**
@@ -30373,7 +30384,7 @@
   }
 
   Stream<T> matches(String selector) =>
-      this.where((event) => event.target.matches(selector, true));
+      this.where((event) => event.target.matchesWithAncestors(selector));
 
   // Delegate all regular Stream behavor to our wrapped Stream.
   StreamSubscription<T> listen(void onData(T event),
diff --git a/tests/co19/co19-analyzer.status b/tests/co19/co19-analyzer.status
index 78dc78a..a79a085 100644
--- a/tests/co19/co19-analyzer.status
+++ b/tests/co19/co19-analyzer.status
@@ -42,7 +42,7 @@
 LibTest/core/String/splitChars_A01_t01: fail, OK
 
 # co19 issue #389, ceil, floor, truncate and round return integers
-LibTest/core/int/operator_division_A01_t01: fail
+LibTest/core/int/operator_division_A01_t01: fail, OK
 
 # co19 issue #395, uses dart:io API (outdated)
 Language/15_Reference/1_Lexical_Rules_A01_t10: Fail, OK
@@ -168,8 +168,8 @@
 LibTest/core/RegExp/Pattern_semantics/firstMatch_CharacterEscape_A09_t01: fail, OK
 
 #co19 issue #432, missing @static-warning annotation
-Language/14_Types/8_Parameterized_Types_A03_t03: fail,OK
-Language/14_Types/8_Parameterized_Types_A03_t05: fail,OK
+Language/14_Types/8_Parameterized_Types_A03_t03: fail, OK
+Language/14_Types/8_Parameterized_Types_A03_t05: fail, OK
 
 # co19 issue #433, missing @static-warning annotation
 Language/11_Expressions/15_Method_Invocation/3_Static_Invocation_A03_t01: fail, OK
@@ -191,8 +191,8 @@
 LibTest/core/AssertionError/url_A01_t01: fail, OK
 
 # co19 issue 459, FallThroughError is no longer const
-LibTest/core/FallThroughError/toString_A01_t01: Fail
-LibTest/core/FallThroughError/FallThroughError_A01_t01: Fail
+LibTest/core/FallThroughError/toString_A01_t01: fail, OK
+LibTest/core/FallThroughError/FallThroughError_A01_t01: Fail, OK
 
 # co19 issue #437, annotation should be constant _variable_ or constant constructor invocation
 Language/07_Classes/07_Classes_A01_t20: fail, OK
@@ -452,3 +452,6 @@
 Language/13_Libraries_and_Scripts/1_Imports_A03_t68: fail, OK
 Language/13_Libraries_and_Scripts/1_Imports_A03_t69: fail, OK
 Language/13_Libraries_and_Scripts/1_Imports_A03_t70: fail, OK
+
+# co19 issue 483, instance access to static member
+Language/11_Expressions/15_Method_Invocation/2_Cascaded_Invocation_A01_t19: fail, OK
diff --git a/tests/html/html.status b/tests/html/html.status
index 1000c5c..10747a7 100644
--- a/tests/html/html.status
+++ b/tests/html/html.status
@@ -71,8 +71,6 @@
 [ $compiler == none && ($runtime == drt || $runtime == dartium) ]
 request_animation_frame_test: Skip   # drt hangs; requestAnimationFrame not implemented
 worker_api_test: Fail # http://dartbug.com/10223
-element_test/click: Fail #TODO(efortuna)
-element_test/eventDelegation: Fail #TODO(efortuna)
 
 [ $compiler == none && ($runtime == drt || $runtime == dartium) && $system == windows]
 websql_test: Skip # Issue 4941: stderr contains a backtrace.
@@ -98,6 +96,8 @@
 canvasrenderingcontext2d_test/drawImage_video_element: Fail # IE does not support drawImage w/ video element
 canvasrenderingcontext2d_test/drawImage_video_element_dataUrl: Fail # IE does not support drawImage w/ video element
 worker_test/functional: Fail # IE uses incorrect security context for Blob URIs.
+element_test/eventDelegation: Fail # TODO(efortuna)
+element_test/matches: Fail # TODO(efortuna)
 
 # IE10 Feature support statuses-
 # All changes should be accompanied by platform support annotation changes.
@@ -160,6 +160,7 @@
 canvasrenderingcontext2d_test/drawImage_video_element_dataUrl: Fail # IE does not support drawImage w/ video element
 canvasrenderingcontext2d_test/drawImage_image_element: Pass, Fail # Issue: 11416
 input_element_test/attributes: Fail # IE returns null while others ''
+element_test/eventDelegation: Fail # TODO(efortuna)
 
 # IE9 Feature support statuses-
 # All changes should be accompanied by platform support annotation changes.
diff --git a/tests/language/language_analyzer.status b/tests/language/language_analyzer.status
index e8a968b..194c299 100644
--- a/tests/language/language_analyzer.status
+++ b/tests/language/language_analyzer.status
@@ -80,11 +80,6 @@
 # TBF: hiding at start of the block and declared after declaration statement
 scope_negative_test: fail
 
-# TBF: 'instance.staticMethod()' is static warning
-static_field_test/01: fail
-static_field_test/02: fail
-static_field_test/03: fail
-
 # TBF
 method_override2_test/00: fail # issue 11497
 method_override2_test/01: fail # issue 11497
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 31a0738..6967e4c 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -22,6 +22,7 @@
 mirrors/null_test : Fail # Issue 12129
 mirrors/library_metadata_test: Fail # Issue 10905
 mirrors/library_uri_io_test: Skip # Not intended for dart2js as it uses dart:io.
+mirrors/unnamed_library_test: Fail # Issue 10580
 async/run_async3_test: Fail # _enqueueImmediate runs after Timer. http://dartbug.com/9002
 async/run_async4_test: Pass, Fail # no global exception handler in isolates. http://dartbug.com/9012
 async/run_async6_test: Fail # global error handling is not supported. http://dartbug.com/5958
@@ -101,7 +102,6 @@
 async/run_async3_test: Fail # _enqueueImmediate runs after Timer. http://dartbug.com/9001.
 mirrors/parameter_test: Fail # http://dartbug.com/11567
 mirrors/operator_test: Fail # http://dartbug.com/11944
-mirrors/null_test: Fail # Issue 12128
 
 [ $compiler == none && $runtime == drt ]
 async/timer_isolate_test: Skip # See Issue 4997
diff --git a/tests/lib/mirrors/null_test.dart b/tests/lib/mirrors/null_test.dart
index 9142c6d..2a8a8ba 100644
--- a/tests/lib/mirrors/null_test.dart
+++ b/tests/lib/mirrors/null_test.dart
@@ -11,6 +11,8 @@
 main() {
   InstanceMirror nullMirror = reflect(null);
   Expect.isTrue(nullMirror.getField(const Symbol('hashCode')).reflectee is int);
+  Expect.equals(null.hashCode,
+                nullMirror.getField(const Symbol('hashCode')).reflectee);
   Expect.equals('Null',
                 nullMirror.getField(const Symbol('runtimeType')).reflectee
                 .toString());
@@ -27,5 +29,7 @@
   Expect.equals(const Symbol('Null'), NullMirror.simpleName);
   Expect.equals(const Symbol('Object'), NullMirror.superclass.simpleName);
   Expect.equals(null, NullMirror.superclass.superclass);
-  Expect.equals(null, NullMirror.owner);  // Null belongs to no library.
+  Expect.listEquals([], NullMirror.superinterfaces);
+  Expect.equals(currentMirrorSystem().libraries[Uri.parse('dart:core')],
+                NullMirror.owner);
 }
diff --git a/tests/lib/mirrors/unnamed_library_test.dart b/tests/lib/mirrors/unnamed_library_test.dart
new file mode 100644
index 0000000..1e81eaa
--- /dev/null
+++ b/tests/lib/mirrors/unnamed_library_test.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// No library declaration.
+
+import 'dart:mirrors';
+
+import 'package:expect/expect.dart';
+
+class C {}
+
+main() {
+  Expect.equals(const Symbol(""), reflectClass(C).owner.simpleName);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 4a9b649..4a2aa46 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
 MAJOR 0
 MINOR 6
-BUILD 16
+BUILD 17
 PATCH 0
diff --git a/tools/dom/src/EventStreamProvider.dart b/tools/dom/src/EventStreamProvider.dart
index a4ad4c0f..a9c293d 100644
--- a/tools/dom/src/EventStreamProvider.dart
+++ b/tools/dom/src/EventStreamProvider.dart
@@ -52,7 +52,7 @@
       super(target, eventType, useCapture);
 
   Stream<T> matches(String selector) =>
-      this.where((event) => event.target.matches(selector, true));
+      this.where((event) => event.target.matchesWithAncestors(selector));
 }
 
 /**
@@ -74,7 +74,7 @@
   }
 
   Stream<T> matches(String selector) =>
-      this.where((event) => event.target.matches(selector, true));
+      this.where((event) => event.target.matchesWithAncestors(selector));
 
   // Delegate all regular Stream behavor to our wrapped Stream.
   StreamSubscription<T> listen(void onData(T event),
diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate
index e708395..991fa21 100644
--- a/tools/dom/templates/html/impl/impl_Element.darttemplate
+++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
@@ -877,35 +877,35 @@
 
   /**
    * Checks if this element matches the CSS selectors.
-   *
-   * If `includeAncestors` is true, we examine all of this element's parent
-   * elements and also return true if any of its parent elements matches
-   * `selectors`.
    */
   @Experimental()
-  bool matches(String selectors, [includeAncestors = false]) {
-    var elem = this;
-    do {
-      bool matches = false;
-      if (JS('bool', '!!#.matches', elem)) {
-        matches = JS('bool', '#.matches(#)', elem, selectors);
-      } else if (JS('bool', '!!#.webkitMatchesSelector', elem)) {
-        matches = JS('bool', '#.webkitMatchesSelector(#)', elem, selectors);
-      } else if (JS('bool', '!!#.mozMatchesSelector', elem)) {
-        matches = JS('bool', '#.mozMatchesSelector(#)', elem, selectors);
-      } else if (JS('bool', '!!#.msMatchesSelector', elem)) {
-        matches = JS('bool', '#.msMatchesSelector(#)', elem, selectors);
-      } else {
-        throw new UnsupportedError("Not supported on this platform");
-      }
-      if (matches) return true;
-      elem = elem.parent;
-    } while(includeAncestors && elem != null);
-    return false;
+  bool matches(String selectors) {
+    if (JS('bool', '!!#.matches', this)) {
+      return JS('bool', '#.matches(#)', this, selectors);
+    } else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
+      return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
+    } else if (JS('bool', '!!#.mozMatchesSelector', this)) {
+      return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
+    } else if (JS('bool', '!!#.msMatchesSelector', this)) {
+      return matches = JS('bool', '#.msMatchesSelector(#)', this, selectors);
+    } else {
+      throw new UnsupportedError("Not supported on this platform");
+    }
   }
 $else
 $endif
 
+  /** Checks if this element or any of its parents match the CSS selectors. */
+  @Experimental()
+  bool matchesWithAncestors(String selectors) {
+    var elem = this;
+    do {
+      if (elem.matches(selectors)) return true;
+      elem = elem.parent;
+    } while(elem != null);
+    return false;
+  }
+
 $if DART2JS
   @Creates('Null')  // Set from Dart code; does not instantiate a native type.
 $endif