Revert "Update to pkg/observable 0.17.0"

This reverts commit c124c2c2b7448ba649d32d75af93c0fa5e2a3fde.
diff --git a/benchmark/index.dart b/benchmark/index.dart
new file mode 100644
index 0000000..49e7cad
--- /dev/null
+++ b/benchmark/index.dart
@@ -0,0 +1,171 @@
+// 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 observe.test.benchmark.index;
+
+import 'dart:async';
+import 'dart:html';
+import 'package:benchmark_harness/benchmark_harness.dart';
+import 'package:chart/chart.dart';
+import 'package:observe/mirrors_used.dart' as mu; // Makes output smaller.
+import 'package:smoke/mirrors.dart';
+import 'object_benchmark.dart';
+import 'setup_object_benchmark.dart';
+import 'observable_list_benchmark.dart';
+import 'setup_observable_list_benchmark.dart';
+import 'path_benchmark.dart';
+import 'setup_path_benchmark.dart';
+
+/// Benchmark names to factory functions.
+/// Uses [mu].
+typedef BenchmarkBase BenchmarkFactory(
+    int objectCount, int mutationCount, String config);
+final Map<String, BenchmarkFactory> benchmarkFactories = {
+  'ObjectBenchmark': (int o, int m, String c) => new ObjectBenchmark(o, m, c),
+  'SetupObjectBenchmark': (int o, int m, String c) =>
+      new SetupObjectBenchmark(o, c),
+  'ObservableListBenchmark': (int o, int m, String c) =>
+      new ObservableListBenchmark(o, m, c),
+  'SetupObservableListBenchmark': (int o, int m, String c) =>
+      new SetupObservableListBenchmark(o, c),
+  'PathBenchmark': (int o, int m, String c) => new PathBenchmark(o, m, c),
+  'SetupPathBenchmark': (int o, int m, String c) =>
+      new SetupPathBenchmark(o, c),
+};
+
+/// Benchmark names to possible configs.
+final Map<String, List<String>> benchmarkConfigs = {
+  'ObjectBenchmark': [],
+  'SetupObjectBenchmark': [],
+  'ObservableListBenchmark': ['splice', 'update', 'push/pop', 'shift/unshift'],
+  'SetupObservableListBenchmark': [],
+  'PathBenchmark': ['leaf', 'root'],
+  'SetupPathBenchmark': [],
+};
+
+Iterable<int> objectCounts;
+Iterable<int> mutationCounts;
+
+final ButtonElement goButton = querySelector('#go');
+final InputElement objectCountInput = querySelector('#objectCountInput');
+final InputElement mutationCountInput = querySelector('#mutationCountInput');
+final SpanElement mutationCountWrapper = querySelector('#mutationCountWrapper');
+final SpanElement statusSpan = querySelector('#status');
+final DivElement canvasWrapper = querySelector('#canvasWrapper');
+final SelectElement benchmarkSelect = querySelector('#benchmarkSelect');
+final SelectElement configSelect = querySelector('#configSelect');
+final UListElement legendList = querySelector('#legendList');
+final List<String> colors = [
+  [0, 0, 255],
+  [138, 43, 226],
+  [165, 42, 42],
+  [100, 149, 237],
+  [220, 20, 60],
+  [184, 134, 11]
+].map((rgb) => 'rgba(' + rgb.join(',') + ',.7)').toList();
+
+main() {
+  // TODO(jakemac): Use a transformer to generate the smoke config so we can see
+  // how that affects the benchmark.
+  useMirrors();
+
+  benchmarkSelect.onChange.listen((_) => changeBenchmark());
+  changeBenchmark();
+
+  goButton.onClick.listen((_) async {
+    canvasWrapper.children.clear();
+    goButton.disabled = true;
+    goButton.text = 'Running...';
+    legendList.text = '';
+    objectCounts =
+        objectCountInput.value.split(',').map((val) => int.parse(val));
+
+    if (benchmarkSelect.value.startsWith('Setup')) {
+      mutationCounts = [0];
+    } else {
+      mutationCounts =
+          mutationCountInput.value.split(',').map((val) => int.parse(val));
+    }
+
+    var i = 0;
+    mutationCounts.forEach((count) {
+      var li = document.createElement('li');
+      li.text = '$count mutations.';
+      li.style.color = colors[i % colors.length];
+      legendList.append(li);
+      i++;
+    });
+
+    var results = <List<double>>[];
+    for (int objectCount in objectCounts) {
+      int x = 0;
+      for (int mutationCount in mutationCounts) {
+        statusSpan.text =
+            'Testing: $objectCount objects with $mutationCount mutations';
+        // Let the status text render before running the next benchmark.
+        await new Future(() {});
+        var factory = benchmarkFactories[benchmarkSelect.value];
+        var benchmark = factory(objectCount, mutationCount, configSelect.value);
+        // Divide by 10 because benchmark_harness returns the amount of time it
+        // took to run 10 times, not once :(.
+        var resultMicros = benchmark.measure() / 10;
+
+        if (results.length <= x) results.add([]);
+        results[x].add(resultMicros / 1000);
+        x++;
+      }
+    }
+
+    drawBenchmarks(results);
+  });
+}
+
+void drawBenchmarks(List<List<double>> results) {
+  var datasets = [];
+  for (int i = 0; i < results.length; i++) {
+    datasets.add({
+      'fillColor': 'rgba(255, 255, 255, 0)',
+      'strokeColor': colors[i % colors.length],
+      'pointColor': colors[i % colors.length],
+      'pointStrokeColor': "#fff",
+      'data': results[i],
+    });
+  }
+  var data = {
+    'labels': objectCounts.map((c) => '$c').toList(),
+    'datasets': datasets,
+  };
+
+  new Line(data, {
+    'bezierCurve': false,
+  }).show(canvasWrapper);
+  goButton.disabled = false;
+  goButton.text = 'Run Benchmarks';
+  statusSpan.text = '';
+}
+
+void changeBenchmark() {
+  var configs = benchmarkConfigs[benchmarkSelect.value];
+  configSelect.text = '';
+  configs.forEach((config) {
+    var option = document.createElement('option');
+    option.text = config;
+    configSelect.append(option);
+  });
+
+  document.title = benchmarkSelect.value;
+
+  // Don't show the configSelect if there are no configs.
+  if (configs.isEmpty) {
+    configSelect.style.display = 'none';
+  } else {
+    configSelect.style.display = 'inline';
+  }
+
+  // Don't show the mutation counts box if running a Setup* benchmark.
+  if (benchmarkSelect.value.startsWith('Setup')) {
+    mutationCountWrapper.style.display = 'none';
+  } else {
+    mutationCountWrapper.style.display = 'inline';
+  }
+}
diff --git a/benchmark/index.html b/benchmark/index.html
new file mode 100644
index 0000000..ce2bf9f
--- /dev/null
+++ b/benchmark/index.html
@@ -0,0 +1,73 @@
+<html>
+<!--
+Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
+This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
+The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
+The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
+Code distributed by Google as part of the polymer project is also
+subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
+-->
+<head>
+  <title>Observation Benchmarks</title>
+  <meta charset="utf-8">
+  <style>
+    * {
+      font-family: arial, helvetica, sans-serif;
+      font-weight: 400;
+    }
+    body {
+      margin: 0;
+      padding: 0;
+      background-color: rgba(0, 0, 0, .1);
+    }
+    #canvasWrapper {
+      width: 800px;
+      height: 400px;
+    }
+  </style>
+</head>
+<body>
+<h1>Observation Benchmarks</h1>
+
+<select id="benchmarkSelect">
+  <option>ObjectBenchmark</option>
+  <option>SetupObjectBenchmark</option>
+  <option>ObservableListBenchmark</option>
+  <option>SetupObservableListBenchmark</option>
+  <option>PathBenchmark</option>
+  <option>SetupPathBenchmark</option>
+</select>
+<select id="configSelect">
+</select>
+
+<button id="go">Run Benchmarks</button>
+
+<span>Object Count: </span>
+<input id="objectCountInput" style="width: 200px" value="4000, 8000, 16000, 32000">
+<span id="mutationCountWrapper">
+  <span>Mutation Count: </span>
+  <input id="mutationCountInput" style="width: 200px"
+         value="0, 100, 200, 400, 800, 1600"><br>
+</span>
+<br>
+<span id="status"></span>
+
+<section style="width: 100%">
+  <article>
+    <div style="display:inline-block; padding-bottom: 20px">
+      Times in ms
+    </div>
+    <div id="canvasWrapper" style="display:inline-block">
+    </div>
+    <div style="display:inline-block">
+      <ul id="legendList">
+      </ul>
+    </div>
+  </article>
+</section>
+<h3 style="margin-left: 440px">Object Set Size</h3>
+<script type="application/dart" src="index.dart"></script>
+<script src="packages/browser/dart.js">
+</script>
+</body>
+</html>
diff --git a/benchmark/object_benchmark.dart b/benchmark/object_benchmark.dart
new file mode 100644
index 0000000..91282be
--- /dev/null
+++ b/benchmark/object_benchmark.dart
@@ -0,0 +1,28 @@
+// 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 observe.test.benchmark.object_benchmark;
+
+import 'observation_benchmark_base.dart';
+import 'test_observable.dart';
+
+class ObjectBenchmark extends ObservationBenchmarkBase<TestObservable> {
+  ObjectBenchmark(int objectCount, int mutationCount, String config)
+      : super('ObjectBenchmark:$objectCount:$mutationCount:$config',
+            objectCount, mutationCount, config);
+
+  @override
+  int mutateObject(TestObservable obj) {
+    // Modify the first 5 properties.
+    obj.a++;
+    obj.b++;
+    obj.c++;
+    obj.d++;
+    obj.e++;
+    // Return # of modifications.
+    return 5;
+  }
+
+  @override
+  TestObservable newObject() => new TestObservable();
+}
diff --git a/benchmark/observable_list_benchmark.dart b/benchmark/observable_list_benchmark.dart
new file mode 100644
index 0000000..d0f8ad9
--- /dev/null
+++ b/benchmark/observable_list_benchmark.dart
@@ -0,0 +1,58 @@
+// 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 observe.test.benchmark.observable_list_benchmark;
+
+import 'package:observable/observable.dart';
+import 'observation_benchmark_base.dart';
+
+class ObservableListBenchmark extends ObservationBenchmarkBase<ObservableList> {
+  final int elementCount = 100;
+
+  ObservableListBenchmark(int objectCount, int mutationCount, String config)
+      : super('ObservableListBenchmark:$objectCount:$mutationCount:$config',
+            objectCount, mutationCount, config);
+
+  @override
+  int mutateObject(ObservableList obj) {
+    switch (config) {
+      case 'update':
+        var size = (elementCount / 10).floor();
+        for (var j = 0; j < size; j++) {
+          obj[j * size]++;
+        }
+        return size;
+
+      case 'splice':
+        var size = (elementCount / 5).floor();
+        // No splice equivalent in List, so we hardcode it.
+        var removed = obj.sublist(size, size * 2);
+        obj.removeRange(size, size * 2);
+        obj.insertAll(size * 2, removed);
+        return size * 2;
+
+      case 'push/pop':
+        var val = obj.removeLast();
+        obj.add(val + 1);
+        return 2;
+
+      case 'shift/unshift':
+        var val = obj.removeAt(0);
+        obj.insert(0, val + 1);
+        return 2;
+
+      default:
+        throw new ArgumentError(
+            'Invalid config for ObservableListBenchmark: $config');
+    }
+  }
+
+  @override
+  ObservableList newObject() {
+    var list = new ObservableList();
+    for (int i = 0; i < elementCount; i++) {
+      list.add(i);
+    }
+    return list;
+  }
+}
diff --git a/benchmark/observation_benchmark_base.dart b/benchmark/observation_benchmark_base.dart
new file mode 100644
index 0000000..f8fc983
--- /dev/null
+++ b/benchmark/observation_benchmark_base.dart
@@ -0,0 +1,120 @@
+// 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 observe.test.benchmark.observation_benchmark_base;
+
+import 'dart:async';
+import 'dart:html';
+
+import 'package:observable/observable.dart';
+import 'package:observe/observe.dart';
+import 'package:benchmark_harness/benchmark_harness.dart';
+
+abstract class ObservationBenchmarkBase<T extends Observable>
+    extends BenchmarkBase {
+  /// The number of objects to create and observe.
+  final int objectCount;
+
+  /// The number of mutations to perform.
+  final int mutationCount;
+
+  /// The current configuration.
+  final String config;
+
+  /// The number of pending mutations left to observe.
+  int mutations;
+
+  /// The objects we want to observe.
+  List<T> objects;
+
+  /// The change listeners on all of our objects.
+  List observers;
+
+  /// The current object being mutated.
+  int objectIndex;
+
+  ObservationBenchmarkBase(
+      String name, this.objectCount, this.mutationCount, this.config)
+      : super(name);
+
+  /// Subclasses should use this method to perform mutations on an object. The
+  /// return value indicates how many mutations were performed on the object.
+  int mutateObject(T obj);
+
+  /// Subclasses should use this method to return an observable object to be
+  /// benchmarked.
+  T newObject();
+
+  /// Subclasses should override this to do anything other than a default change
+  /// listener. It must return either a StreamSubscription or a PathObserver.
+  /// If overridden this observer should decrement [mutations] each time a
+  /// change is observed.
+  newObserver(T obj) {
+    decrement(_) => mutations--;
+    if (obj is ObservableList) return obj.listChanges.listen(decrement);
+    return obj.changes.listen(decrement);
+  }
+
+  /// Set up each benchmark by creating all the objects and listeners.
+  @override
+  void setup() {
+    mutations = 0;
+
+    objects = [];
+    observers = [];
+    objectIndex = 0;
+
+    while (objects.length < objectCount) {
+      var obj = newObject();
+      objects.add(obj);
+      observers.add(newObserver(obj));
+    }
+  }
+
+  /// Tear down each benchmark and make sure that [mutations] is 0.
+  @override
+  void teardown() {
+    if (mutations != 0) {
+      window.alert('$mutations mutation sets were not observed!');
+    }
+    mutations = 0;
+
+    while (observers.isNotEmpty) {
+      var observer = observers.removeLast();
+      if (observer is StreamSubscription) {
+        observer.cancel();
+      } else if (observer is PathObserver) {
+        observer.close();
+      } else {
+        throw 'Unknown observer type ${observer.runtimeType}. Only '
+            '[PathObserver] and [StreamSubscription] are supported.';
+      }
+    }
+    observers = null;
+
+    bool leakedObservers = false;
+    while (objects.isNotEmpty) {
+      leakedObservers = objects.removeLast().hasObservers || leakedObservers;
+    }
+    if (leakedObservers) window.alert('Observers leaked!');
+    objects = null;
+  }
+
+  /// Run the benchmark
+  @override
+  void run() {
+    var mutationsLeft = mutationCount;
+    while (mutationsLeft > 0) {
+      var obj = objects[objectIndex];
+      mutationsLeft -= mutateObject(obj);
+      this.mutations++;
+      this.objectIndex++;
+      if (this.objectIndex == this.objects.length) {
+        this.objectIndex = 0;
+      }
+      obj.deliverChanges();
+      if (obj is ObservableList) obj.deliverListChanges();
+    }
+    AutoObservable.dirtyCheck();
+  }
+}
diff --git a/benchmark/path_benchmark.dart b/benchmark/path_benchmark.dart
new file mode 100644
index 0000000..a612c55
--- /dev/null
+++ b/benchmark/path_benchmark.dart
@@ -0,0 +1,42 @@
+// 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 observe.test.benchmark.path_benchmark;
+
+import 'package:observe/observe.dart';
+import 'observation_benchmark_base.dart';
+import 'test_path_observable.dart';
+
+class PathBenchmark extends ObservationBenchmarkBase<TestPathObservable> {
+  final PropertyPath path = new PropertyPath('foo.bar.baz');
+
+  PathBenchmark(int objectCount, int mutationCount, String config)
+      : super('PathBenchmark:$objectCount:$mutationCount:$config', objectCount,
+            mutationCount, config);
+
+  @override
+  int mutateObject(TestPathObservable obj) {
+    switch (config) {
+      case 'leaf':
+        obj.foo.bar.baz += 1;
+        // Make sure [obj.foo.bar] delivers its changes synchronously. The base
+        // class already handles this for [obj].
+        obj.foo.bar.deliverChanges();
+        return 1;
+
+      case 'root':
+        obj.foo = new Foo(obj.foo.bar.baz + 1);
+        return 1;
+
+      default:
+        throw new ArgumentError('Invalid config for PathBenchmark: $config');
+    }
+  }
+
+  @override
+  TestPathObservable newObject() => new TestPathObservable(1);
+
+  @override
+  PathObserver newObserver(TestPathObservable obj) =>
+      new PathObserver(obj, path)..open((_) => mutations--);
+}
diff --git a/benchmark/setup_object_benchmark.dart b/benchmark/setup_object_benchmark.dart
new file mode 100644
index 0000000..6fda9fc
--- /dev/null
+++ b/benchmark/setup_object_benchmark.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.
+library observe.test.benchmark.setup_object_benchmark;
+
+import 'setup_observation_benchmark_base.dart';
+import 'test_observable.dart';
+
+class SetupObjectBenchmark extends SetupObservationBenchmarkBase {
+  SetupObjectBenchmark(int objectCount, String config)
+      : super('SetupObjectBenchmark:$objectCount:$config', objectCount, config);
+
+  @override
+  TestObservable newObject() => new TestObservable();
+}
diff --git a/benchmark/setup_observable_list_benchmark.dart b/benchmark/setup_observable_list_benchmark.dart
new file mode 100644
index 0000000..b40aced
--- /dev/null
+++ b/benchmark/setup_observable_list_benchmark.dart
@@ -0,0 +1,26 @@
+// 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 observe.test.benchmark.setup_observable_list_benchmark;
+
+import 'package:observable/observable.dart';
+import 'setup_observation_benchmark_base.dart';
+
+class SetupObservableListBenchmark extends SetupObservationBenchmarkBase {
+  final int elementCount = 100;
+
+  SetupObservableListBenchmark(int objectCount, String config)
+      : super('SetupObservableListBenchmark:$objectCount:$config', objectCount,
+            config);
+
+  @override
+  ObservableList newObject() {
+    var list = new ObservableList();
+    for (int i = 0; i < elementCount; i++) {
+      list.add(i);
+    }
+    list.deliverChanges();
+    list.deliverListChanges();
+    return list;
+  }
+}
diff --git a/benchmark/setup_observation_benchmark_base.dart b/benchmark/setup_observation_benchmark_base.dart
new file mode 100644
index 0000000..2b26b72
--- /dev/null
+++ b/benchmark/setup_observation_benchmark_base.dart
@@ -0,0 +1,76 @@
+// 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 observe.test.benchmark.setup_observation_benchmark_base;
+
+import 'dart:async';
+import 'dart:html';
+import 'package:observable/observable.dart';
+import 'package:observe/observe.dart';
+import 'package:benchmark_harness/benchmark_harness.dart';
+
+abstract class SetupObservationBenchmarkBase<T extends Observable>
+    extends BenchmarkBase {
+  /// The number of objects to create and observe.
+  final int objectCount;
+
+  /// The current configuration.
+  final String config;
+
+  /// The objects we want to observe.
+  List<T> objects;
+
+  SetupObservationBenchmarkBase(String name, this.objectCount, this.config)
+      : super(name);
+
+  /// Subclasses should use this method to return an observable object to be
+  /// benchmarked.
+  T newObject();
+
+  /// Subclasses should override this to do anything other than a default change
+  /// listener. It must return either a StreamSubscription or a PathObserver.
+  newObserver(T obj) => obj.changes.listen((_) {});
+
+  /// Set up each benchmark by creating all the objects.
+  @override
+  void setup() {
+    objects = [];
+    while (objects.length < objectCount) {
+      objects.add(newObject());
+    }
+  }
+
+  /// Tear down each the benchmark and remove all listeners.
+  @override
+  void teardown() {
+    while (objects.isNotEmpty) {
+      var obj = objects.removeLast();
+      if (obj.hasObservers || (obj is ObservableList && obj.hasListObservers)) {
+        window.alert('Observers leaked!');
+      }
+    }
+    objects = null;
+  }
+
+  /// Run the benchmark by creating a listener on each object.
+  @override
+  void run() {
+    for (var object in objects) {
+      var observer = newObserver(object);
+
+      // **Note:** This is different than the JS implementation. Since run can
+      // be called an arbitrary number of times between [setup] and [teardown],
+      // we clean up all observers as we go. This means we are measuring both
+      // the setup and teardown of observers, versus the setup only in the
+      // JS benchmark. Not cleaning these up ends up giving `oh snap` errors.
+      if (observer is StreamSubscription) {
+        observer.cancel();
+      } else if (observer is PathObserver) {
+        observer.close();
+      } else {
+        throw 'Unknown observer type ${observer.runtimeType}. Only '
+            '[PathObserver] and [StreamSubscription] are supported.';
+      }
+    }
+  }
+}
diff --git a/benchmark/setup_path_benchmark.dart b/benchmark/setup_path_benchmark.dart
new file mode 100644
index 0000000..72bba82
--- /dev/null
+++ b/benchmark/setup_path_benchmark.dart
@@ -0,0 +1,23 @@
+// 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 observe.test.benchmark.setup_path_benchmark;
+
+import 'package:observe/observe.dart';
+import 'setup_observation_benchmark_base.dart';
+import 'test_path_observable.dart';
+
+class SetupPathBenchmark
+    extends SetupObservationBenchmarkBase<TestPathObservable> {
+  final PropertyPath path = new PropertyPath('foo.bar.baz');
+
+  SetupPathBenchmark(int objectCount, String config)
+      : super('SetupPathBenchmark:$objectCount:$config', objectCount, config);
+
+  @override
+  TestPathObservable newObject() => new TestPathObservable(1);
+
+  @override
+  PathObserver newObserver(TestPathObservable obj) =>
+      new PathObserver(obj, path)..open(() {});
+}
diff --git a/benchmark/test_observable.dart b/benchmark/test_observable.dart
new file mode 100644
index 0000000..15ef2d7
--- /dev/null
+++ b/benchmark/test_observable.dart
@@ -0,0 +1,39 @@
+// 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 observe.test.benchmark.test_observable;
+
+import 'package:observe/observe.dart';
+
+class TestObservable extends AutoObservable {
+  @observable
+  int a = 0;
+  @observable
+  int b = 0;
+  @observable
+  int c = 0;
+  @observable
+  int d = 0;
+  @observable
+  int e = 0;
+  @observable
+  int f = 0;
+  @observable
+  int g = 0;
+  @observable
+  int h = 0;
+  @observable
+  int i = 0;
+  @observable
+  int j = 0;
+  @observable
+  int k = 0;
+  @observable
+  int l = 0;
+  @observable
+  int m = 0;
+  @observable
+  int n = 0;
+  @observable
+  int o = 0;
+}
diff --git a/benchmark/test_path_observable.dart b/benchmark/test_path_observable.dart
new file mode 100644
index 0000000..6675822
--- /dev/null
+++ b/benchmark/test_path_observable.dart
@@ -0,0 +1,27 @@
+// 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 observe.test.benchmark.test_observable;
+
+import 'package:observe/observe.dart';
+
+class Bar extends AutoObservable {
+  @observable
+  int baz;
+
+  Bar(this.baz);
+}
+
+class Foo extends AutoObservable {
+  @observable
+  Bar bar;
+
+  Foo(int value) : bar = new Bar(value);
+}
+
+class TestPathObservable extends AutoObservable {
+  @observable
+  Foo foo;
+
+  TestPathObservable(int value) : foo = new Foo(value);
+}
diff --git a/lib/html.dart b/lib/html.dart
index e29b0db..5000a78 100644
--- a/lib/html.dart
+++ b/lib/html.dart
@@ -19,7 +19,7 @@
 /// An observable version of [window.location.hash].
 final ObservableLocationHash windowLocation = new ObservableLocationHash._();
 
-class ObservableLocationHash extends PropertyChangeNotifier {
+class ObservableLocationHash extends Observable {
   Object _currentHash;
 
   ObservableLocationHash._() {
diff --git a/lib/src/auto_observable.dart b/lib/src/auto_observable.dart
index 542bccc..bcb486a 100644
--- a/lib/src/auto_observable.dart
+++ b/lib/src/auto_observable.dart
@@ -13,7 +13,7 @@
 import 'dirty_check.dart' show dirtyCheckObservables, registerObservable;
 import 'metadata.dart' show ObservableProperty;
 
-abstract class AutoObservable implements ChangeNotifier {
+abstract class AutoObservable implements Observable {
   /// Performs dirty checking of objects that inherit from [AutoObservable].
   /// This scans all observed objects using mirrors and determines if any fields
   /// have changed. If they have, it delivers the changes for the object.
@@ -147,8 +147,8 @@
   /// - Unlike [Observable] this will not schedule [deliverChanges]; use
   ///   [AutoObservable.dirtyCheck] instead.
   @override
-  void notifyChange([ChangeRecord record]) {
-    if (!hasObservers || record == null) return;
+  void notifyChange(ChangeRecord record) {
+    if (!hasObservers) return;
 
     if (_records == null) _records = [];
     _records.add(record);
diff --git a/lib/src/list_path_observer.dart b/lib/src/list_path_observer.dart
index c329103..09f1b6c 100644
--- a/lib/src/list_path_observer.dart
+++ b/lib/src/list_path_observer.dart
@@ -14,7 +14,7 @@
 
 /// Observes a path starting from each item in the list.
 @deprecated
-class ListPathObserver<E, P> extends PropertyChangeNotifier {
+class ListPathObserver<E, P> extends Observable {
   final ObservableList<E> list;
   final String _itemPath;
   final List<PathObserver> _observers = <PathObserver>[];
diff --git a/lib/src/metadata.dart b/lib/src/metadata.dart
index 15e7905..6811332 100644
--- a/lib/src/metadata.dart
+++ b/lib/src/metadata.dart
@@ -37,7 +37,7 @@
 /// it available to `PathObserver` at runtime. For example:
 ///
 ///     @reflectable
-///     class Monster extends AutoObservable {
+///     class Monster extends Observable {
 ///       int _health;
 ///       int get health => _health;
 ///       ...
diff --git a/lib/src/observable_box.dart b/lib/src/observable_box.dart
index 5749890..51c584a 100644
--- a/lib/src/observable_box.dart
+++ b/lib/src/observable_box.dart
@@ -13,7 +13,7 @@
 /// value. For other cases, it is better to use [AutoObservableList],
 /// [AutoObservableMap], or a custom [AutoObservable] implementation based on
 /// [AutoObservable]. The property name for changes is "value".
-class ObservableBox<T> extends PropertyChangeNotifier {
+class ObservableBox<T> extends Observable {
   T _value;
 
   ObservableBox([T initialValue]) : _value = initialValue;
diff --git a/pubspec.yaml b/pubspec.yaml
index e9a4b36..e74974a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: observe
-version: 0.15.0
+version: 0.14.0
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 description: >
   Observable properties and objects for use in template_binding.
@@ -13,7 +13,7 @@
   barback: '>=0.14.2 <0.16.0'
   func: ^0.1.0
   logging: '>=0.9.0 <0.12.0'
-  observable: '^0.17.0'
+  observable: '^0.14.0'
   path: '>=0.9.0 <2.0.0'
   smoke: '>=0.1.0 <0.4.0'
   source_maps: '>=0.9.4 <0.11.0'
diff --git a/test/list_path_observer_test.dart b/test/list_path_observer_test.dart
index f0e89b5..e3df022 100644
--- a/test/list_path_observer_test.dart
+++ b/test/list_path_observer_test.dart
@@ -85,7 +85,7 @@
 _nextMicrotask(_) => new Future(() {});
 
 @reflectable
-class TestModel extends PropertyChangeNotifier {
+class TestModel extends Observable {
   var _a, _b;
   TestModel();
 
diff --git a/test/path_observer_test.dart b/test/path_observer_test.dart
index bc7731a..ba9cc22 100644
--- a/test/path_observer_test.dart
+++ b/test/path_observer_test.dart
@@ -175,8 +175,7 @@
   });
 
   test('get value at path ObservableBox', () {
-    var obj =
-        new ObservableBox<dynamic>(new ObservableBox(new ObservableBox(1)));
+    var obj = new ObservableBox(new ObservableBox(new ObservableBox(1)));
 
     expect(new PathObserver(obj, '').value, obj);
     expect(new PathObserver(obj, 'value').value, obj.value);
@@ -189,7 +188,7 @@
     obj.value.value = new ObservableBox(3);
     expect(new PathObserver(obj, 'value.value.value').value, 3);
 
-    obj.value = new ObservableBox<int>(4);
+    obj.value = new ObservableBox(4);
     expect(() => new PathObserver(obj, 'value.value.value').value,
         _throwsNSM('value'));
     expect(new PathObserver(obj, 'value.value').value, 4);
@@ -754,7 +753,7 @@
 }
 
 @reflectable
-class TestModel extends ChangeNotifier implements WatcherModel {
+class TestModel extends Observable implements WatcherModel {
   var _a, _b, _c;
 
   TestModel([this._a, this._b, this._c]);
@@ -776,15 +775,6 @@
   void set c(newValue) {
     _c = notifyPropertyChange(#c, _c, newValue);
   }
-
-  @override
-  /*=T*/ notifyPropertyChange/*<T>*/(
-      Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
-    if (hasObservers && oldValue != newValue) {
-      notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
-    }
-    return newValue;
-  }
 }
 
 class WatcherModel extends AutoObservable {