Commit 3abb8c4e authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1493 from Hixie/semantics

Semantics updates
parents 9e784f0c 85689f12
...@@ -21,4 +21,4 @@ export 'src/services/image_resource.dart'; ...@@ -21,4 +21,4 @@ export 'src/services/image_resource.dart';
export 'src/services/keyboard.dart'; export 'src/services/keyboard.dart';
export 'src/services/print.dart'; export 'src/services/print.dart';
export 'src/services/service_registry.dart'; export 'src/services/service_registry.dart';
export 'src/services/shell.dart';
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; 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/network_service.mojom.dart' as mojo;
import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojo; import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojo;
import 'package:mojo/core.dart' as mojo; import 'package:mojo/core.dart' as mojo;
......
...@@ -7,6 +7,7 @@ import 'dart:ui' as ui; ...@@ -7,6 +7,7 @@ import 'dart:ui' as ui;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mojo/core.dart' as core;
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom; import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
import 'box.dart'; import 'box.dart';
...@@ -18,7 +19,7 @@ import 'semantics.dart'; ...@@ -18,7 +19,7 @@ import 'semantics.dart';
export 'package:flutter/gestures.dart' show HitTestResult; export 'package:flutter/gestures.dart' show HitTestResult;
/// The glue between the render tree and the Flutter engine. /// 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 { implements HitTestable {
void initInstances() { void initInstances() {
...@@ -26,6 +27,7 @@ abstract class Renderer extends Scheduler ...@@ -26,6 +27,7 @@ abstract class Renderer extends Scheduler
_instance = this; _instance = this;
ui.window.onMetricsChanged = handleMetricsChanged; ui.window.onMetricsChanged = handleMetricsChanged;
initRenderView(); initRenderView();
initSemantics();
assert(renderView != null); assert(renderView != null);
assert(() { assert(() {
initServiceExtensions(); initServiceExtensions();
...@@ -41,8 +43,6 @@ abstract class Renderer extends Scheduler ...@@ -41,8 +43,6 @@ abstract class Renderer extends Scheduler
if (renderView == null) { if (renderView == null) {
renderView = new RenderView(); renderView = new RenderView();
renderView.scheduleInitialFrame(); renderView.scheduleInitialFrame();
if (_semanticsClient != null)
renderView.scheduleInitialSemantics();
} }
handleMetricsChanged(); // configures renderView's metrics handleMetricsChanged(); // configures renderView's metrics
} }
...@@ -65,12 +65,11 @@ abstract class Renderer extends Scheduler ...@@ -65,12 +65,11 @@ abstract class Renderer extends Scheduler
renderView.configuration = new ViewConfiguration(size: ui.window.size); renderView.configuration = new ViewConfiguration(size: ui.window.size);
} }
mojom.SemanticsListener _semanticsClient; void initSemantics() {
void setSemanticsClient(mojom.SemanticsListener client) { SemanticsNode.onSemanticsEnabled = renderView.scheduleInitialSemantics;
assert(_semanticsClient == null); provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
_semanticsClient = client; return new SemanticsServer();
if (renderView != null) });
renderView.scheduleInitialSemantics();
} }
void _handlePersistentFrameCallback(Duration timeStamp) { void _handlePersistentFrameCallback(Duration timeStamp) {
...@@ -84,9 +83,9 @@ abstract class Renderer extends Scheduler ...@@ -84,9 +83,9 @@ abstract class Renderer extends Scheduler
RenderObject.flushCompositingBits(); RenderObject.flushCompositingBits();
RenderObject.flushPaint(); RenderObject.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU renderView.compositeFrame(); // this sends the bits to the GPU
if (_semanticsClient != null) { if (SemanticsNode.hasListeners) {
RenderObject.flushSemantics(); RenderObject.flushSemantics();
SemanticsNode.sendSemanticsTreeTo(_semanticsClient); SemanticsNode.sendSemanticsTree();
} }
} }
...@@ -116,7 +115,7 @@ void debugDumpSemanticsTree() { ...@@ -116,7 +115,7 @@ void debugDumpSemanticsTree() {
/// A concrete binding for applications that use the Rendering framework /// A concrete binding for applications that use the Rendering framework
/// directly. This is the glue that binds the framework to the Flutter engine. /// 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 }) { RenderingFlutterBinding({ RenderBox root }) {
assert(renderView != null); assert(renderView != null);
renderView.child = root; renderView.child = root;
......
...@@ -333,7 +333,18 @@ class SemanticsNode extends AbstractNode { ...@@ -333,7 +333,18 @@ class SemanticsNode extends AbstractNode {
return result; 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) { for (SemanticsNode oldNode in _detachedNodes) {
// The other side will have forgotten this node if we even send // 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 // it again, so make sure to mark it dirty so that it'll get
...@@ -377,7 +388,8 @@ class SemanticsNode extends AbstractNode { ...@@ -377,7 +388,8 @@ class SemanticsNode extends AbstractNode {
if (node._dirty && node.attached) if (node._dirty && node.attached)
updatedNodes.add(node._serialize()); updatedNodes.add(node._serialize());
} }
client.updateSemanticsTree(updatedNodes); for (mojom.SemanticsListener listener in _listeners)
listener.updateSemanticsTree(updatedNodes);
_dirtyNodes.clear(); _dirtyNodes.clear();
} }
...@@ -428,6 +440,7 @@ class SemanticsNode extends AbstractNode { ...@@ -428,6 +440,7 @@ class SemanticsNode extends AbstractNode {
class SemanticsServer extends mojom.SemanticsServer { class SemanticsServer extends mojom.SemanticsServer {
void addSemanticsListener(mojom.SemanticsListener listener) { void addSemanticsListener(mojom.SemanticsListener listener) {
SemanticsNode.addListener(listener);
} }
void tap(int nodeID) { void tap(int nodeID) {
SemanticsNode.getSemanticActionHandlerForId(nodeID, neededFlag: _SemanticFlags.canBeTapped)?.handleSemanticTap(); SemanticsNode.getSemanticActionHandlerForId(nodeID, neededFlag: _SemanticFlags.canBeTapped)?.handleSemanticTap();
......
...@@ -7,7 +7,7 @@ import 'dart:async'; ...@@ -7,7 +7,7 @@ import 'dart:async';
import 'package:sky_services/activity/activity.mojom.dart'; import 'package:sky_services/activity/activity.mojom.dart';
import 'shell.dart'; import 'binding.dart';
export 'package:sky_services/activity/activity.mojom.dart'; export 'package:sky_services/activity/activity.mojom.dart';
......
...@@ -16,7 +16,7 @@ import 'fetch.dart'; ...@@ -16,7 +16,7 @@ import 'fetch.dart';
import 'image_cache.dart'; import 'image_cache.dart';
import 'image_decoder.dart'; import 'image_decoder.dart';
import 'image_resource.dart'; import 'image_resource.dart';
import 'shell.dart'; import 'binding.dart';
abstract class AssetBundle { abstract class AssetBundle {
ImageResource loadImage(String key); ImageResource loadImage(String key);
......
...@@ -2,6 +2,14 @@ ...@@ -2,6 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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 /// Base class for mixins that provide singleton services (also known as
/// "bindings"). /// "bindings").
/// ///
...@@ -30,3 +38,85 @@ abstract class BindingBase { ...@@ -30,3 +38,85 @@ abstract class BindingBase {
assert(() { _debugInitialized = true; return true; }); 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;
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import 'package:mojo_services/keyboard/keyboard.mojom.dart'; import 'package:mojo_services/keyboard/keyboard.mojom.dart';
import 'shell.dart'; import 'binding.dart';
export 'package:mojo_services/keyboard/keyboard.mojom.dart'; export 'package:mojo_services/keyboard/keyboard.mojom.dart';
......
// 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._();
...@@ -21,7 +21,7 @@ class BindingObserver { ...@@ -21,7 +21,7 @@ class BindingObserver {
/// A concrete binding for applications based on the Widgets framework. /// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine. /// 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._(); WidgetFlutterBinding._();
......
...@@ -31,7 +31,7 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> { ...@@ -31,7 +31,7 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
} }
void _update() { void _update() {
setState(() { setState(() {
// the generation of the _SemanticsDebuggerClient has changed // the generation of the _SemanticsDebuggerListener has changed
}); });
} }
Point _lastPointerDownLocation; Point _lastPointerDownLocation;
...@@ -214,9 +214,10 @@ class _SemanticsDebuggerEntry { ...@@ -214,9 +214,10 @@ class _SemanticsDebuggerEntry {
_SemanticsDebuggerEntry hitTest(Point position, _SemanticsDebuggerEntryFilter filter) { _SemanticsDebuggerEntry hitTest(Point position, _SemanticsDebuggerEntryFilter filter) {
if (transform != null) { if (transform != null) {
if (transform.determinant == 0.0) Matrix4 invertedTransform = new Matrix4.identity();
double determinant = invertedTransform.copyInverse(transform);
if (determinant == 0.0)
return null; return null;
Matrix4 invertedTransform = new Matrix4.inverted(transform);
position = MatrixUtils.transformPoint(invertedTransform, position); position = MatrixUtils.transformPoint(invertedTransform, position);
} }
if (!rect.contains(position)) if (!rect.contains(position))
...@@ -235,13 +236,12 @@ class _SemanticsDebuggerEntry { ...@@ -235,13 +236,12 @@ class _SemanticsDebuggerEntry {
class _SemanticsDebuggerListener implements mojom.SemanticsListener { class _SemanticsDebuggerListener implements mojom.SemanticsListener {
_SemanticsDebuggerListener._() { _SemanticsDebuggerListener._() {
Renderer.instance.setSemanticsClient(this); SemanticsNode.addListener(this);
} }
static _SemanticsDebuggerListener instance; static _SemanticsDebuggerListener instance;
static mojom.SemanticsServer _server; static final SemanticsServer _server = new SemanticsServer();
static void ensureInstantiated({ mojom.SemanticsServer server }) { static void ensureInstantiated() {
_server = server ?? new SemanticsServer();
instance ??= new _SemanticsDebuggerListener._(); instance ??= new _SemanticsDebuggerListener._();
} }
......
...@@ -26,7 +26,7 @@ enum EnginePhase { ...@@ -26,7 +26,7 @@ enum EnginePhase {
composite composite
} }
class TestRenderingFlutterBinding extends BindingBase with Scheduler, Renderer, Gesturer { class TestRenderingFlutterBinding extends BindingBase with Scheduler, MojoShell, Renderer, Gesturer {
void initRenderView() { void initRenderView() {
if (renderView == null) { if (renderView == null) {
renderView = new TestRenderView(); renderView = new TestRenderView();
......
import 'package:flutter/src/services/shell.dart'; import 'package:flutter/services.dart';
import 'package:mojo/bindings.dart' as bindings; import 'package:mojo/bindings.dart' as bindings;
// Tests can use ServiceMocker to register replacement implementations // Tests can use ServiceMocker to register replacement implementations
// of Mojo services. // of Mojo services.
class _ServiceMocker { class ServiceMocker {
_ServiceMocker() { ServiceMocker._() {
shell.overrideConnectToService = _connectToService; shell.overrideConnectToService = _connectToService;
} }
// Map of interface names to mock implementations. // 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) { bool _connectToService(String url, bindings.ProxyBase proxy) {
Object mock = _interfaceMock[proxy.serviceName]; Object mock = _interfaceMocks[proxy.serviceName];
if (mock != null) { if (mock != null) {
// Replace the proxy's implementation of the service interface with the // Replace the proxy's implementation of the service interface with the
// mock. The mojom bindings put the "ptr" field on all proxies. // mock. The mojom bindings put the "ptr" field on all proxies.
...@@ -24,9 +24,11 @@ class _ServiceMocker { ...@@ -24,9 +24,11 @@ class _ServiceMocker {
} }
// Provide a mock implementation for a Mojo interface. // 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) { void registerMockService(String interfaceName, Object mock) {
_interfaceMock[interfaceName] = mock; _interfaceMocks[interfaceName] = mock;
} }
} }
final _ServiceMocker serviceMocker = new _ServiceMocker(); final ServiceMocker serviceMocker = new ServiceMocker._();
...@@ -28,6 +28,7 @@ class MockKeyboard implements KeyboardService { ...@@ -28,6 +28,7 @@ class MockKeyboard implements KeyboardService {
} }
void main() { void main() {
WidgetFlutterBinding.ensureInitialized(); // for serviceMocker
MockKeyboard mockKeyboard = new MockKeyboard(); MockKeyboard mockKeyboard = new MockKeyboard();
serviceMocker.registerMockService(KeyboardService.serviceName, mockKeyboard); serviceMocker.registerMockService(KeyboardService.serviceName, mockKeyboard);
......
...@@ -7,7 +7,7 @@ import 'package:sky_services/semantics/semantics.mojom.dart' as mojom; ...@@ -7,7 +7,7 @@ import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
class TestSemanticsListener implements mojom.SemanticsListener { class TestSemanticsListener implements mojom.SemanticsListener {
TestSemanticsListener() { TestSemanticsListener() {
Renderer.instance.setSemanticsClient(this); SemanticsNode.addListener(this);
} }
final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[]; final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
updateSemanticsTree(List<mojom.SemanticsNode> nodes) { updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment