From 53318be751bec11e685cabbb63351bc4cf66cd27 Mon Sep 17 00:00:00 2001 From: nbayati <99771966+nbayati@users.noreply.github.com> Date: Mon, 23 May 2022 17:58:05 -0700 Subject: [PATCH] Update accessibleNavigation when the state of accessibility changes (flutter/engine#33576) --- .../lib/web_ui/lib/platform_dispatcher.dart | 2 +- .../lib/src/engine/platform_dispatcher.dart | 13 +- .../lib/src/engine/semantics/semantics.dart | 111 ++++++++++++++++++ engine/src/flutter/lib/web_ui/lib/window.dart | 68 ++--------- .../test/engine/semantics/semantics_test.dart | 75 ++++++++++++ 5 files changed, 201 insertions(+), 68 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart index 5cc6175738e..dcc9a80543f 100644 --- a/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart +++ b/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart @@ -120,7 +120,7 @@ abstract class PlatformDispatcher { class PlatformConfiguration { const PlatformConfiguration({ - this.accessibilityFeatures = const AccessibilityFeatures._(0), + this.accessibilityFeatures = const engine.EngineAccessibilityFeatures(0), this.alwaysUse24HourFormat = false, this.semanticsEnabled = false, this.platformBrightness = Brightness.light, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 76a6da34438..eaff1941d9d 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -54,8 +54,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// The current platform configuration. @override - ui.PlatformConfiguration get configuration => _configuration; - ui.PlatformConfiguration _configuration = ui.PlatformConfiguration( + ui.PlatformConfiguration configuration = ui.PlatformConfiguration( locales: parseBrowserLanguages(), textScaleFactor: findBrowserTextScaleFactor(), ); @@ -725,12 +724,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// The empty list is not a valid value for locales. This is only used for /// testing locale update logic. void debugResetLocales() { - _configuration = _configuration.copyWith(locales: const []); + configuration = configuration.copyWith(locales: const []); } // Called by FlutterViewEmbedder when browser languages change. void updateLocales() { - _configuration = _configuration.copyWith(locales: parseBrowserLanguages()); + configuration = configuration.copyWith(locales: parseBrowserLanguages()); } static List parseBrowserLanguages() { @@ -788,7 +787,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// [onPlatformConfigurationChanged] callbacks if [textScaleFactor] changed. void _updateTextScaleFactor(double value) { if (configuration.textScaleFactor != value) { - _configuration = configuration.copyWith(textScaleFactor: value); + configuration = configuration.copyWith(textScaleFactor: value); invokeOnPlatformConfigurationChanged(); invokeOnTextScaleFactorChanged(); } @@ -859,7 +858,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { void updateSemanticsEnabled(bool semanticsEnabled) { if (semanticsEnabled != this.semanticsEnabled) { - _configuration = _configuration.copyWith(semanticsEnabled: semanticsEnabled); + configuration = configuration.copyWith(semanticsEnabled: semanticsEnabled); if (_onSemanticsEnabledChanged != null) { invokeOnSemanticsEnabledChanged(); } @@ -875,7 +874,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// callback if [_platformBrightness] changed. void _updatePlatformBrightness(ui.Brightness value) { if (configuration.platformBrightness != value) { - _configuration = configuration.copyWith(platformBrightness: value); + configuration = configuration.copyWith(platformBrightness: value); invokeOnPlatformConfigurationChanged(); invokeOnPlatformBrightnessChanged(); } 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 01feefbe230..a72b7150532 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 @@ -26,6 +26,108 @@ import 'semantics_helper.dart'; import 'tappable.dart'; import 'text_field.dart'; +class EngineAccessibilityFeatures implements ui.AccessibilityFeatures { + const EngineAccessibilityFeatures(this._index); + + static const int _kAccessibleNavigation = 1 << 0; + static const int _kInvertColorsIndex = 1 << 1; + static const int _kDisableAnimationsIndex = 1 << 2; + static const int _kBoldTextIndex = 1 << 3; + static const int _kReduceMotionIndex = 1 << 4; + static const int _kHighContrastIndex = 1 << 5; + static const int _kOnOffSwitchLabelsIndex = 1 << 6; + + // A bitfield which represents each enabled feature. + final int _index; + + @override + bool get accessibleNavigation => _kAccessibleNavigation & _index != 0; + @override + bool get invertColors => _kInvertColorsIndex & _index != 0; + @override + bool get disableAnimations => _kDisableAnimationsIndex & _index != 0; + @override + bool get boldText => _kBoldTextIndex & _index != 0; + @override + bool get reduceMotion => _kReduceMotionIndex & _index != 0; + @override + bool get highContrast => _kHighContrastIndex & _index != 0; + @override + bool get onOffSwitchLabels => _kOnOffSwitchLabelsIndex & _index != 0; + + @override + String toString() { + final List features = []; + if (accessibleNavigation) { + features.add('accessibleNavigation'); + } + if (invertColors) { + features.add('invertColors'); + } + if (disableAnimations) { + features.add('disableAnimations'); + } + if (boldText) { + features.add('boldText'); + } + if (reduceMotion) { + features.add('reduceMotion'); + } + if (highContrast) { + features.add('highContrast'); + } + if (onOffSwitchLabels) { + features.add('onOffSwitchLabels'); + } + return 'AccessibilityFeatures$features'; + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is EngineAccessibilityFeatures && other._index == _index; + } + + @override + int get hashCode => _index.hashCode; + + EngineAccessibilityFeatures copyWith({ + bool? accessibleNavigation, + bool? invertColors, + bool? disableAnimations, + bool? boldText, + bool? reduceMotion, + bool? highContrast, + bool? onOffSwitchLabels}) + { + int value = 0; + if (accessibleNavigation ?? this.accessibleNavigation) { + value = value | _kAccessibleNavigation; + } + if (invertColors ?? this.invertColors) { + value = value | _kInvertColorsIndex; + } + if (disableAnimations ?? this.disableAnimations) { + value = value | _kDisableAnimationsIndex; + } + if (boldText ?? this.boldText) { + value = value | _kBoldTextIndex; + } + if (reduceMotion ?? this.reduceMotion) { + value = value | _kReduceMotionIndex; + } + if (highContrast ?? this.highContrast) { + value = value | _kHighContrastIndex; + } + if (onOffSwitchLabels ?? this.onOffSwitchLabels) { + value = value | _kOnOffSwitchLabelsIndex; + } + return EngineAccessibilityFeatures(value); + } +} + /// Contains updates for the semantics tree. /// /// This class provides private engine-side API that's not available in the @@ -1433,6 +1535,15 @@ class EngineSemanticsOwner { if (value == _semanticsEnabled) { return; } + final EngineAccessibilityFeatures original = + EnginePlatformDispatcher.instance.configuration.accessibilityFeatures + as EngineAccessibilityFeatures; + final ui.PlatformConfiguration newConfiguration = + EnginePlatformDispatcher.instance.configuration.copyWith( + accessibilityFeatures: + original.copyWith(accessibleNavigation: value)); + EnginePlatformDispatcher.instance.configuration = newConfiguration; + _semanticsEnabled = value; if (!_semanticsEnabled) { diff --git a/engine/src/flutter/lib/web_ui/lib/window.dart b/engine/src/flutter/lib/web_ui/lib/window.dart index 43711551cd9..27020e44463 100644 --- a/engine/src/flutter/lib/web_ui/lib/window.dart +++ b/engine/src/flutter/lib/web_ui/lib/window.dart @@ -148,66 +148,14 @@ abstract class SingletonFlutterWindow extends FlutterWindow { void setIsolateDebugName(String name) => PlatformDispatcher.instance.setIsolateDebugName(name); } -class AccessibilityFeatures { - const AccessibilityFeatures._(this._index); - - static const int _kAccessibleNavigation = 1 << 0; - static const int _kInvertColorsIndex = 1 << 1; - static const int _kDisableAnimationsIndex = 1 << 2; - static const int _kBoldTextIndex = 1 << 3; - static const int _kReduceMotionIndex = 1 << 4; - static const int _kHighContrastIndex = 1 << 5; - static const int _kOnOffSwitchLabelsIndex = 1 << 6; - - // A bitfield which represents each enabled feature. - final int _index; - - bool get accessibleNavigation => _kAccessibleNavigation & _index != 0; - bool get invertColors => _kInvertColorsIndex & _index != 0; - bool get disableAnimations => _kDisableAnimationsIndex & _index != 0; - bool get boldText => _kBoldTextIndex & _index != 0; - bool get reduceMotion => _kReduceMotionIndex & _index != 0; - bool get highContrast => _kHighContrastIndex & _index != 0; - bool get onOffSwitchLabels => _kOnOffSwitchLabelsIndex & _index != 0; - - @override - String toString() { - final List features = []; - if (accessibleNavigation) { - features.add('accessibleNavigation'); - } - if (invertColors) { - features.add('invertColors'); - } - if (disableAnimations) { - features.add('disableAnimations'); - } - if (boldText) { - features.add('boldText'); - } - if (reduceMotion) { - features.add('reduceMotion'); - } - if (highContrast) { - features.add('highContrast'); - } - if (onOffSwitchLabels) { - features.add('onOffSwitchLabels'); - } - return 'AccessibilityFeatures$features'; - } - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) { - return false; - } - return other is AccessibilityFeatures - && other._index == _index; - } - - @override - int get hashCode => _index.hashCode; +abstract class AccessibilityFeatures { + bool get accessibleNavigation; + bool get invertColors; + bool get disableAnimations; + bool get boldText; + bool get reduceMotion; + bool get highContrast; + bool get onOffSwitchLabels; } enum Brightness { diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart index d6d356967ba..a888e7a62e4 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -118,9 +118,79 @@ void _testEngineSemanticsOwner() { expect(placeholder.isConnected, isFalse); }); + test('accessibilityFeatures copyWith function works', () { + const EngineAccessibilityFeatures original = EngineAccessibilityFeatures(0); + EngineAccessibilityFeatures copy = original.copyWith(accessibleNavigation: true); + expect(copy.accessibleNavigation, true); + expect(copy.boldText, false); + expect(copy.disableAnimations, false); + expect(copy.highContrast, false); + expect(copy.invertColors, false); + expect(copy.onOffSwitchLabels, false); + expect(copy.reduceMotion, false); + + copy = original.copyWith(boldText: true); + expect(copy.accessibleNavigation, false); + expect(copy.boldText, true); + expect(copy.disableAnimations, false); + expect(copy.highContrast, false); + expect(copy.invertColors, false); + expect(copy.onOffSwitchLabels, false); + expect(copy.reduceMotion, false); + + copy = original.copyWith(disableAnimations: true); + expect(copy.accessibleNavigation, false); + expect(copy.boldText, false); + expect(copy.disableAnimations, true); + expect(copy.highContrast, false); + expect(copy.invertColors, false); + expect(copy.onOffSwitchLabels, false); + expect(copy.reduceMotion, false); + + copy = original.copyWith(highContrast: true); + expect(copy.accessibleNavigation, false); + expect(copy.boldText, false); + expect(copy.disableAnimations, false); + expect(copy.highContrast, true); + expect(copy.invertColors, false); + expect(copy.onOffSwitchLabels, false); + expect(copy.reduceMotion, false); + + copy = original.copyWith(invertColors: true); + expect(copy.accessibleNavigation, false); + expect(copy.boldText, false); + expect(copy.disableAnimations, false); + expect(copy.highContrast, false); + expect(copy.invertColors, true); + expect(copy.onOffSwitchLabels, false); + expect(copy.reduceMotion, false); + + copy = original.copyWith(onOffSwitchLabels: true); + expect(copy.accessibleNavigation, false); + expect(copy.boldText, false); + expect(copy.disableAnimations, false); + expect(copy.highContrast, false); + expect(copy.invertColors, false); + expect(copy.onOffSwitchLabels, true); + expect(copy.reduceMotion, false); + + copy = original.copyWith(reduceMotion: true); + expect(copy.accessibleNavigation, false); + expect(copy.boldText, false); + expect(copy.disableAnimations, false); + expect(copy.highContrast, false); + expect(copy.invertColors, false); + expect(copy.onOffSwitchLabels, false); + expect(copy.reduceMotion, true); + }); + test('auto-enables semantics', () async { flutterViewEmbedder.reset(); // triggers `autoEnableOnTap` to be called expect(semantics().semanticsEnabled, isFalse); + expect( + EnginePlatformDispatcher + .instance.configuration.accessibilityFeatures.accessibleNavigation, + isFalse); final html.Element placeholder = appHostNode.querySelector('flt-semantics-placeholder')!; @@ -133,6 +203,10 @@ void _testEngineSemanticsOwner() { semantics().updateSemantics(builder.build()); expect(semantics().semanticsEnabled, isTrue); + expect( + EnginePlatformDispatcher + .instance.configuration.accessibilityFeatures.accessibleNavigation, + isTrue); // The placeholder should be removed expect(placeholder.isConnected, isFalse); @@ -1753,6 +1827,7 @@ void _testPlatformView() { rect: const ui.Rect.fromLTRB(0, 0, 20, 60), childrenInTraversalOrder: Int32List.fromList([1, 2, 3]), childrenInHitTestOrder: Int32List.fromList([1, 2, 3]), + transform: Float64List.fromList(Matrix4.diagonal3Values(ui.window.devicePixelRatio, ui.window.devicePixelRatio, 1).storage) ); updateNode( builder,