Make the widgets binding reusable. (#3479)

Previously the widgets layer only provided a concrete binding, which
makes it awkward to extend it compared to other bindings. This moves
widgets to the same style as the other layers.

In a subsequent patch I'll use this to make the tests layer saner.
diff --git a/examples/layers/widgets/spinning_mixed.dart b/examples/layers/widgets/spinning_mixed.dart
index b6e1e7a..e7c094b 100644
--- a/examples/layers/widgets/spinning_mixed.dart
+++ b/examples/layers/widgets/spinning_mixed.dart
@@ -89,7 +89,7 @@
 }
 
 void main() {
-  WidgetFlutterBinding.ensureInitialized();
+  Widgeteer binding = WidgetFlutterBinding.ensureInitialized();
   RenderProxyBox proxy = new RenderProxyBox();
   attachWidgetTreeToRenderTree(proxy);
 
@@ -101,6 +101,6 @@
   transformBox = new RenderTransform(child: flexRoot, transform: new Matrix4.identity());
   RenderPadding root = new RenderPadding(padding: new EdgeInsets.all(80.0), child: transformBox);
 
-  WidgetFlutterBinding.instance.renderView.child = root;
-  WidgetFlutterBinding.instance.addPersistentFrameCallback(rotate);
+  binding.renderView.child = root;
+  binding.addPersistentFrameCallback(rotate);
 }
diff --git a/packages/flutter/benchmark/stocks/build_bench.dart b/packages/flutter/benchmark/stocks/build_bench.dart
index 8a623b8..41c35cf 100644
--- a/packages/flutter/benchmark/stocks/build_bench.dart
+++ b/packages/flutter/benchmark/stocks/build_bench.dart
@@ -27,14 +27,14 @@
     appState = tester.stateOf(find.byType(stocks.StocksApp));
   });
 
-  WidgetFlutterBinding binding = WidgetFlutterBinding.instance;
+  BuildOwner buildOwner = Widgeteer.instance.buildOwner;
 
   Stopwatch watch = new Stopwatch()
     ..start();
 
   for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) {
     appState.setState(_doNothing);
-    binding.buildOwner.buildDirtyElements();
+    buildOwner.buildDirtyElements();
   }
 
   watch.stop();
diff --git a/packages/flutter/benchmark/stocks/layout_bench.dart b/packages/flutter/benchmark/stocks/layout_bench.dart
index 1ce9f42..720f2af 100644
--- a/packages/flutter/benchmark/stocks/layout_bench.dart
+++ b/packages/flutter/benchmark/stocks/layout_bench.dart
@@ -24,14 +24,14 @@
 
   ViewConfiguration big = const ViewConfiguration(size: const Size(360.0, 640.0));
   ViewConfiguration small = const ViewConfiguration(size: const Size(355.0, 635.0));
-  RenderView renderView = WidgetFlutterBinding.instance.renderView;
+  RenderView renderView = Widgeteer.instance.renderView;
 
   Stopwatch watch = new Stopwatch()
     ..start();
 
   for (int i = 0; i < _kNumberOfIterations || _kRunForever; ++i) {
     renderView.configuration = (i % 2 == 0) ? big : small;
-    WidgetFlutterBinding.instance.pipelineOwner.flushLayout();
+    Renderer.instance.pipelineOwner.flushLayout();
   }
 
   watch.stop();
diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart
index 46fde26..e794300 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -20,9 +20,7 @@
 export 'package:flutter/gestures.dart' show HitTestResult;
 
 /// The glue between the render tree and the Flutter engine.
-abstract class Renderer extends Object with Scheduler, Services
-  implements HitTestable {
-
+abstract class Renderer implements Scheduler, Services, HitTestable {
   @override
   void initInstances() {
     super.initInstances();
@@ -73,6 +71,12 @@
     });
   }
 
