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) {