Skip to content

Commit 00b7292

Browse files
authored
[Linux][A11y] report disabled animations and high contrast accessibility features (flutter#33313)
* FlEngine: add fl_engine_update_accessibility_features() * FlSettings: add API for high-contrast & animations * FlEngine: allow passing mock messenger at construction time * FlSettingsPlugin: report accessibility features
1 parent ee56813 commit 00b7292

14 files changed

+294
-15
lines changed

shell/platform/linux/fl_engine.cc

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ G_DEFINE_TYPE_WITH_CODE(
7373
G_IMPLEMENT_INTERFACE(fl_plugin_registry_get_type(),
7474
fl_engine_plugin_registry_iface_init))
7575

76+
enum { PROP_0, PROP_BINARY_MESSENGER, PROP_LAST };
77+
7678
// Parse a locale into its components.
7779
static void parse_locale(const gchar* locale,
7880
gchar** language,
@@ -351,6 +353,22 @@ static void fl_engine_plugin_registry_iface_init(
351353
iface->get_registrar_for_plugin = fl_engine_get_registrar_for_plugin;
352354
}
353355

356+
static void fl_engine_set_property(GObject* object,
357+
guint prop_id,
358+
const GValue* value,
359+
GParamSpec* pspec) {
360+
FlEngine* self = FL_ENGINE(object);
361+
switch (prop_id) {
362+
case PROP_BINARY_MESSENGER:
363+
g_set_object(&self->binary_messenger,
364+
FL_BINARY_MESSENGER(g_value_get_object(value)));
365+
break;
366+
default:
367+
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
368+
break;
369+
}
370+
}
371+
354372
static void fl_engine_dispose(GObject* object) {
355373
FlEngine* self = FL_ENGINE(object);
356374

@@ -397,6 +415,15 @@ static void fl_engine_dispose(GObject* object) {
397415

398416
static void fl_engine_class_init(FlEngineClass* klass) {
399417
G_OBJECT_CLASS(klass)->dispose = fl_engine_dispose;
418+
G_OBJECT_CLASS(klass)->set_property = fl_engine_set_property;
419+
420+
g_object_class_install_property(
421+
G_OBJECT_CLASS(klass), PROP_BINARY_MESSENGER,
422+
g_param_spec_object(
423+
"binary-messenger", "messenger", "Binary messenger",
424+
fl_binary_messenger_get_type(),
425+
static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
426+
G_PARAM_STATIC_STRINGS)));
400427
}
401428

402429
static void fl_engine_init(FlEngine* self) {
@@ -406,7 +433,6 @@ static void fl_engine_init(FlEngine* self) {
406433
FlutterEngineGetProcAddresses(&self->embedder_api);
407434

408435
self->texture_registrar = fl_texture_registrar_new(self);
409-
self->binary_messenger = fl_binary_messenger_new(self);
410436
}
411437

412438
FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer) {
@@ -416,6 +442,7 @@ FlEngine* fl_engine_new(FlDartProject* project, FlRenderer* renderer) {
416442
FlEngine* self = FL_ENGINE(g_object_new(fl_engine_get_type(), nullptr));
417443
self->project = FL_DART_PROJECT(g_object_ref(project));
418444
self->renderer = FL_RENDERER(g_object_ref(renderer));
445+
self->binary_messenger = fl_binary_messenger_new(self);
419446
return self;
420447
}
421448

@@ -522,7 +549,7 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
522549
setup_locales(self);
523550

524551
g_autoptr(FlSettings) settings = fl_settings_new();
525-
self->settings_plugin = fl_settings_plugin_new(self->binary_messenger);
552+
self->settings_plugin = fl_settings_plugin_new(self);
526553
fl_settings_plugin_start(self->settings_plugin, settings);
527554

528555
result = self->embedder_api.UpdateSemanticsEnabled(self->engine, TRUE);
@@ -844,3 +871,14 @@ G_MODULE_EXPORT FlTextureRegistrar* fl_engine_get_texture_registrar(
844871
g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);
845872
return self->texture_registrar;
846873
}
874+
875+
void fl_engine_update_accessibility_features(FlEngine* self, int32_t flags) {
876+
g_return_if_fail(FL_IS_ENGINE(self));
877+
878+
if (self->engine == nullptr) {
879+
return;
880+
}
881+
882+
self->embedder_api.UpdateAccessibilityFeatures(
883+
self->engine, static_cast<FlutterAccessibilityFeature>(flags));
884+
}

shell/platform/linux/fl_engine_private.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@ gboolean fl_engine_register_external_texture(FlEngine* engine,
327327
gboolean fl_engine_unregister_external_texture(FlEngine* engine,
328328
int64_t texture_id);
329329

330+
/**
331+
* fl_engine_update_accessibility_features:
332+
* @engine: an #FlEngine.
333+
* @flags: the features to enable in the accessibility tree.
334+
*
335+
* Tells the Flutter engine to update the flags on the accessibility tree.
336+
*/
337+
void fl_engine_update_accessibility_features(FlEngine* engine, int32_t flags);
338+
330339
G_END_DECLS
331340

332341
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_

shell/platform/linux/fl_gnome_settings.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ static FlColorScheme fl_gnome_settings_get_color_scheme(FlSettings* settings) {
6363
return color_scheme;
6464
}
6565

66+
static gboolean fl_gnome_settings_get_enable_animations(FlSettings* settings) {
67+
return true;
68+
}
69+
70+
static gboolean fl_gnome_settings_get_high_contrast(FlSettings* settings) {
71+
return false;
72+
}
73+
6674
static gdouble fl_gnome_settings_get_text_scaling_factor(FlSettings* settings) {
6775
FlGnomeSettings* self = FL_GNOME_SETTINGS(settings);
6876

@@ -133,6 +141,8 @@ static void fl_gnome_settings_class_init(FlGnomeSettingsClass* klass) {
133141
static void fl_gnome_settings_iface_init(FlSettingsInterface* iface) {
134142
iface->get_clock_format = fl_gnome_settings_get_clock_format;
135143
iface->get_color_scheme = fl_gnome_settings_get_color_scheme;
144+
iface->get_enable_animations = fl_gnome_settings_get_enable_animations;
145+
iface->get_high_contrast = fl_gnome_settings_get_high_contrast;
136146
iface->get_text_scaling_factor = fl_gnome_settings_get_text_scaling_factor;
137147
}
138148

shell/platform/linux/fl_gnome_settings_test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ TEST_F(FlGnomeSettingsTest, GtkTheme) {
6969
EXPECT_EQ(fl_settings_get_color_scheme(settings), FL_COLOR_SCHEME_DARK);
7070
}
7171

72+
TEST_F(FlGnomeSettingsTest, EnableAnimations) {
73+
g_autoptr(FlSettings) settings = fl_gnome_settings_new();
74+
EXPECT_TRUE(fl_settings_get_enable_animations(settings));
75+
}
76+
77+
TEST_F(FlGnomeSettingsTest, HighContrast) {
78+
g_autoptr(FlSettings) settings = fl_gnome_settings_new();
79+
EXPECT_FALSE(fl_settings_get_high_contrast(settings));
80+
}
81+
7282
TEST_F(FlGnomeSettingsTest, TextScalingFactor) {
7383
g_autoptr(GSettings) interface_settings =
7484
create_settings("ubuntu-20.04", "org.gnome.desktop.interface");

shell/platform/linux/fl_settings.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ FlColorScheme fl_settings_get_color_scheme(FlSettings* self) {
3535
return FL_SETTINGS_GET_IFACE(self)->get_color_scheme(self);
3636
}
3737

38+
gboolean fl_settings_get_enable_animations(FlSettings* self) {
39+
return FL_SETTINGS_GET_IFACE(self)->get_enable_animations(self);
40+
}
41+
42+
gboolean fl_settings_get_high_contrast(FlSettings* self) {
43+
return FL_SETTINGS_GET_IFACE(self)->get_high_contrast(self);
44+
}
45+
3846
gdouble fl_settings_get_text_scaling_factor(FlSettings* self) {
3947
return FL_SETTINGS_GET_IFACE(self)->get_text_scaling_factor(self);
4048
}

shell/platform/linux/fl_settings.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ struct _FlSettingsInterface {
4343
GTypeInterface parent;
4444
FlClockFormat (*get_clock_format)(FlSettings* settings);
4545
FlColorScheme (*get_color_scheme)(FlSettings* settings);
46+
gboolean (*get_enable_animations)(FlSettings* settings);
47+
gboolean (*get_high_contrast)(FlSettings* settings);
4648
gdouble (*get_text_scaling_factor)(FlSettings* settings);
4749
};
4850

@@ -79,6 +81,31 @@ FlClockFormat fl_settings_get_clock_format(FlSettings* settings);
7981
*/
8082
FlColorScheme fl_settings_get_color_scheme(FlSettings* settings);
8183

84+
/**
85+
* fl_settings_get_enable_animations:
86+
* @settings: an #FlSettings.
87+
*
88+
* Whether animations should be enabled.
89+
*
90+
* This corresponds to `org.gnome.desktop.interface.enable-animations` in GNOME.
91+
*
92+
* Returns: %TRUE if animations are enabled.
93+
*/
94+
gboolean fl_settings_get_enable_animations(FlSettings* settings);
95+
96+
/**
97+
* fl_settings_get_high_contrast:
98+
* @settings: an #FlSettings.
99+
*
100+
* Whether to use high contrast theme.
101+
*
102+
* This corresponds to `org.gnome.desktop.a11y.interface.high-contrast` in
103+
* GNOME.
104+
*
105+
* Returns: %TRUE if high contrast is used.
106+
*/
107+
gboolean fl_settings_get_high_contrast(FlSettings* settings);
108+
82109
/**
83110
* fl_settings_get_text_scaling_factor:
84111
* @settings: an #FlSettings.

shell/platform/linux/fl_settings_plugin.cc

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
#include <gmodule.h>
88

9+
#include "flutter/shell/platform/embedder/embedder.h"
10+
#include "flutter/shell/platform/linux/fl_engine_private.h"
911
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
12+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
1013
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
1114

1215
static constexpr char kChannelName[] = "flutter/settings";
@@ -20,7 +23,7 @@ struct _FlSettingsPlugin {
2023
GObject parent_instance;
2124

2225
FlBasicMessageChannel* channel;
23-
26+
FlEngine* engine;
2427
FlSettings* settings;
2528
};
2629

@@ -54,6 +57,17 @@ static void update_settings(FlSettingsPlugin* self) {
5457
fl_value_new_string(to_platform_brightness(color_scheme)));
5558
fl_basic_message_channel_send(self->channel, message, nullptr, nullptr,
5659
nullptr);
60+
61+
if (self->engine != nullptr) {
62+
int32_t flags = 0;
63+
if (!fl_settings_get_enable_animations(self->settings)) {
64+
flags |= kFlutterAccessibilityFeatureDisableAnimations;
65+
}
66+
if (fl_settings_get_high_contrast(self->settings)) {
67+
flags |= kFlutterAccessibilityFeatureHighContrast;
68+
}
69+
fl_engine_update_accessibility_features(self->engine, flags);
70+
}
5771
}
5872

5973
static void fl_settings_plugin_dispose(GObject* object) {
@@ -62,6 +76,12 @@ static void fl_settings_plugin_dispose(GObject* object) {
6276
g_clear_object(&self->channel);
6377
g_clear_object(&self->settings);
6478

79+
if (self->engine != nullptr) {
80+
g_object_remove_weak_pointer(G_OBJECT(self),
81+
reinterpret_cast<gpointer*>(&(self->engine)));
82+
self->engine = nullptr;
83+
}
84+
6585
G_OBJECT_CLASS(fl_settings_plugin_parent_class)->dispose(object);
6686
}
6787

@@ -71,12 +91,17 @@ static void fl_settings_plugin_class_init(FlSettingsPluginClass* klass) {
7191

7292
static void fl_settings_plugin_init(FlSettingsPlugin* self) {}
7393

74-
FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger) {
75-
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
94+
FlSettingsPlugin* fl_settings_plugin_new(FlEngine* engine) {
95+
g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);
7696

7797
FlSettingsPlugin* self =
7898
FL_SETTINGS_PLUGIN(g_object_new(fl_settings_plugin_get_type(), nullptr));
7999

100+
self->engine = engine;
101+
g_object_add_weak_pointer(G_OBJECT(self),
102+
reinterpret_cast<gpointer*>(&(self->engine)));
103+
104+
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(engine);
80105
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
81106
self->channel = fl_basic_message_channel_new(messenger, kChannelName,
82107
FL_MESSAGE_CODEC(codec));

shell/platform/linux/fl_settings_plugin.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_SETTINGS_PLUGIN_H_
77

88
#include "flutter/shell/platform/linux/fl_settings.h"
9-
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
9+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h"
1010

1111
G_BEGIN_DECLS
1212

@@ -25,13 +25,13 @@ G_DECLARE_FINAL_TYPE(FlSettingsPlugin,
2525

2626
/**
2727
* fl_settings_plugin_new:
28-
* @messenger: an #FlBinaryMessenger
28+
* @messenger: an #FlEngine
2929
*
3030
* Creates a new plugin that sends user settings to the Flutter engine.
3131
*
3232
* Returns: a new #FlSettingsPlugin
3333
*/
34-
FlSettingsPlugin* fl_settings_plugin_new(FlBinaryMessenger* messenger);
34+
FlSettingsPlugin* fl_settings_plugin_new(FlEngine* engine);
3535

3636
/**
3737
* fl_settings_plugin_start:

shell/platform/linux/fl_settings_plugin_test.cc

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// found in the LICENSE file.
44

55
#include "flutter/shell/platform/linux/fl_settings_plugin.h"
6+
#include "flutter/shell/platform/embedder/embedder.h"
7+
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
8+
#include "flutter/shell/platform/linux/fl_engine_private.h"
69
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
710
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
811
#include "flutter/shell/platform/linux/public/flutter_linux/fl_value.h"
@@ -38,7 +41,9 @@ TEST(FlSettingsPluginTest, AlwaysUse24HourFormat) {
3841
::testing::NiceMock<flutter::testing::MockSettings> settings;
3942
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
4043

41-
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
44+
g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(
45+
fl_engine_get_type(), "binary-messenger", messenger, nullptr));
46+
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);
4247

4348
g_autoptr(FlValue) use_12h = fl_value_new_bool(false);
4449
g_autoptr(FlValue) use_24h = fl_value_new_bool(true);
@@ -61,7 +66,9 @@ TEST(FlSettingsPluginTest, PlatformBrightness) {
6166
::testing::NiceMock<flutter::testing::MockSettings> settings;
6267
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
6368

64-
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
69+
g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(
70+
fl_engine_get_type(), "binary-messenger", messenger, nullptr));
71+
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);
6572

6673
g_autoptr(FlValue) light = fl_value_new_string("light");
6774
g_autoptr(FlValue) dark = fl_value_new_string("dark");
@@ -84,7 +91,9 @@ TEST(FlSettingsPluginTest, TextScaleFactor) {
8491
::testing::NiceMock<flutter::testing::MockSettings> settings;
8592
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
8693

87-
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(messenger);
94+
g_autoptr(FlEngine) engine = FL_ENGINE(g_object_new(
95+
fl_engine_get_type(), "binary-messenger", messenger, nullptr));
96+
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);
8897

8998
g_autoptr(FlValue) one = fl_value_new_float(1.0);
9099
g_autoptr(FlValue) two = fl_value_new_float(2.0);
@@ -102,3 +111,57 @@ TEST(FlSettingsPluginTest, TextScaleFactor) {
102111

103112
fl_settings_emit_changed(settings);
104113
}
114+
115+
// MOCK_ENGINE_PROC is leaky by design
116+
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
117+
TEST(FlSettingsPluginTest, AccessibilityFeatures) {
118+
g_autoptr(FlEngine) engine = make_mock_engine();
119+
FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine);
120+
121+
std::vector<FlutterAccessibilityFeature> calls;
122+
embedder_api->UpdateAccessibilityFeatures = MOCK_ENGINE_PROC(
123+
UpdateAccessibilityFeatures,
124+
([&calls](auto engine, FlutterAccessibilityFeature features) {
125+
calls.push_back(features);
126+
return kSuccess;
127+
}));
128+
129+
g_autoptr(FlSettingsPlugin) plugin = fl_settings_plugin_new(engine);
130+
131+
::testing::NiceMock<flutter::testing::MockSettings> settings;
132+
133+
EXPECT_CALL(settings, fl_settings_get_enable_animations(
134+
::testing::Eq<FlSettings*>(settings)))
135+
.WillOnce(::testing::Return(false))
136+
.WillOnce(::testing::Return(true))
137+
.WillOnce(::testing::Return(false))
138+
.WillOnce(::testing::Return(true));
139+
140+
EXPECT_CALL(settings, fl_settings_get_high_contrast(
141+
::testing::Eq<FlSettings*>(settings)))
142+
.WillOnce(::testing::Return(true))
143+
.WillOnce(::testing::Return(false))
144+
.WillOnce(::testing::Return(false))
145+
.WillOnce(::testing::Return(true));
146+
147+
fl_settings_plugin_start(plugin, settings);
148+
EXPECT_THAT(calls, ::testing::SizeIs(1));
149+
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(
150+
kFlutterAccessibilityFeatureDisableAnimations |
151+
kFlutterAccessibilityFeatureHighContrast));
152+
153+
fl_settings_emit_changed(settings);
154+
EXPECT_THAT(calls, ::testing::SizeIs(2));
155+
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(0));
156+
157+
fl_settings_emit_changed(settings);
158+
EXPECT_THAT(calls, ::testing::SizeIs(3));
159+
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(
160+
kFlutterAccessibilityFeatureDisableAnimations));
161+
162+
fl_settings_emit_changed(settings);
163+
EXPECT_THAT(calls, ::testing::SizeIs(4));
164+
EXPECT_EQ(calls.back(), static_cast<FlutterAccessibilityFeature>(
165+
kFlutterAccessibilityFeatureHighContrast));
166+
}
167+
// NOLINTEND(clang-analyzer-core.StackAddressEscape)

0 commit comments

Comments
 (0)