Merge pull request #1493 from Hixie/semantics

Semantics updates
diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart
index 1d21784..f9a0b86 100644
--- a/packages/flutter/lib/services.dart
+++ b/packages/flutter/lib/services.dart
@@ -21,4 +21,4 @@
 export 'src/services/keyboard.dart';
 export 'src/services/print.dart';
 export 'src/services/service_registry.dart';
-export 'src/services/shell.dart';
+
diff --git a/packages/flutter/lib/src/http/mojo_client.dart b/packages/flutter/lib/src/http/mojo_client.dart
index 14cea26..ce579da 100644
--- a/packages/flutter/lib/src/http/mojo_client.dart
+++ b/packages/flutter/lib/src/http/mojo_client.dart
@@ -6,7 +6,7 @@
 import 'dart:convert';
 import 'dart:typed_data';
 
-import 'package:flutter/src/services/shell.dart';
+import 'package:flutter/services.dart';
 import 'package:mojo_services/mojo/network_service.mojom.dart' as mojo;
 import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojo;
 import 'package:mojo/core.dart' as mojo;
diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart
index 78046f9..29fa3e2 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -7,6 +7,7 @@
 import 'package:flutter/gestures.dart';
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/services.dart';
+import 'package:mojo/core.dart' as core;
 import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
 
 import 'box.dart';
@@ -18,7 +19,7 @@
 export 'package:flutter/gestures.dart' show HitTestResult;
 
 /// The glue between the render tree and the Flutter engine.
