diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index f54d4a8f6c..15cf986da5 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -790,10 +790,20 @@ class _BindingPipelineManifold extends ChangeNotifier implements PipelineManifol ChangeNotifier.maybeDispatchObjectCreation(this); } _binding.addSemanticsEnabledListener(notifyListeners); + if (_binding.semanticsEnabled) { + SystemChannels.accessibility.send(const GeneratingSemanticsTreeSemanticsEvent(true).toMap()); + } } final RendererBinding _binding; + @protected + @override + void notifyListeners() { + SystemChannels.accessibility.send(GeneratingSemanticsTreeSemanticsEvent(_binding.semanticsEnabled).toMap()); + super.notifyListeners(); + } + @override void requestVisualUpdate() { _binding.ensureVisualUpdate(); diff --git a/packages/flutter/lib/src/semantics/semantics_event.dart b/packages/flutter/lib/src/semantics/semantics_event.dart index b3751ea6c3..5e17fdfb1c 100644 --- a/packages/flutter/lib/src/semantics/semantics_event.dart +++ b/packages/flutter/lib/src/semantics/semantics_event.dart @@ -135,6 +135,40 @@ class TooltipSemanticsEvent extends SemanticsEvent { } } +/// An event to notify native OS that flutter starts or stops the semantics tree +/// generation +/// +/// The [generating] indicate whether flutter starts generating the semantics +/// tree. If true, flutter will start sending semantics update to platform +/// embedding. +/// +/// Embeddings must be ready to receive semantics update after they receive this +/// event with [generating] set to true as framework will start sending +/// semantics update in the next frame. +/// +/// If [generating] is false, embeddings need to clean up previous updates as +/// the framework semantics tree was completely destroyed. +class GeneratingSemanticsTreeSemanticsEvent extends SemanticsEvent { + + /// Constructs an event that notify platform whether it is generating + /// semantics tree. + const GeneratingSemanticsTreeSemanticsEvent(this.generating) + : super('generatingSemanticsTree'); + + /// Whether framework starts generating the semantics tree. + /// + /// If true, flutter starts sending semantics update to platform + /// embedding. + final bool generating; + + @override + Map getDataMap() { + return { + 'generating': generating, + }; + } +} + /// An event which triggers long press semantic feedback. /// /// Currently only honored on Android. Triggers a long-press specific sound diff --git a/packages/flutter/test/rendering/binding_pipeline_manifold_semantics_event_test.dart b/packages/flutter/test/rendering/binding_pipeline_manifold_semantics_event_test.dart new file mode 100644 index 0000000000..b4e20b6405 --- /dev/null +++ b/packages/flutter/test/rendering/binding_pipeline_manifold_semantics_event_test.dart @@ -0,0 +1,39 @@ +// Copyright 2014 The Flutter 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/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'rendering_tester.dart'; + +void main() { + test('Turning global semantics on/off sends semantics event', () { + TestRenderingFlutterBinding.ensureInitialized(); + final List messages = []; + TestRenderingFlutterBinding.instance.defaultBinaryMessenger.setMockDecodedMessageHandler( + SystemChannels.accessibility, + (dynamic message) async { + messages.add(message); + } + ); + final SemanticsHandle handle = TestRenderingFlutterBinding.instance.ensureSemantics(); + expect(messages.length, 1); + expect(messages[0], { + 'type': 'generatingSemanticsTree', + 'data': { + 'generating': true, + }, + }); + + handle.dispose(); + expect(messages.length, 2); + expect(messages[1], { + 'type': 'generatingSemanticsTree', + 'data': { + 'generating': false, + }, + }); + TestRenderingFlutterBinding.instance.defaultBinaryMessenger.setMockDecodedMessageHandler(SystemChannels.accessibility, null); + }); +} diff --git a/packages/flutter/test/widgets/feedback_test.dart b/packages/flutter/test/widgets/feedback_test.dart index e66bcc21ad..40584f648d 100644 --- a/packages/flutter/test/widgets/feedback_test.dart +++ b/packages/flutter/test/widgets/feedback_test.dart @@ -38,7 +38,7 @@ void main () { testWidgets('forTap', (WidgetTester tester) async { final SemanticsTester semanticsTester = SemanticsTester(tester); - + semanticEvents.clear(); await tester.pumpWidget(TestWidget( tapHandler: (BuildContext context) { return () => Feedback.forTap(context); @@ -67,7 +67,7 @@ void main () { testWidgets('forTap Wrapper', (WidgetTester tester) async { final SemanticsTester semanticsTester = SemanticsTester(tester); - + semanticEvents.clear(); int callbackCount = 0; void callback() { callbackCount++; @@ -102,7 +102,7 @@ void main () { testWidgets('forLongPress', (WidgetTester tester) async { final SemanticsTester semanticsTester = SemanticsTester(tester); - + semanticEvents.clear(); await tester.pumpWidget(TestWidget( longPressHandler: (BuildContext context) { return () => Feedback.forLongPress(context); @@ -130,6 +130,7 @@ void main () { testWidgets('forLongPress Wrapper', (WidgetTester tester) async { final SemanticsTester semanticsTester = SemanticsTester(tester); + semanticEvents.clear(); int callbackCount = 0; void callback() { callbackCount++; diff --git a/packages/flutter_driver/test/src/real_tests/extension_test.dart b/packages/flutter_driver/test/src/real_tests/extension_test.dart index 5ea6396571..27f2a7c585 100644 --- a/packages/flutter_driver/test/src/real_tests/extension_test.dart +++ b/packages/flutter_driver/test/src/real_tests/extension_test.dart @@ -280,7 +280,7 @@ void main() { 'response': {}, }, ); - }); + }, semanticsEnabled: false); // Disabling semantics to prevent the unexpected message. testWidgets( 'waiting for NoPendingPlatformMessages returns until a single method channel call returns', (WidgetTester tester) async { @@ -313,7 +313,7 @@ void main() { 'response': {}, }, ); - }); + }, semanticsEnabled: false); // Disabling semantics to prevent the unexpected message. testWidgets( 'waiting for NoPendingPlatformMessages returns until both method channel calls return', (WidgetTester tester) async { @@ -362,7 +362,7 @@ void main() { 'response': {}, }, ); - }); + }, semanticsEnabled: false); // Disabling semantics to prevent the unexpected message. testWidgets( 'waiting for NoPendingPlatformMessages returns until new method channel call returns', (WidgetTester tester) async { @@ -413,7 +413,7 @@ void main() { 'response': {}, }, ); - }); + }, semanticsEnabled: false); // Disabling semantics to prevent the unexpected message. testWidgets( 'waiting for NoPendingPlatformMessages returns until both old and new method channel calls return', (WidgetTester tester) async { @@ -463,7 +463,7 @@ void main() { 'response': {}, }, ); - }); + }, semanticsEnabled: false); // Disabling semantics to prevent the unexpected message. }); group('getSemanticsId', () {