Commit 839def55 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Teach the system to shutdown semantics (#4811)

We now stop updating semantics when there are no remaining clients.
parent cbf7d988
......@@ -90,18 +90,12 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
PipelineOwner _pipelineOwner;
/// The render tree that's attached to the output surface.
RenderView get renderView => _renderView;
RenderView _renderView;
RenderView get renderView => _pipelineOwner.rootRenderObject;
/// 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.
set renderView(RenderView value) {
assert(value != null);
if (_renderView == value)
return;
if (_renderView != null)
_renderView.detach();
_renderView = value;
_renderView.attach(pipelineOwner);
_pipelineOwner.rootRenderObject = value;
}
/// Called when the system metrics change.
......@@ -134,18 +128,15 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
/// Called automatically when the binding is created.
void initSemantics() {
shell.provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
ensureSemantics();
mojom.SemanticsServerStub server = new mojom.SemanticsServerStub.fromEndpoint(endpoint);
server.impl = new SemanticsServer(semanticsOwner: pipelineOwner.semanticsOwner);
mojom.SemanticsServerStub stub = new mojom.SemanticsServerStub.fromEndpoint(endpoint);
SemanticsServer server = new SemanticsServer(pipelineOwner);
stub.impl = server;
stub.ctrl.onError = (_) {
server.dispose();
};
});
}
void ensureSemantics() {
if (pipelineOwner.semanticsOwner == null)
renderView.scheduleInitialSemantics();
assert(pipelineOwner.semanticsOwner != null);
}
void _handlePersistentFrameCallback(Duration timeStamp) {
beginFrame();
}
......@@ -165,7 +156,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
@override
void reassembleApplication() {
super.reassembleApplication();
pipelineOwner.reassemble(renderView);
pipelineOwner.reassemble();
}
@override
......@@ -202,6 +193,44 @@ void debugDumpSemanticsTree() {
debugPrint(RendererBinding.instance?.renderView?.debugSemantics?.toStringDeep() ?? 'Semantics not collected.');
}
/// Exposes the [SemanticsNode] tree to the underlying platform.
class SemanticsServer extends mojom.SemanticsServer {
/// Creates a semantics server that listens to semantic informationa about the
/// given [PipelineOwner].
///
/// Call [dispose] to stop listening for semantic updates.
SemanticsServer(PipelineOwner pipelineOwner) {
_semanticsOwner = pipelineOwner.addSemanticsListener(_updateSemanticsTree);
}
SemanticsOwner _semanticsOwner;
final List<mojom.SemanticsListenerProxy> _listeners = <mojom.SemanticsListenerProxy>[];
/// Stops listening for semantic updates and closes all outstanding listeners.
void dispose() {
for (mojom.SemanticsListenerProxy listener in _listeners)
listener.close();
_listeners.clear();
_semanticsOwner.removeListener(_updateSemanticsTree);
_semanticsOwner = null;
}
void _updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
for (mojom.SemanticsListenerProxy listener in _listeners)
listener.updateSemanticsTree(nodes);
}
@override
void addSemanticsListener(mojom.SemanticsListenerProxy listener) {
_listeners.add(listener);
}
@override
void performAction(int id, mojom.SemanticAction encodedAction) {
_semanticsOwner.performAction(id, SemanticAction.values[encodedAction.mojoEnumValue]);
}
}
/// A concrete binding for applications that use the Rendering framework
/// directly. This is the glue that binds the framework to the Flutter engine.
///
......
......@@ -793,6 +793,41 @@ class PipelineOwner {
onNeedVisualUpdate();
}
/// The unique render object managed by this pipeline that has no parent.
RenderObject get rootRenderObject => _rootRenderObject;
RenderObject _rootRenderObject;
set rootRenderObject(RenderObject value) {
if (_rootRenderObject == value)
return;
_rootRenderObject?.detach();
_rootRenderObject = value;
_rootRenderObject?.attach(this);
}
/// Calls the given listener whenever the semantics of the render tree change.
///
/// Creates [semanticsOwner] if necessary.
SemanticsOwner addSemanticsListener(SemanticsListener listener) {
if (_semanticsOwner == null) {
_semanticsOwner = new SemanticsOwner(
initialListener: listener,
onLastListenerRemoved: _handleLastSemanticsListenerRemoved
);
_rootRenderObject.scheduleInitialSemantics();
} else {
_semanticsOwner.addListener(listener);
}
assert(_semanticsOwner != null);
return _semanticsOwner;
}
void _handleLastSemanticsListenerRemoved() {
assert(!_debugDoingSemantics);
rootRenderObject._clearSemantics();
_semanticsOwner.dispose();
_semanticsOwner = null;
}
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
/// Whether this pipeline is currently in the layout phase.
......@@ -883,7 +918,7 @@ class PipelineOwner {
SemanticsOwner get semanticsOwner => _semanticsOwner;
SemanticsOwner _semanticsOwner;
bool _debugDoingSemantics = false;
List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
final List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
/// Update the semantics for all render objects.
///
......@@ -912,17 +947,14 @@ class PipelineOwner {
_semanticsOwner.sendSemanticsTree();
}
/// Cause the entire subtree rooted at the given [RenderObject] to
/// be entirely reprocessed. This is used by development tools when
/// the application code has changed, to cause the rendering tree to
/// pick up any changed implementations.
/// Cause the entire render tree rooted at [rootRenderObject] to be entirely
/// reprocessed. This is used by development tools when the application code
/// has changed, to cause the rendering tree to pick up any changed
/// implementations.
///
/// This is expensive and should not be called except during
/// development.
void reassemble(RenderObject root) {
assert(root.parent is! RenderObject);
assert(root.owner == this);
root._reassemble();
/// This is expensive and should not be called except during development.
void reassemble() {
_rootRenderObject?._reassemble();
}
}
......@@ -1862,8 +1894,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
assert(!owner._debugDoingSemantics);
assert(_semantics == null);
assert(_needsSemanticsUpdate);
assert(owner._semanticsOwner == null);
owner._semanticsOwner = new SemanticsOwner();
assert(owner._semanticsOwner != null);
owner._nodesNeedingSemantics.add(this);
owner.requestVisualUpdate();
}
......@@ -1896,6 +1927,16 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
return result;
}
/// Removes all semantics from this render object and its descendants.
void _clearSemantics() {
_needsSemanticsUpdate = true;
_needsSemanticsGeometryUpdate = true;
_semantics = null;
visitChildren((RenderObject child) {
child._clearSemantics();
});
}
/// Mark this node as needing an update to its semantics
/// description.
///
......
......@@ -455,32 +455,68 @@ class SemanticsNode extends AbstractNode {
}
}
/// Signature for functions that receive updates about render tree semantics.
typedef void SemanticsListener(List<mojom.SemanticsNode> nodes);
/// Owns [SemanticsNode] objects and notifies listeners of changes to the
/// render tree semantics.
///
/// To listen for semantic updates, call [PipelineOwner.addSemanticsListener],
/// which will create a [SemanticsOwner] if necessary.
class SemanticsOwner {
/// Creates a [SemanticsOwner].
///
/// The `onLastListenerRemoved` argument must not be null and will be called
/// when the last listener is removed from this object.
SemanticsOwner({
@required SemanticsListener initialListener,
@required VoidCallback onLastListenerRemoved
}) : _onLastListenerRemoved = onLastListenerRemoved {
assert(_onLastListenerRemoved != null);
addListener(initialListener);
}
final VoidCallback _onLastListenerRemoved;
final List<SemanticsNode> _dirtyNodes = <SemanticsNode>[];
final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
final Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
List<mojom.SemanticsListener> _listeners;
final List<SemanticsListener> _listeners = <SemanticsListener>[];
/// Whether there are currently any consumers of semantic data.
/// Releases any resources retained by this object.
///
/// If there are no consumers of semantic data, there is no need to compile
/// semantic data into a [SemanticsNode] tree.
bool get hasListeners => _listeners != null && _listeners.length > 0;
/// Requires that there are no listeners registered with [addListener].
void dispose() {
assert(_listeners.isEmpty);
_dirtyNodes.clear();
_nodes.clear();
_detachedNodes.clear();
}
/// Add a consumer of semantic data.
///
/// After the [PipelineOwner] updates the semantic data for a given frame, it
/// calls [sendSemanticsTree], which uploads the data to each listener
/// registered with this function.
void addListener(mojom.SemanticsListener listener) {
_listeners ??= <mojom.SemanticsListener>[];
///
/// Listeners can be removed with [removeListener].
void addListener(SemanticsListener listener) {
_listeners.add(listener);
}
/// Removes a consumer of semantic data.
///
/// Listeners can be added with [addListener].
void removeListener(SemanticsListener listener) {
_listeners.remove(listener);
if (_listeners.isEmpty)
_onLastListenerRemoved();
}
/// Uploads the semantics tree to the listeners registered with [addListener].
void sendSemanticsTree() {
assert(hasListeners);
assert(_listeners.isNotEmpty);
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
......@@ -540,8 +576,8 @@ class SemanticsOwner {
if (node._dirty && node.attached)
updatedNodes.add(node._serialize());
}
for (mojom.SemanticsListener listener in _listeners)
listener.updateSemanticsTree(updatedNodes);
for (SemanticsListener listener in new List<SemanticsListener>.from(_listeners))
listener(updatedNodes);
_dirtyNodes.clear();
}
......@@ -562,29 +598,12 @@ class SemanticsOwner {
return result._actionHandler;
}
/// Asks the [SemanticsNode] with the given id to perform the given action.
///
/// If the [SemanticsNode] has not indicated that it can perform the action,
/// this function does nothing.
void performAction(int id, SemanticAction action) {
SemanticActionHandler handler = _getSemanticActionHandlerForId(id, action: action);
handler?.performAction(action);
}
}
/// Exposes the [SemanticsNode] tree to the underlying platform.
class SemanticsServer extends mojom.SemanticsServer {
SemanticsServer({ @required this.semanticsOwner }) {
assert(semanticsOwner != null);
}
final SemanticsOwner semanticsOwner;
@override
void addSemanticsListener(mojom.SemanticsListenerProxy listener) {
// TODO(abarth): We should remove the listener when this pipe closes.
// See <https://github.com/flutter/flutter/issues/3342>.
semanticsOwner.addListener(listener);
}
@override
void performAction(int id, mojom.SemanticAction encodedAction) {
semanticsOwner.performAction(id, SemanticAction.values[encodedAction.mojoEnumValue]);
}
}
......@@ -40,14 +40,15 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
// static here because we might not be in a tree that's attached to that
// binding. Instead, we should find a way to get to the PipelineOwner from
// the BuildContext.
WidgetsBinding.instance.ensureSemantics();
_client = new _SemanticsClient(WidgetsBinding.instance.pipelineOwner.semanticsOwner)
_client = new _SemanticsClient(WidgetsBinding.instance.pipelineOwner)
..addListener(_update);
}
@override
void dispose() {
_client.removeListener(_update);
_client
..removeListener(_update)
..dispose();
super.dispose();
}
......@@ -310,12 +311,19 @@ class _SemanticsDebuggerEntry {
}
}
class _SemanticsClient extends ChangeNotifier implements mojom.SemanticsListener {
_SemanticsClient(this.semanticsOwner) {
semanticsOwner.addListener(this);
class _SemanticsClient extends ChangeNotifier {
_SemanticsClient(PipelineOwner pipelineOwner) {
_semanticsOwner = pipelineOwner.addSemanticsListener(_updateSemanticsTree);
}
final SemanticsOwner semanticsOwner;
SemanticsOwner _semanticsOwner;
@override
void dispose() {
_semanticsOwner.removeListener(_updateSemanticsTree);
_semanticsOwner = null;
super.dispose();
}
_SemanticsDebuggerEntry get rootNode => _nodes[0];
final Map<int, _SemanticsDebuggerEntry> _nodes = <int, _SemanticsDebuggerEntry>{};
......@@ -353,8 +361,7 @@ class _SemanticsClient extends ChangeNotifier implements mojom.SemanticsListener
int generation = 0;
@override
void updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
void _updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
generation += 1;
for (mojom.SemanticsNode node in nodes)
_updateNode(node);
......@@ -368,7 +375,7 @@ class _SemanticsClient extends ChangeNotifier implements mojom.SemanticsListener
void _performAction(Point position, SemanticAction action) {
_SemanticsDebuggerEntry entry = _hitTest(position, (_SemanticsDebuggerEntry entry) => entry.actions.contains(action));
semanticsOwner.performAction(entry?.id ?? 0, action);
_semanticsOwner.performAction(entry?.id ?? 0, action);
}
void handlePanEnd(Point position, Velocity velocity) {
......
......@@ -7,7 +7,7 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widget/test_semantics.dart';
import '../rendering/test_semantics_client.dart';
// This file uses "as dynamic" in a few places to defeat the static
// analysis. In general you want to avoid using this style in your
......@@ -394,7 +394,7 @@ void main() {
});
testWidgets('Does tooltip contribute semantics', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
GlobalKey key = new GlobalKey();
await tester.pumpWidget(
new Overlay(
......@@ -419,7 +419,7 @@ void main() {
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -431,14 +431,13 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// before using "as dynamic" in your code, see note top of file
(key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -450,7 +449,7 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
......@@ -4,10 +4,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
import 'package:test/test.dart';
import 'rendering_tester.dart';
import 'test_semantics_client.dart';
class TestTree {
TestTree() {
......@@ -77,15 +77,6 @@ class TestCompositingBitsTree {
bool painted = false;
}
class TestSemanticsListener implements mojom.SemanticsListener {
final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
@override
void updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
updates.addAll(nodes);
}
}
void main() {
test('objects can be detached and re-attached: layout', () {
TestTree testTree = new TestTree();
......@@ -135,21 +126,20 @@ void main() {
});
test('objects can be detached and re-attached: semantics', () {
TestTree testTree = new TestTree();
TestSemanticsListener listener = new TestSemanticsListener();
renderer.ensureSemantics();
renderer.pipelineOwner.semanticsOwner.addListener(listener);
TestSemanticsClient client = new TestSemanticsClient(renderer.pipelineOwner);
// Lay out, composite, paint, and update semantics
layout(testTree.root, phase: EnginePhase.flushSemantics);
expect(listener.updates.length, equals(1));
expect(client.updates.length, equals(1));
// Remove testTree from the custom render view
renderer.renderView.child = null;
expect(testTree.child.owner, isNull);
// Dirty one of the elements
listener.updates.clear();
client.updates.clear();
testTree.child.markNeedsSemanticsUpdate();
expect(listener.updates.length, equals(0));
expect(client.updates.length, equals(0));
// Lay out, composite, paint, and update semantics again
layout(testTree.root, phase: EnginePhase.flushSemantics);
expect(listener.updates.length, equals(1));
expect(client.updates.length, equals(1));
client.dispose();
});
}
......@@ -2,21 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
class TestSemanticsListener implements mojom.SemanticsListener {
TestSemanticsListener(WidgetTester tester) {
tester.binding.ensureSemantics();
tester.binding.pipelineOwner.semanticsOwner.addListener(this);
class TestSemanticsClient {
TestSemanticsClient(PipelineOwner pipelineOwner) {
_semanticsOwner = pipelineOwner.addSemanticsListener(_updateSemanticsTree);
}
SemanticsOwner _semanticsOwner;
void dispose() {
_semanticsOwner.removeListener(_updateSemanticsTree);
_semanticsOwner = null;
}
final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
@override
void updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
assert(!nodes.any((mojom.SemanticsNode node) => node == null));
void _updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
updates.addAll(nodes);
updates.add(null);
}
}
......@@ -7,11 +7,11 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
await tester.pumpWidget(
new Material(
child: new Center(
......@@ -22,7 +22,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -40,7 +40,7 @@ void main() {
expect(client.updates[0].children[0].flags.isChecked, isFalse);
expect(client.updates[0].children[0].strings.label, equals('Hello'));
expect(client.updates[0].children[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
......@@ -7,11 +7,11 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 1', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
// smoketest
await tester.pumpWidget(
......@@ -22,7 +22,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -34,7 +34,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// control for forking
......@@ -56,7 +55,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -68,7 +67,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// forking semantics
......@@ -90,7 +88,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -124,7 +122,6 @@ void main() {
expect(client.updates[0].children[1].geometry.width, equals(800.0));
expect(client.updates[0].children[1].geometry.height, equals(10.0));
expect(client.updates[0].children[1].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// toggle a branch off
......@@ -146,7 +143,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -158,7 +155,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// toggle a branch back on
......@@ -180,7 +176,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -214,7 +210,7 @@ void main() {
expect(client.updates[0].children[1].geometry.width, equals(800.0));
expect(client.updates[0].children[1].geometry.height, equals(10.0));
expect(client.updates[0].children[1].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
......@@ -7,11 +7,11 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 2', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
// this test is the same as the test in Semantics 1, but
// starting with the second branch being ignored and then
......@@ -36,7 +36,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -70,7 +70,6 @@ void main() {
expect(client.updates[0].children[1].geometry.width, equals(800.0));
expect(client.updates[0].children[1].geometry.height, equals(10.0));
expect(client.updates[0].children[1].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// toggle a branch off
......@@ -92,7 +91,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -104,7 +103,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// toggle a branch back on
......@@ -126,7 +124,7 @@ void main() {
crossAxisAlignment: CrossAxisAlignment.stretch
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -160,7 +158,7 @@ void main() {
expect(client.updates[0].children[1].geometry.width, equals(800.0));
expect(client.updates[0].children[1].geometry.height, equals(10.0));
expect(client.updates[0].children[1].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
......@@ -6,11 +6,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 3', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
// implicit annotators
await tester.pumpWidget(
......@@ -25,7 +25,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isTrue);
......@@ -37,7 +37,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// remove one
......@@ -50,7 +49,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isTrue);
......@@ -62,7 +61,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// change what it says
......@@ -75,7 +73,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -87,7 +85,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// add a node
......@@ -103,7 +100,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isTrue);
......@@ -115,7 +112,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// make no changes
......@@ -132,6 +128,6 @@ void main() {
)
);
expect(client.updates.length, equals(0));
client.dispose();
});
}
......@@ -6,11 +6,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 4', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
// O
// / \ O=root
......@@ -40,7 +40,7 @@ void main() {
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].children.length, equals(2));
expect(client.updates[0].children[0].id, equals(1));
......@@ -51,7 +51,6 @@ void main() {
expect(client.updates[0].children[1].children[0].children.length, equals(0));
expect(client.updates[0].children[1].children[1].id, equals(4));
expect(client.updates[0].children[1].children[1].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// O O=root
......@@ -79,10 +78,9 @@ void main() {
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(2));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// O=root
......@@ -107,11 +105,10 @@ void main() {
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
......@@ -6,11 +6,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 5', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
await tester.pumpWidget(
new Stack(
......@@ -28,7 +28,7 @@ void main() {
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].flags.hasCheckedState, isFalse);
expect(client.updates[0].strings.label, equals(''));
......@@ -39,8 +39,7 @@ void main() {
expect(client.updates[0].children[1].id, equals(2));
expect(client.updates[0].children[1].flags.hasCheckedState, isFalse);
expect(client.updates[0].children[1].strings.label, equals('label'));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
......@@ -5,13 +5,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 7 - Merging', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
String label;
......@@ -44,7 +44,7 @@ void main() {
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isFalse);
......@@ -80,7 +80,6 @@ void main() {
expect(client.updates[0].children[1].geometry.height, equals(600.0));
expect(client.updates[0].children[1].children.length, equals(0));
// IDs 5 and 6 are used up by the nodes that get merged in
expect(client.updates[1], isNull);
client.updates.clear();
label = '2';
......@@ -112,8 +111,7 @@ void main() {
]
)
);
expect(client.updates.length, equals(3));
expect(client.updates[2], isNull);
expect(client.updates.length, equals(2));
// The order of the nodes is undefined, so allow both orders.
mojom.SemanticsNode a, b;
......@@ -150,6 +148,6 @@ void main() {
expect(b.children.length, equals(0));
client.updates.clear();
client.dispose();
});
}
......@@ -6,11 +6,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_semantics.dart';
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics 8 - Merging with reset', (WidgetTester tester) async {
TestSemanticsListener client = new TestSemanticsListener(tester);
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
await tester.pumpWidget(
new MergeSemantics(
......@@ -32,7 +32,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isTrue);
......@@ -44,7 +44,6 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// switch the order of the inner Semantics node to trigger a reset
......@@ -68,7 +67,7 @@ void main() {
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates.length, equals(1));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].actions, isEmpty);
expect(client.updates[0].flags.hasCheckedState, isTrue);
......@@ -80,8 +79,7 @@ void main() {
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
client.dispose();
});
}
// 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 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
import '../rendering/test_semantics_client.dart';
void main() {
testWidgets('Semantics shutdown and restart', (WidgetTester tester) async {
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
await tester.pumpWidget(
new Container(
child: new Semantics(
label: 'test1',
child: new Container()
)
)
);
void checkUpdates(List<mojom.SemanticsNode> updates) {
expect(updates.length, equals(1));
expect(updates[0].id, equals(0));
expect(updates[0].actions, isEmpty);
expect(updates[0].flags.hasCheckedState, isFalse);
expect(updates[0].flags.isChecked, isFalse);
expect(updates[0].strings.label, equals('test1'));
expect(updates[0].geometry.transform, isNull);
expect(updates[0].geometry.left, equals(0.0));
expect(updates[0].geometry.top, equals(0.0));
expect(updates[0].geometry.width, equals(800.0));
expect(updates[0].geometry.height, equals(600.0));
expect(updates[0].children.length, equals(0));
}
checkUpdates(client.updates);
client.updates.clear();
client.dispose();
expect(tester.binding.hasScheduledFrame, isFalse);
client = new TestSemanticsClient(tester.binding.pipelineOwner);
expect(tester.binding.hasScheduledFrame, isTrue);
await tester.pump();
checkUpdates(client.updates);
client.updates.clear();
client.dispose();
});
}
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