+  /// Creates a [RenderView] object to be the root of the
+  /// [RenderObject] rendering tree, and initializes it so that it
+  /// will be rendered when the engine is next ready to display a
+  /// frame.
+  ///
+  /// Called automatically when the binding is created.
   void initRenderView() {
     if (renderView == null) {
       renderView = new RenderView();
@@ -88,6 +92,8 @@
   /// The render tree that's attached to the output surface.
   RenderView get renderView => _renderView;
   RenderView _renderView;
+  /// Sets the given [RenderView] object (which must not be null), and its tree, to
+  /// be the new render tree to display. The previous tree, if any, is detached.
   void set renderView(RenderView value) {
     assert(value != null);
     if (_renderView == value)
@@ -98,11 +104,17 @@
     _renderView.attach(pipelineOwner);
   }
 
+  /// Invoked when the system metrics change.
+  ///
+  /// See [ui.window.onMetricsChanged].
   void handleMetricsChanged() {
     assert(renderView != null);
     renderView.configuration = new ViewConfiguration(size: ui.window.size);
   }
 
+  /// Prepares the rendering library to handle semantics requests from the engine.
+  ///
+  /// Called automatically when the binding is created.
   void initSemantics() {
     SemanticsNode.onSemanticsEnabled = renderView.scheduleInitialSemantics;
     shell.provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
@@ -116,6 +128,8 @@
   }
 
   /// Pump the rendering pipeline to generate a frame.
+  ///
+  /// Called automatically by the engine when it is time to lay out and paint a frame.
   void beginFrame() {
     assert(renderView != null);
     pipelineOwner.flushLayout();
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 41ff9d2..6e890a3 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -98,7 +98,7 @@
   WidgetsAppState<WidgetsApp> createState() => new WidgetsAppState<WidgetsApp>();
 }
 
-class WidgetsAppState<T extends WidgetsApp> extends State<T> implements BindingObserver {
+class WidgetsAppState<T extends WidgetsApp> extends State<T> implements WidgetsBindingObserver {
 
   GlobalObjectKey _navigator;
 
@@ -109,12 +109,12 @@
     super.initState();
     _navigator = new GlobalObjectKey(this);
     didChangeLocale(ui.window.locale);
-    WidgetFlutterBinding.instance.addObserver(this);
+    Widgeteer.instance.addObserver(this);
   }
 
   @override
   void dispose() {
-    WidgetFlutterBinding.instance.removeObserver(this);
+    Widgeteer.instance.removeObserver(this);
     super.dispose();
   }
 
diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart
index cc9e03e..291dda0 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -15,44 +15,58 @@
 
 export 'dart:ui' show AppLifecycleState, Locale;
 
-class BindingObserver {
+/// Interface for classes that register with the Widgets layer binding.
+///
+/// See [Widgeteer.addObserver] and [Widgeteer.removeObserver].
+abstract class WidgetsBindingObserver {
+  /// Called when the system tells the app to pop the current route.
+  /// For example, on Android, this is called when the user presses
+  /// the back button.
+  ///
+  /// Observers are notified in registration order until one returns
+  /// true. If none return true, the application quits.
+  ///
+  /// Observers are expected to return true if they were able to
+  /// handle the notification, for example by closing an active dialog
+  /// box, and false otherwise. The [WidgetsApp] widget uses this
+  /// mechanism to notify the [Navigator] widget that it should pop
+  /// its current route if possible.
   bool didPopRoute() => false;
+
+  /// Called when the application's dimensions change. For example,
+  /// when a phone is rotated.
   void didChangeMetrics() { }
+
+  /// Called when the system tells the app that the user's locale has
+  /// changed. For example, if the user changes the system language
+  /// settings.
   void didChangeLocale(Locale locale) { }
+
+  /// Called when the system puts the app in the background or returns
+  /// the app to the foreground.
   void didChangeAppLifecycleState(AppLifecycleState state) { }
 }
 
-/// A concrete binding for applications based on the Widgets framework.
-/// This is the glue that binds the framework to the Flutter engine.
-class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Services, Renderer {
-
-  WidgetFlutterBinding() {
-    buildOwner.onBuildScheduled = ensureVisualUpdate;
-  }
-
-  /// Creates and initializes the WidgetFlutterBinding. This constructor is
-  /// idempotent; calling it a second time will just return the
-  /// previously-created instance.
-  static WidgetFlutterBinding ensureInitialized() {
-    if (_instance == null)
-      new WidgetFlutterBinding();
-    return _instance;
-  }
-
-  final BuildOwner _buildOwner = new BuildOwner();
-  /// The [BuildOwner] in charge of executing the build pipeline for the
-  /// widget tree rooted at this binding.
-  BuildOwner get buildOwner => _buildOwner;
-
+/// The glue between the widgets layer and the Flutter engine.
+abstract class Widgeteer implements Gesturer, Renderer {
   @override
   void initInstances() {
     super.initInstances();
     _instance = this;
+    buildOwner.onBuildScheduled = ensureVisualUpdate;
     ui.window.onLocaleChanged = handleLocaleChanged;
     ui.window.onPopRoute = handlePopRoute;
     ui.window.onAppLifecycleStateChanged = handleAppLifecycleStateChanged;
   }
 
+  /// The current [Widgeteer], if one has been created.
+  ///
+  /// If you need the binding to be constructed before calling [runApp],
+  /// you can ensure a Widget binding has been constructed by calling the
+  /// `WidgetFlutterBinding.ensureInitialized()` function.
+  static Widgeteer get instance => _instance;
+  static Widgeteer _instance;
+
   @override
   void initServiceExtensions() {
     super.initServiceExtensions();
@@ -69,48 +83,91 @@
     );
   }
 
-  /// The one static instance of this class.
+  /// The [BuildOwner] in charge of executing the build pipeline for the
+  /// widget tree rooted at this binding.
+  BuildOwner get buildOwner => _buildOwner;
+  final BuildOwner _buildOwner = new BuildOwner();
+
+  final List<WidgetsBindingObserver> _observers = <WidgetsBindingObserver>[];
+
+  /// Registers the given object as a binding observer. Binding
+  /// observers are notified when various application events occur,
+  /// for example when the system locale changes. Generally, one
+  /// widget in the widget tree registers itself as a binding
+  /// observer, and converts the system state into inherited widgets.
   ///
-  /// Only valid after the WidgetFlutterBinding constructor) has been called.
-  /// Only one binding class can be instantiated per process. If another
-  /// BindingBase implementation has been instantiated before this one (e.g.
-  /// bindings from other frameworks based on the Flutter "rendering" library),
-  /// then WidgetFlutterBinding.instance will not be valid (and will throw in
-  /// checked mode).
-  static WidgetFlutterBinding get instance => _instance;
-  static WidgetFlutterBinding _instance;
+  /// For example, the [WidgetsApp] widget registers as a binding
+  /// observer and passes the screen size to a [MediaQuery] widget
+  /// each time it is built, which enables other widgets to use the
+  /// [MediaQuery.of] static method and (implicitly) the
+  /// [InheritedWidget] mechanism to be notified whenever the screen
+  /// size changes (e.g. whenever the screen rotates).
+  void addObserver(WidgetsBindingObserver observer) => _observers.add(observer);
 
-  final List<BindingObserver> _observers = new List<BindingObserver>();
+  /// Unregisters the given observer. This should be used sparingly as
+  /// it is relatively expensive (O(N) in the number of registered
+  /// observers).
+  bool removeObserver(WidgetsBindingObserver observer) => _observers.remove(observer);
 
-  void addObserver(BindingObserver observer) => _observers.add(observer);
-  bool removeObserver(BindingObserver observer) => _observers.remove(observer);
-
+  /// Invoked when the system metrics change.
+  ///
+  /// Notifies all the observers using
+  /// [WidgetsBindingObserver.didChangeMetrics].
+  ///
+  /// See [ui.window.onMetricsChanged].
   @override
   void handleMetricsChanged() {
     super.handleMetricsChanged();
-    for (BindingObserver observer in _observers)
+    for (WidgetsBindingObserver observer in _observers)
       observer.didChangeMetrics();
   }
 
+  /// Invoked when the system locale changes.
+  ///
+  /// Calls [dispatchLocaleChanged] to notify the binding observers.
+  ///
+  /// See [ui.window.onLocaleChanged].
   void handleLocaleChanged() {
     dispatchLocaleChanged(ui.window.locale);
   }
 
+  /// Notify all the observers that the locale has changed (using
+  /// [WidgetsBindingObserver.didChangeLocale]), giving them the
+  /// `locale` argument.
   void dispatchLocaleChanged(Locale locale) {
-    for (BindingObserver observer in _observers)
+    for (WidgetsBindingObserver observer in _observers)
       observer.didChangeLocale(locale);
   }
 
+  /// Invoked when the system pops the current route.
+  ///
+  /// This first notifies the binding observers (using
+  /// [WidgetsBindingObserver.didPopRoute]), in registration order,
+  /// until one returns true, meaning that it was able to handle the
+  /// request (e.g. by closing a dialog box). If none return true,
+  /// then the application is shut down.
+  ///
+  /// [WidgetsApp] uses this in conjunction with a [Navigator] to
+  /// cause the back button to close dialog boxes, return from modal
+  /// pages, and so forth.
+  ///
+  /// See [ui.window.onPopRoute].
   void handlePopRoute() {
-    for (BindingObserver observer in _observers) {
+    for (WidgetsBindingObserver observer in _observers) {
       if (observer.didPopRoute())
         return;
     }
     activity.finishCurrentActivity();
   }
 
+  /// Invoked when the application lifecycle state changes.
+  ///
+  /// Notifies all the observers using
+  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
+  ///
+  /// See [ui.window.onAppLifecycleStateChanged].
   void handleAppLifecycleStateChanged(AppLifecycleState state) {
-    for (BindingObserver observer in _observers)
+    for (WidgetsBindingObserver observer in _observers)
       observer.didChangeAppLifecycleState(state);
   }
 
@@ -123,6 +180,8 @@
 
   /// The [Element] that is at the root of the hierarchy (and which wraps the
   /// [RenderView] object at the root of the rendering hierarchy).
+  ///
+  /// This is initialized the first time [runApp] is called.
   Element get renderViewElement => _renderViewElement;
   Element _renderViewElement;
   void _runApp(Widget app) {
@@ -142,18 +201,20 @@
 }
 
 /// Inflate the given widget and attach it to the screen.
+///
+/// Initializes the binding using [WidgetFlutterBinding] if necessary.
 void runApp(Widget app) {
   WidgetFlutterBinding.ensureInitialized()._runApp(app);
 }
 
 /// Print a string representation of the currently running app.
 void debugDumpApp() {
-  assert(WidgetFlutterBinding.instance != null);
-  assert(WidgetFlutterBinding.instance.renderViewElement != null);
+  assert(Widgeteer.instance != null);
+  assert(Widgeteer.instance.renderViewElement != null);
   String mode = 'RELEASE MODE';
   assert(() { mode = 'CHECKED MODE'; return true; });
-  debugPrint('${WidgetFlutterBinding.instance.runtimeType} - $mode');
-  debugPrint(WidgetFlutterBinding.instance.renderViewElement.toStringDeep());
+  debugPrint('${Widgeteer.instance.runtimeType} - $mode');
+  debugPrint(Widgeteer.instance.renderViewElement.toStringDeep());
 }
 
 /// This class provides a bridge from a RenderObject to an Element tree. The
@@ -259,3 +320,19 @@
     renderObject.child = null;
   }
 }
+
+/// A concrete binding for applications based on the Widgets framework.
+/// This is the glue that binds the framework to the Flutter engine.
+class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, Services, Renderer, Widgeteer {
+  /// Creates and initializes the WidgetFlutterBinding. This function
+  /// is idempotent; calling it a second time will just return the
+  /// previously-created instance.
+  ///
+  /// You only need to call this method if you need the binding to be
+  /// initialized before calling [runApp].
+  static WidgetFlutterBinding ensureInitialized() {
+    if (Widgeteer.instance == null)
+      new WidgetFlutterBinding();
+    return Widgeteer.instance;
+  }
+}
diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart
index defc875..c76c0d3 100644
--- a/packages/flutter/lib/src/widgets/drag_target.dart
+++ b/packages/flutter/lib/src/widgets/drag_target.dart
@@ -417,7 +417,7 @@
     _lastOffset = globalPosition - dragStartPoint;
     _entry.markNeedsBuild();
     HitTestResult result = new HitTestResult();
-    WidgetFlutterBinding.instance.hitTest(result, globalPosition + feedbackOffset);
+    Widgeteer.instance.hitTest(result, globalPosition + feedbackOffset);
 
     List<_DragTargetState<T>> targets = _getDragTargets(result.path).toList();
 
diff --git a/packages/flutter_driver/lib/src/extension.dart b/packages/flutter_driver/lib/src/extension.dart
index eaac038..e6e0c9e 100644
--- a/packages/flutter_driver/lib/src/extension.dart
+++ b/packages/flutter_driver/lib/src/extension.dart
@@ -21,7 +21,7 @@
 const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
 const Duration _kDefaultTimeout = const Duration(seconds: 5);
 
-class _DriverBinding extends WidgetFlutterBinding {
+class _DriverBinding extends WidgetFlutterBinding { // TODO(ianh): refactor so we're not extending a concrete binding
   @override
   void initServiceExtensions() {
     super.initServiceExtensions();
@@ -41,9 +41,9 @@
 /// Call this function prior to running your application, e.g. before you call
 /// `runApp`.
 void enableFlutterDriverExtension() {
-  assert(WidgetFlutterBinding.instance == null);
+  assert(Widgeteer.instance == null);
   new _DriverBinding();
-  assert(WidgetFlutterBinding.instance is _DriverBinding);
+  assert(Widgeteer.instance is _DriverBinding);
 }
 
 /// Handles a command and returns a result.
diff --git a/packages/flutter_markdown/test/flutter_markdown_test.dart b/packages/flutter_markdown/test/flutter_markdown_test.dart
index 942ab53..975a3f5 100644
--- a/packages/flutter_markdown/test/flutter_markdown_test.dart
+++ b/packages/flutter_markdown/test/flutter_markdown_test.dart
@@ -100,9 +100,9 @@
       tester.pumpWidget(new Markdown(data: "Data1"));
       _expectTextStrings(tester.widgets, <String>["Data1"]);
 
-      String stateBefore = WidgetFlutterBinding.instance.renderViewElement.toStringDeep();
+      String stateBefore = Widgeteer.instance.renderViewElement.toStringDeep();
       tester.pumpWidget(new Markdown(data: "Data1"));
-      String stateAfter = WidgetFlutterBinding.instance.renderViewElement.toStringDeep();
+      String stateAfter = Widgeteer.instance.renderViewElement.toStringDeep();
       expect(stateBefore, equals(stateAfter));
 
       tester.pumpWidget(new Markdown(data: "Data2"));
@@ -119,9 +119,9 @@
 
       tester.pumpWidget(new Markdown(data: "Test", markdownStyle: style1));
 
-      String stateBefore = WidgetFlutterBinding.instance.renderViewElement.toStringDeep();
+      String stateBefore = Widgeteer.instance.renderViewElement.toStringDeep();
       tester.pumpWidget(new Markdown(data: "Test", markdownStyle: style2));
-      String stateAfter = WidgetFlutterBinding.instance.renderViewElement.toStringDeep();
+      String stateAfter = Widgeteer.instance.renderViewElement.toStringDeep();
       expect(stateBefore, isNot(stateAfter));
     });
   });
diff --git a/packages/flutter_test/lib/flutter_test.dart b/packages/flutter_test/lib/flutter_test.dart
index 81a1550..5883d9b 100644
--- a/packages/flutter_test/lib/flutter_test.dart
+++ b/packages/flutter_test/lib/flutter_test.dart
@@ -5,7 +5,7 @@
 /// Testing library for flutter, built on top of `package:test`.
 library flutter_test;
 
-export 'src/element_tree_tester.dart';
+export 'src/binding.dart';
 export 'src/instrumentation.dart';
 export 'src/service_mocker.dart';
 export 'src/test_pointer.dart';
diff --git a/packages/flutter_test/lib/src/element_tree_tester.dart b/packages/flutter_test/lib/src/binding.dart
similarity index 95%
rename from packages/flutter_test/lib/src/element_tree_tester.dart
rename to packages/flutter_test/lib/src/binding.dart
index 3b4be16..41d579c 100644
--- a/packages/flutter_test/lib/src/element_tree_tester.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -23,18 +23,18 @@
   sendSemanticsTree
 }
 
-class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding {
-  _SteppedWidgetFlutterBinding._(this.async);
+class _SteppedWidgetFlutterBinding extends WidgetFlutterBinding { // TODO(ianh): refactor so we're not extending a concrete binding
+  _SteppedWidgetFlutterBinding(this.async);
 
   final FakeAsync async;
 
   /// Creates and initializes the binding. This constructor is
   /// idempotent; calling it a second time will just return the
   /// previously-created instance.
-  static WidgetFlutterBinding ensureInitialized(FakeAsync async) {
-    if (WidgetFlutterBinding.instance == null)
-      new _SteppedWidgetFlutterBinding._(async);
-    return WidgetFlutterBinding.instance;
+  static Widgeteer ensureInitialized(FakeAsync async) {
+    if (Widgeteer.instance == null)
+      new _SteppedWidgetFlutterBinding(async);
+    return Widgeteer.instance;
   }
 
   EnginePhase phase = EnginePhase.sendSemanticsTree;
diff --git a/packages/flutter_test/lib/src/instrumentation.dart b/packages/flutter_test/lib/src/instrumentation.dart
index 19c9ce6..d0d774e 100644
--- a/packages/flutter_test/lib/src/instrumentation.dart
+++ b/packages/flutter_test/lib/src/instrumentation.dart
@@ -17,10 +17,10 @@
 /// This class provides hooks for accessing the rendering tree and dispatching
 /// fake tap/drag/etc. events.
 class Instrumentation {
-  Instrumentation({ WidgetFlutterBinding binding })
+  Instrumentation({ Widgeteer binding })
     : this.binding = binding ?? WidgetFlutterBinding.ensureInitialized();
 
-  final WidgetFlutterBinding binding;
+  final Widgeteer binding;
 
   /// Returns a list of all the [Layer] objects in the rendering.
   List<Layer> get layers => _layers(binding.renderView.layer);
diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart
index 2faa639..faac1c5 100644
--- a/packages/flutter_test/lib/src/widget_tester.dart
+++ b/packages/flutter_test/lib/src/widget_tester.dart
@@ -7,7 +7,7 @@
 import 'package:quiver/testing/async.dart';
 import 'package:test/test.dart';
 
-import 'element_tree_tester.dart';
+import 'binding.dart';
 import 'test_pointer.dart';
 
 /// Runs the [callback] inside the Flutter test environment.
@@ -57,7 +57,7 @@
   /// Exposes the [Element] tree created from widgets.
   final ElementTreeTester elementTreeTester;
 
-  WidgetFlutterBinding get binding => elementTreeTester.binding;
+  Widgeteer get binding => elementTreeTester.binding;
 
   /// Renders the UI from the given [widget].
   ///