From d064c95c1065e71ea397aa40767c2fa828d07036 Mon Sep 17 00:00:00 2001 From: Koji Wakamiya Date: Mon, 2 Jun 2025 22:58:26 +0900 Subject: [PATCH] [Web][Engine] Update MediaQuery in response to semanticsEnabled (#166836) fix https://github.com/flutter/flutter/issues/134980 Calling `EnginePlatformDispatcher.instance.invokeOnAccessibilityFeaturesChanged();` after `EnginePlatformDispatcher.instance.configuration = newConfiguration` to notify update configuration event to `MediaQuery`. before https://github.com/user-attachments/assets/89969cc7-f9fa-4ac0-8ce0-d026d5676f27 after https://github.com/user-attachments/assets/8a284d42-e344-4039-8569-8567956326b7
Example Code ```dart import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; void main() { runApp(const MaterialApp(home: MyHomePage())); } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { SemanticsBinding.instance.ensureSemantics(); }, child: const Text('Enable a11y'), ), const SizedBox(height: 24), ElevatedButton( onPressed: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text('Should stay visible'), action: SnackBarAction(label: 'Action', onPressed: () {}), ), ); }, child: const Text('Show snackbar'), ), const SizedBox(height: 24), Text( 'MediaQuery.accessibleNavigationOf(context): ${MediaQuery.accessibleNavigationOf(context)}', ), Text( 'SemanticsBinding.instance.semanticsEnabled: ${SemanticsBinding.instance.semanticsEnabled}', ), Text( 'SemanticsBinding.instance.platformDispatcher.semanticsEnabled: ${SemanticsBinding.instance.platformDispatcher.semanticsEnabled}', ), Text( 'SemanticsBinding.instance.accessibilityFeatures: ${SemanticsBinding.instance.accessibilityFeatures}', ), ], ), ), ); } } ```
## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: chunhtai <47866232+chunhtai@users.noreply.github.com> Co-authored-by: Mouad Debbar --- .../web_ui/lib/src/engine/semantics/semantics.dart | 1 + .../flutter/lib/web_ui/test/engine/window_test.dart | 13 +++++++++++++ packages/flutter/lib/src/semantics/binding.dart | 13 ++++++++++++- packages/flutter/test/material/app_test.dart | 2 +- packages/flutter/test/widgets/media_query_test.dart | 4 ++-- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart index d571e631c88..014fffbbd96 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -2477,6 +2477,7 @@ class EngineSemantics { final PlatformConfiguration newConfiguration = EnginePlatformDispatcher.instance.configuration .copyWith(accessibilityFeatures: original.copyWith(accessibleNavigation: value)); EnginePlatformDispatcher.instance.configuration = newConfiguration; + EnginePlatformDispatcher.instance.invokeOnAccessibilityFeaturesChanged(); _semanticsEnabled = value; diff --git a/engine/src/flutter/lib/web_ui/test/engine/window_test.dart b/engine/src/flutter/lib/web_ui/test/engine/window_test.dart index 73a2bd50a16..822520cf968 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/window_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/window_test.dart @@ -334,6 +334,19 @@ Future testMain() async { EnginePlatformDispatcher.instance.invokeOnAccessibilityFeaturesChanged(); }); + test('onAccessibilityFeaturesChanged is called when semantics is enabled', () { + bool a11yChangeInvoked = false; + myWindow.onAccessibilityFeaturesChanged = () { + a11yChangeInvoked = true; + }; + + expect(EngineSemantics.instance.semanticsEnabled, isFalse); + EngineSemantics.instance.semanticsEnabled = true; + + expect(EngineSemantics.instance.semanticsEnabled, isTrue); + expect(a11yChangeInvoked, isTrue); + }); + test('onPlatformMessage preserves the zone', () { final Zone innerZone = Zone.current.fork(); diff --git a/packages/flutter/lib/src/semantics/binding.dart b/packages/flutter/lib/src/semantics/binding.dart index e09ab653843..b02315df9ea 100644 --- a/packages/flutter/lib/src/semantics/binding.dart +++ b/packages/flutter/lib/src/semantics/binding.dart @@ -10,6 +10,7 @@ library; import 'dart:ui' as ui show AccessibilityFeatures, SemanticsActionEvent, SemanticsUpdateBuilder; import 'package:flutter/foundation.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'debug.dart'; @@ -26,7 +27,17 @@ mixin SemanticsBinding on BindingBase { platformDispatcher ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged ..onSemanticsActionEvent = _handleSemanticsActionEvent - ..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; + ..onAccessibilityFeaturesChanged = () { + // TODO(chunhtai): Web should not notify accessibility feature changes during updateSemantics + // https://github.com/flutter/flutter/issues/158399 + if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) { + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + handleAccessibilityFeaturesChanged(); + }, debugLabel: 'SemanticsBinding.handleAccessibilityFeaturesChanged'); + } else { + handleAccessibilityFeaturesChanged(); + } + }; _handleSemanticsEnabledChanged(); } diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart index af9e0565ea7..ee04f3d2524 100644 --- a/packages/flutter/test/material/app_test.dart +++ b/packages/flutter/test/material/app_test.dart @@ -425,7 +425,7 @@ void main() { // didChangeAccessibilityFeatures tester.platformDispatcher.accessibilityFeaturesTestValue = FakeAccessibilityFeatures.allOn; - await tester.pump(); + await tester.pumpAndSettle(); expect(routeBuildCount, equals(1)); expect(dependentBuildCount, equals(5)); diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index 8ca9fddcf8b..6f671c3841b 100644 --- a/packages/flutter/test/widgets/media_query_test.dart +++ b/packages/flutter/test/widgets/media_query_test.dart @@ -451,7 +451,7 @@ void main() { expect(data.accessibleNavigation, true); tester.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures(); - await tester.pump(); + await tester.pumpAndSettle(); expect(data.accessibleNavigation, false); expect(rebuildCount, 4); @@ -512,7 +512,7 @@ void main() { expect(data.accessibleNavigation, true); tester.platformDispatcher.accessibilityFeaturesTestValue = const FakeAccessibilityFeatures(); - await tester.pump(); + await tester.pumpAndSettle(); expect(data.accessibleNavigation, true); expect(rebuildCount, 1);