Unverified Commit 46629b71 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Do not crash when toggling Semantics on, off, on (#13999)

* Do not crash when toggeling Semantics on, off, on

* review comments
parent 316d8e1c
...@@ -627,6 +627,13 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -627,6 +627,13 @@ class RenderCustomPaint extends RenderProxyBox {
super.assembleSemanticsNode(node, config, finalChildren); super.assembleSemanticsNode(node, config, finalChildren);
} }
@override
void clearSemantics() {
super.clearSemantics();
_backgroundSemanticsNodes = null;
_foregroundSemanticsNodes = null;
}
/// Updates the nodes of `oldSemantics` using data in `newChildSemantics`, and /// Updates the nodes of `oldSemantics` using data in `newChildSemantics`, and
/// returns a new list containing child nodes sorted according to the order /// returns a new list containing child nodes sorted according to the order
/// specified by `newChildSemantics`. /// specified by `newChildSemantics`.
......
...@@ -2220,6 +2220,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2220,6 +2220,10 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// Removes all semantics from this render object and its descendants. /// Removes all semantics from this render object and its descendants.
/// ///
/// Should only be called on objects whose [parent] is not a [RenderObject]. /// Should only be called on objects whose [parent] is not a [RenderObject].
///
/// Override this method if you instantiate new [SemanticsNode]s in an
/// overridden [assembleSemanticsNode] method, to dispose of those nodes.
@mustCallSuper
void clearSemantics() { void clearSemantics() {
_needsSemanticsUpdate = true; _needsSemanticsUpdate = true;
_semantics = null; _semantics = null;
...@@ -2399,7 +2403,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2399,7 +2403,8 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// `children` to it. /// `children` to it.
/// ///
/// Subclasses can override this method to add additional [SemanticsNode]s /// Subclasses can override this method to add additional [SemanticsNode]s
/// to the tree. /// to the tree. If new [SemanticsNode]s are instantiated in this method
/// they must be disposed in [clearSemantics].
void assembleSemanticsNode( void assembleSemanticsNode(
SemanticsNode node, SemanticsNode node,
SemanticsConfiguration config, SemanticsConfiguration config,
......
...@@ -581,6 +581,13 @@ class _RenderExcludableScrollSemantics extends RenderProxyBox { ...@@ -581,6 +581,13 @@ class _RenderExcludableScrollSemantics extends RenderProxyBox {
_innerNode.updateWith(config: config, childrenInInversePaintOrder: included); _innerNode.updateWith(config: config, childrenInInversePaintOrder: included);
} }
@override
void clearSemantics() {
super.clearSemantics();
_innerNode = null;
_annotatedNode = null;
}
/// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is /// Sends a [SemanticsEvent] in the context of the [SemanticsNode] that is
/// annotated with this object's semantics information. /// annotated with this object's semantics information.
void sendSemanticsEvent(SemanticsEvent event) { void sendSemanticsEvent(SemanticsEvent event) {
......
...@@ -274,6 +274,48 @@ void _defineTests() { ...@@ -274,6 +274,48 @@ void _defineTests() {
semanticsTester.dispose(); semanticsTester.dispose();
}); });
testWidgets('Can toggle semantics on, off, on without crash', (WidgetTester tester) async {
await tester.pumpWidget(new CustomPaint(
painter: new _PainterWithSemantics(
semantics: new CustomPainterSemantics(
key: const ValueKey<int>(1),
rect: new Rect.fromLTRB(1.0, 2.0, 3.0, 4.0),
properties: const SemanticsProperties(
checked: false,
selected: false,
button: false,
label: 'label-before',
value: 'value-before',
increasedValue: 'increase-before',
decreasedValue: 'decrease-before',
hint: 'hint-before',
textDirection: TextDirection.rtl,
),
),
),
));
// Start with semantics off.
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
// Semantics on
SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
// Semantics off
semantics.dispose();
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
// Semantics on
semantics = new SemanticsTester(tester);
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
semantics.dispose();
});
group('diffing', () { group('diffing', () {
testWidgets('complains about duplicate keys', (WidgetTester tester) async { testWidgets('complains about duplicate keys', (WidgetTester tester) async {
final SemanticsTester semanticsTester = new SemanticsTester(tester); final SemanticsTester semanticsTester = new SemanticsTester(tester);
......
...@@ -11,8 +11,15 @@ import 'package:flutter/widgets.dart'; ...@@ -11,8 +11,15 @@ import 'package:flutter/widgets.dart';
import 'semantics_tester.dart'; import 'semantics_tester.dart';
void main() { void main() {
SemanticsTester semantics;
tearDown(() {
semantics?.dispose();
semantics = null;
});
testWidgets('scrollable exposes the correct semantic actions', (WidgetTester tester) async { testWidgets('scrollable exposes the correct semantic actions', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); semantics = new SemanticsTester(tester);
final List<Widget> textWidgets = <Widget>[]; final List<Widget> textWidgets = <Widget>[];
for (int i = 0; i < 80; i++) for (int i = 0; i < 80; i++)
...@@ -40,7 +47,7 @@ void main() { ...@@ -40,7 +47,7 @@ void main() {
}); });
testWidgets('showOnScreen works in scrollable', (WidgetTester tester) async { testWidgets('showOnScreen works in scrollable', (WidgetTester tester) async {
new SemanticsTester(tester); // enables semantics tree generation semantics = new SemanticsTester(tester); // enables semantics tree generation
const double kItemHeight = 40.0; const double kItemHeight = 40.0;
...@@ -76,7 +83,7 @@ void main() { ...@@ -76,7 +83,7 @@ void main() {
}); });
testWidgets('showOnScreen works with pinned app bar and sliver list', (WidgetTester tester) async { testWidgets('showOnScreen works with pinned app bar and sliver list', (WidgetTester tester) async {
new SemanticsTester(tester); // enables semantics tree generation semantics = new SemanticsTester(tester); // enables semantics tree generation
const double kItemHeight = 100.0; const double kItemHeight = 100.0;
const double kExpandedAppBarHeight = 56.0; const double kExpandedAppBarHeight = 56.0;
...@@ -134,13 +141,13 @@ void main() { ...@@ -134,13 +141,13 @@ void main() {
}); });
testWidgets('showOnScreen works with pinned app bar and individual slivers', (WidgetTester tester) async { testWidgets('showOnScreen works with pinned app bar and individual slivers', (WidgetTester tester) async {
new SemanticsTester(tester); // enables semantics tree generation semantics = new SemanticsTester(tester); // enables semantics tree generation
const double kItemHeight = 100.0; const double kItemHeight = 100.0;
const double kExpandedAppBarHeight = 256.0; const double kExpandedAppBarHeight = 256.0;
final List<Widget> semantics = <Widget>[]; final List<Widget> children = <Widget>[];
final List<Widget> slivers = new List<Widget>.generate(30, (int i) { final List<Widget> slivers = new List<Widget>.generate(30, (int i) {
final Widget child = new MergeSemantics( final Widget child = new MergeSemantics(
child: new Container( child: new Container(
...@@ -148,7 +155,7 @@ void main() { ...@@ -148,7 +155,7 @@ void main() {
height: 72.0, height: 72.0,
), ),
); );
semantics.add(child); children.add(child);
return new SliverToBoxAdapter( return new SliverToBoxAdapter(
child: child, child: child,
); );
...@@ -184,17 +191,17 @@ void main() { ...@@ -184,17 +191,17 @@ void main() {
expect(scrollController.offset, kItemHeight / 2); expect(scrollController.offset, kItemHeight / 2);
final int id0 = tester.renderObject(find.byWidget(semantics[0])).debugSemantics.id; final int id0 = tester.renderObject(find.byWidget(children[0])).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(id0, SemanticsAction.showOnScreen); tester.binding.pipelineOwner.semanticsOwner.performAction(id0, SemanticsAction.showOnScreen);
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 5)); await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(semantics[0])).dy, kToolbarHeight); expect(tester.getTopLeft(find.byWidget(children[0])).dy, kToolbarHeight);
final int id1 = tester.renderObject(find.byWidget(semantics[1])).debugSemantics.id; final int id1 = tester.renderObject(find.byWidget(children[1])).debugSemantics.id;
tester.binding.pipelineOwner.semanticsOwner.performAction(id1, SemanticsAction.showOnScreen); tester.binding.pipelineOwner.semanticsOwner.performAction(id1, SemanticsAction.showOnScreen);
await tester.pump(); await tester.pump();
await tester.pump(const Duration(seconds: 5)); await tester.pump(const Duration(seconds: 5));
expect(tester.getTopLeft(find.byWidget(semantics[1])).dy, kToolbarHeight); expect(tester.getTopLeft(find.byWidget(children[1])).dy, kToolbarHeight);
}); });
testWidgets('vertical scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async { testWidgets('vertical scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
...@@ -203,7 +210,7 @@ void main() { ...@@ -203,7 +210,7 @@ void main() {
messages.add(message); messages.add(message);
}); });
final SemanticsTester semantics = new SemanticsTester(tester); semantics = new SemanticsTester(tester);
final List<Widget> textWidgets = <Widget>[]; final List<Widget> textWidgets = <Widget>[];
for (int i = 0; i < 80; i++) for (int i = 0; i < 80; i++)
...@@ -235,8 +242,6 @@ void main() { ...@@ -235,8 +242,6 @@ void main() {
expect(message['pixels'], isNonNegative); expect(message['pixels'], isNonNegative);
expect(message['minScrollExtent'], 0.0); expect(message['minScrollExtent'], 0.0);
expect(message['maxScrollExtent'], 520.0); expect(message['maxScrollExtent'], 520.0);
semantics.dispose();
}); });
testWidgets('horizontal scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async { testWidgets('horizontal scrolling sends ScrollCompletedSemanticsEvent', (WidgetTester tester) async {
...@@ -245,7 +250,7 @@ void main() { ...@@ -245,7 +250,7 @@ void main() {
messages.add(message); messages.add(message);
}); });
final SemanticsTester semantics = new SemanticsTester(tester); semantics = new SemanticsTester(tester);
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
for (int i = 0; i < 80; i++) for (int i = 0; i < 80; i++)
...@@ -283,12 +288,10 @@ void main() { ...@@ -283,12 +288,10 @@ void main() {
expect(message['pixels'], isNonNegative); expect(message['pixels'], isNonNegative);
expect(message['minScrollExtent'], 0.0); expect(message['minScrollExtent'], 0.0);
expect(message['maxScrollExtent'], 7200.0); expect(message['maxScrollExtent'], 7200.0);
semantics.dispose();
}); });
testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async { testWidgets('Semantics tree is populated mid-scroll', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); semantics = new SemanticsTester(tester);
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
for (int i = 0; i < 80; i++) for (int i = 0; i < 80; i++)
...@@ -310,8 +313,40 @@ void main() { ...@@ -310,8 +313,40 @@ void main() {
expect(semantics, includesNodeWith(label: 'Item 1')); expect(semantics, includesNodeWith(label: 'Item 1'));
expect(semantics, includesNodeWith(label: 'Item 2')); expect(semantics, includesNodeWith(label: 'Item 2'));
expect(semantics, includesNodeWith(label: 'Item 3')); expect(semantics, includesNodeWith(label: 'Item 3'));
});
testWidgets('Can toggle semantics on, off, on without crash', (WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new ListView(
children: new List<Widget>.generate(40, (int i) {
return new Container(
child: new Text('item $i'),
height: 40.0,
);
}),
),
),
);
// Start with semantics off.
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
// Semantics on
semantics = new SemanticsTester(tester);
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
// Semantics off
semantics.dispose(); semantics.dispose();
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNull);
// Semantics on
semantics = new SemanticsTester(tester);
await tester.pumpAndSettle();
expect(tester.binding.pipelineOwner.semanticsOwner, isNotNull);
}); });
} }
......
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