Do not crash when toggling Semantics on, off, on (#13999)
* Do not crash when toggeling Semantics on, off, on * review comments
This commit is contained in:
parent
316d8e1c2c
commit
46629b7187
@ -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
|
|||||||
/// 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
|
|||||||
/// `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 {
|
|||||||
_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() {
|
|||||||
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';
|
|||||||
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() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
|
|
||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user