-abstract class Renderer extends Scheduler
+abstract class Renderer extends Object with Scheduler, MojoShell
   implements HitTestable {
 
   void initInstances() {
@@ -26,6 +27,7 @@
     _instance = this;
     ui.window.onMetricsChanged = handleMetricsChanged;
     initRenderView();
+    initSemantics();
     assert(renderView != null);
     assert(() {
       initServiceExtensions();
@@ -41,8 +43,6 @@
     if (renderView == null) {
       renderView = new RenderView();
       renderView.scheduleInitialFrame();
-      if (_semanticsClient != null)
-        renderView.scheduleInitialSemantics();
     }
     handleMetricsChanged(); // configures renderView's metrics
   }
@@ -65,12 +65,11 @@
     renderView.configuration = new ViewConfiguration(size: ui.window.size);
   }
 
-  mojom.SemanticsListener _semanticsClient;
-  void setSemanticsClient(mojom.SemanticsListener client) {
-    assert(_semanticsClient == null);
-    _semanticsClient = client;
-    if (renderView != null)
-      renderView.scheduleInitialSemantics();
+  void initSemantics() {
+    SemanticsNode.onSemanticsEnabled = renderView.scheduleInitialSemantics;
+    provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
+      return new SemanticsServer();
+    });
   }
 
   void _handlePersistentFrameCallback(Duration timeStamp) {
@@ -84,9 +83,9 @@
     RenderObject.flushCompositingBits();
     RenderObject.flushPaint();
     renderView.compositeFrame(); // this sends the bits to the GPU
-    if (_semanticsClient != null) {
+    if (SemanticsNode.hasListeners) {
       RenderObject.flushSemantics();
-      SemanticsNode.sendSemanticsTreeTo(_semanticsClient);
+      SemanticsNode.sendSemanticsTree();
     }
   }
 
@@ -116,7 +115,7 @@
 
 /// A concrete binding for applications that use the Rendering framework
 /// directly. This is the glue that binds the framework to the Flutter engine.
-class RenderingFlutterBinding extends BindingBase with Scheduler, Gesturer, Renderer {
+class RenderingFlutterBinding extends BindingBase with Scheduler, Gesturer, MojoShell, Renderer {
   RenderingFlutterBinding({ RenderBox root }) {
     assert(renderView != null);
     renderView.child = root;
diff --git a/packages/flutter/lib/src/rendering/semantics.dart b/packages/flutter/lib/src/rendering/semantics.dart
index 53adf44..ac27b1d 100644
--- a/packages/flutter/lib/src/rendering/semantics.dart
+++ b/packages/flutter/lib/src/rendering/semantics.dart
@@ -333,7 +333,18 @@
     return result;
   }
 
-  static void sendSemanticsTreeTo(mojom.SemanticsListener client) {
+  static List<mojom.SemanticsListener> _listeners;
+  static bool get hasListeners => _listeners != null && _listeners.length > 0;
+  static VoidCallback onSemanticsEnabled; // set by the binding
+  static void addListener(mojom.SemanticsListener listener) {
+    if (!hasListeners)
+      onSemanticsEnabled();
+    _listeners ??= <mojom.SemanticsListener>[];
+    _listeners.add(listener);
+  }
+
+  static void sendSemanticsTree() {
+    assert(hasListeners);
     for (SemanticsNode oldNode in _detachedNodes) {
       // The other side will have forgotten this node if we even send
       // it again, so make sure to mark it dirty so that it'll get
@@ -377,7 +388,8 @@
       if (node._dirty && node.attached)
         updatedNodes.add(node._serialize());
     }
-    client.updateSemanticsTree(updatedNodes);
+    for (mojom.SemanticsListener listener in _listeners)
+      listener.updateSemanticsTree(updatedNodes);
     _dirtyNodes.clear();
   }
 
@@ -428,6 +440,7 @@
 
 class SemanticsServer extends mojom.SemanticsServer {
   void addSemanticsListener(mojom.SemanticsListener listener) {
+    SemanticsNode.addListener(listener);
   }
   void tap(int nodeID) {
     SemanticsNode.getSemanticActionHandlerForId(nodeID, neededFlag: _SemanticFlags.canBeTapped)?.handleSemanticTap();
diff --git a/packages/flutter/lib/src/services/activity.dart b/packages/flutter/lib/src/services/activity.dart
index 4c3feae..6bba0fb 100644
--- a/packages/flutter/lib/src/services/activity.dart
+++ b/packages/flutter/lib/src/services/activity.dart
@@ -7,7 +7,7 @@
 
 import 'package:sky_services/activity/activity.mojom.dart';
 
-import 'shell.dart';
+import 'binding.dart';
 
 export 'package:sky_services/activity/activity.mojom.dart';
 
diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart
index d7e79a0..48dbe07 100644
--- a/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/packages/flutter/lib/src/services/asset_bundle.dart
@@ -16,7 +16,7 @@
 import 'image_cache.dart';
 import 'image_decoder.dart';
 import 'image_resource.dart';
-import 'shell.dart';
+import 'binding.dart';
 
 abstract class AssetBundle {
   ImageResource loadImage(String key);
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index c9cf8a3..51c0dcf 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -2,6 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:ui_internals' as internals;
+
+import 'package:mojo/application.dart';
+import 'package:mojo/bindings.dart' as bindings;
+import 'package:mojo/core.dart' as core;
+import 'package:mojo/mojo/service_provider.mojom.dart' as mojom;
+import 'package:mojo/mojo/shell.mojom.dart' as mojom;
+
 /// Base class for mixins that provide singleton services (also known as
 /// "bindings").
 ///
@@ -30,3 +38,85 @@
     assert(() { _debugInitialized = true; return true; });
   }
 }
+
+// A replacement for shell.connectToService.  Implementations should return true
+// if they handled the request, or false if the request should fall through
+// to the default requestService.
+typedef bool OverrideConnectToService(String url, Object proxy);
+
+abstract class MojoShell extends BindingBase {
+
+  void initInstances() {
+    super.initInstances();
+    _instance = this;
+  }
+
+  static MojoShell _instance;
+  static MojoShell get instance => _instance;
+
+  static mojom.ShellProxy _initShellProxy() {
+    core.MojoHandle shellHandle = new core.MojoHandle(internals.takeShellProxyHandle());
+    if (!shellHandle.isValid)
+      return null;
+    return new mojom.ShellProxy.fromHandle(shellHandle);
+  }
+  final mojom.Shell _shell = _initShellProxy()?.ptr;
+
+  static ApplicationConnection _initEmbedderConnection() {
+    core.MojoHandle servicesHandle = new core.MojoHandle(internals.takeServicesProvidedByEmbedder());
+    core.MojoHandle exposedServicesHandle = new core.MojoHandle(internals.takeServicesProvidedToEmbedder());
+    if (!servicesHandle.isValid || !exposedServicesHandle.isValid)
+      return null;
+    mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.fromHandle(servicesHandle);
+    mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.fromHandle(exposedServicesHandle);
+    return new ApplicationConnection(exposedServices, services);
+  }
+  final ApplicationConnection _embedderConnection = _initEmbedderConnection();
+
+  /// Attempts to connect to an application via the Mojo shell.
+  ApplicationConnection connectToApplication(String url) {
+    if (_shell == null)
+      return null;
+    mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
+    mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.unbound();
+    _shell.connectToApplication(url, services, exposedServices);
+    return new ApplicationConnection(exposedServices, services);
+  }
+
+  /// Set this to intercept calls to [connectToService()] and supply an
+  /// alternative implementation of a service (for example, a mock for testing).
+  OverrideConnectToService overrideConnectToService;
+
+  /// Attempts to connect to a service implementing the interface for the given proxy.
+  /// If an application URL is specified, the service will be requested from that application.
+  /// Otherwise, it will be requested from the embedder (the Flutter engine).
+  void connectToService(String url, bindings.ProxyBase proxy) {
+    if (overrideConnectToService != null && overrideConnectToService(url, proxy))
+      return;
+    if (url == null || _shell == null) {
+      // If the application URL is null, it means the service to connect
+      // to is one provided by the embedder.
+      // If the applircation URL isn't null but there's no shell, then
+      // ask the embedder in case it provides it. (For example, if you're
+      // running on Android without the Mojo shell, then you can obtain
+      // the media service from the embedder directly, instead of having
+      // to ask the media application for it.)
+      // This makes it easier to write an application that works both
+      // with and without a Mojo environment.
+      _embedderConnection?.requestService(proxy);
+      return;
+    }
+    mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
+    _shell.connectToApplication(url, services, null);
+    core.MojoMessagePipe pipe = new core.MojoMessagePipe();
+    proxy.impl.bind(pipe.endpoints[0]);
+    services.ptr.connectToService(proxy.serviceName, pipe.endpoints[1]);
+    services.close();
+  }
+
+  /// Registers a service to expose to the embedder.
+  void provideService(String interfaceName, ServiceFactory factory) {
+    _embedderConnection?.provideService(interfaceName, factory);
+  }
+}
+MojoShell get shell => MojoShell.instance;
diff --git a/packages/flutter/lib/src/services/keyboard.dart b/packages/flutter/lib/src/services/keyboard.dart
index 8e58165..8f64cad 100644
--- a/packages/flutter/lib/src/services/keyboard.dart
+++ b/packages/flutter/lib/src/services/keyboard.dart
@@ -6,7 +6,7 @@
 
 import 'package:mojo_services/keyboard/keyboard.mojom.dart';
 
-import 'shell.dart';
+import 'binding.dart';
 
 export 'package:mojo_services/keyboard/keyboard.mojom.dart';
 
diff --git a/packages/flutter/lib/src/services/shell.dart b/packages/flutter/lib/src/services/shell.dart
deleted file mode 100644
index c0e3733..0000000
--- a/packages/flutter/lib/src/services/shell.dart
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2015 The Chromium Authors. 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:ui_internals' as internals;
-
-import 'package:mojo/application.dart';
-import 'package:mojo/bindings.dart' as bindings;
-import 'package:mojo/core.dart' as core;
-import 'package:mojo/mojo/service_provider.mojom.dart' as mojom;
-import 'package:mojo/mojo/shell.mojom.dart' as mojom;
-
-// A replacement for shell.connectToService.  Implementations should return true
-// if they handled the request, or false if the request should fall through
-// to the default requestService.
-typedef bool OverrideConnectToService(String url, Object proxy);
-
-class MojoShell {
-  MojoShell._();
-
-  static mojom.ShellProxy _initShellProxy() {
-    core.MojoHandle shellHandle = new core.MojoHandle(internals.takeShellProxyHandle());
-    if (!shellHandle.isValid)
-      return null;
-    return new mojom.ShellProxy.fromHandle(shellHandle);
-  }
-  static final mojom.Shell _shell = _initShellProxy()?.ptr;
-
-  static ApplicationConnection _initEmbedderConnection() {
-    core.MojoHandle servicesHandle = new core.MojoHandle(internals.takeServicesProvidedByEmbedder());
-    core.MojoHandle exposedServicesHandle = new core.MojoHandle(internals.takeServicesProvidedToEmbedder());
-    if (!servicesHandle.isValid || !exposedServicesHandle.isValid)
-      return null;
-    mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.fromHandle(servicesHandle);
-    mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.fromHandle(exposedServicesHandle);
-    return new ApplicationConnection(exposedServices, services);
-  }
-  static final ApplicationConnection _embedderConnection = _initEmbedderConnection();
-
-  ApplicationConnection connectToApplication(String url) {
-    if (_shell == null)
-      return null;
-    mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
-    mojom.ServiceProviderStub exposedServices = new mojom.ServiceProviderStub.unbound();
-    _shell.connectToApplication(url, services, exposedServices);
-    return new ApplicationConnection(exposedServices, services);
-  }
-
-  // Set this to intercept calls to shell.connectToService and supply an
-  // alternative implementation of a service (for example, a mock for testing).
-  OverrideConnectToService overrideConnectToService;
-
-  void connectToService(String url, bindings.ProxyBase proxy) {
-    if (overrideConnectToService != null && overrideConnectToService(url, proxy))
-      return;
-    _connectToService(url, proxy);
-  }
-
-  void _connectToService(String url, bindings.ProxyBase proxy) {
-    if (_shell == null || url == null) {
-      // If we don't have a shell or a url, we try to get the services from the
-      // embedder directly instead of using the shell to connect.
-      _embedderConnection?.requestService(proxy);
-      return;
-    }
-    mojom.ServiceProviderProxy services = new mojom.ServiceProviderProxy.unbound();
-    _shell.connectToApplication(url, services, null);
-    core.MojoMessagePipe pipe = new core.MojoMessagePipe();
-    proxy.impl.bind(pipe.endpoints[0]);
-    services.ptr.connectToService(proxy.serviceName, pipe.endpoints[1]);
-    services.close();
-  }
-}
-final MojoShell shell = new MojoShell._();
diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart
index a73cffb..96ec7f4 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -21,7 +21,7 @@
 
 /// 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, Renderer {
+class WidgetFlutterBinding extends BindingBase with Scheduler, Gesturer, MojoShell, Renderer {
 
   WidgetFlutterBinding._();
 
diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart
index 7616b96..288150b 100644
--- a/packages/flutter/lib/src/widgets/semantics_debugger.dart
+++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart
@@ -31,7 +31,7 @@
   }
   void _update() {
     setState(() {
-      // the generation of the _SemanticsDebuggerClient has changed
+      // the generation of the _SemanticsDebuggerListener has changed
     });
   }
   Point _lastPointerDownLocation;
@@ -214,9 +214,10 @@
 
   _SemanticsDebuggerEntry hitTest(Point position, _SemanticsDebuggerEntryFilter filter) {
     if (transform != null) {
-      if (transform.determinant == 0.0)
+      Matrix4 invertedTransform = new Matrix4.identity();
+      double determinant = invertedTransform.copyInverse(transform);
+      if (determinant == 0.0)
         return null;
-      Matrix4 invertedTransform = new Matrix4.inverted(transform);
       position = MatrixUtils.transformPoint(invertedTransform, position);
     }
     if (!rect.contains(position))
@@ -235,13 +236,12 @@
 
 class _SemanticsDebuggerListener implements mojom.SemanticsListener {
   _SemanticsDebuggerListener._() {
-    Renderer.instance.setSemanticsClient(this);
+    SemanticsNode.addListener(this);
   }
 
   static _SemanticsDebuggerListener instance;
-  static mojom.SemanticsServer _server;
-  static void ensureInstantiated({ mojom.SemanticsServer server }) {
-    _server = server ?? new SemanticsServer();
+  static final SemanticsServer _server = new SemanticsServer();
+  static void ensureInstantiated() {
     instance ??= new _SemanticsDebuggerListener._();
   }
 
diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart
index 2b620ad..fb700b9 100644
--- a/packages/flutter/test/rendering/rendering_tester.dart
+++ b/packages/flutter/test/rendering/rendering_tester.dart
@@ -26,7 +26,7 @@
   composite
 }
 
-class TestRenderingFlutterBinding extends BindingBase with Scheduler, Renderer, Gesturer {
+class TestRenderingFlutterBinding extends BindingBase with Scheduler, MojoShell, Renderer, Gesturer {
   void initRenderView() {
     if (renderView == null) {
       renderView = new TestRenderView();
diff --git a/packages/flutter/test/services/mock_services.dart b/packages/flutter/test/services/mock_services.dart
index 381426c..dd2adb2 100644
--- a/packages/flutter/test/services/mock_services.dart
+++ b/packages/flutter/test/services/mock_services.dart
@@ -1,18 +1,18 @@
-import 'package:flutter/src/services/shell.dart';
+import 'package:flutter/services.dart';
 import 'package:mojo/bindings.dart' as bindings;
 
 // Tests can use ServiceMocker to register replacement implementations
 // of Mojo services.
-class _ServiceMocker {
-  _ServiceMocker() {
+class ServiceMocker {
+  ServiceMocker._() {
     shell.overrideConnectToService = _connectToService;
   }
 
   // Map of interface names to mock implementations.
-  Map<String, Object> _interfaceMock = new Map<String, Object>();
+  Map<String, Object> _interfaceMocks = <String, Object>{};
 
   bool _connectToService(String url, bindings.ProxyBase proxy) {
-    Object mock = _interfaceMock[proxy.serviceName];
+    Object mock = _interfaceMocks[proxy.serviceName];
     if (mock != null) {
       // Replace the proxy's implementation of the service interface with the
       // mock. The mojom bindings put the "ptr" field on all proxies.
@@ -24,9 +24,11 @@
   }
 
   // Provide a mock implementation for a Mojo interface.
+  // Make sure you initialise the binding before calling this.
+  // For example, by calling `WidgetFlutterBinding.ensureInitialized();`
   void registerMockService(String interfaceName, Object mock) {
-    _interfaceMock[interfaceName] = mock;
+    _interfaceMocks[interfaceName] = mock;
   }
 }
 
-final _ServiceMocker serviceMocker = new _ServiceMocker();
+final ServiceMocker serviceMocker = new ServiceMocker._();
diff --git a/packages/flutter/test/widget/input_test.dart b/packages/flutter/test/widget/input_test.dart
index 904025d..dcac27a 100644
--- a/packages/flutter/test/widget/input_test.dart
+++ b/packages/flutter/test/widget/input_test.dart
@@ -28,6 +28,7 @@
 }
 
 void main() {
+  WidgetFlutterBinding.ensureInitialized(); // for serviceMocker
   MockKeyboard mockKeyboard = new MockKeyboard();
   serviceMocker.registerMockService(KeyboardService.serviceName, mockKeyboard);
 
diff --git a/packages/flutter/test/widget/test_semantics.dart b/packages/flutter/test/widget/test_semantics.dart
index 6079e93..7eda783 100644
--- a/packages/flutter/test/widget/test_semantics.dart
+++ b/packages/flutter/test/widget/test_semantics.dart
@@ -7,7 +7,7 @@
 
 class TestSemanticsListener implements mojom.SemanticsListener {
   TestSemanticsListener() {
-    Renderer.instance.setSemanticsClient(this);
+    SemanticsNode.addListener(this);
   }
   final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
   updateSemanticsTree(List<mojom.SemanticsNode> nodes) {