Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 24c7b03

Browse files
authored
[Windows] Introduce an accessibility plugin (#50975)
_This is the same pull request as #50898. GitHub broke on the previous pull request so I re-created it_ This moves the logic to handle `flutter/accessibility` messages to a new type, `AccessibilityPlugin`. Notable changes: 1. Windows app no longer crashes if it receives accessibility events it does not support 2. Windows app no longer crashes if it receives accessibility events while in headless mode @yaakovschectman After playing around with this, I ended up using a different pattern than what what I suggested on #50598 (comment). This message handler is simple enough that splitting into a child/base types felt like unnecessary boilerplate. The key thing is separating messaging and implementation logic, which was achieved through the `SetUp` method. Let me know what you think, and sorry for all my flip-flopping on this topic! � This is preparation for: flutter/flutter#143765 Sample app for manual testing: flutter/flutter#113059 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent c805e43 commit 24c7b03

File tree

8 files changed

+272
-61
lines changed

8 files changed

+272
-61
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29967,6 +29967,8 @@ ORIGIN: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h + .
2996729967
ORIGIN: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h + ../../../flutter/LICENSE
2996829968
ORIGIN: ../../../flutter/shell/platform/windows/accessibility_bridge_windows.cc + ../../../flutter/LICENSE
2996929969
ORIGIN: ../../../flutter/shell/platform/windows/accessibility_bridge_windows.h + ../../../flutter/LICENSE
29970+
ORIGIN: ../../../flutter/shell/platform/windows/accessibility_plugin.cc + ../../../flutter/LICENSE
29971+
ORIGIN: ../../../flutter/shell/platform/windows/accessibility_plugin.h + ../../../flutter/LICENSE
2997029972
ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/flutter_engine.cc + ../../../flutter/LICENSE
2997129973
ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/flutter_view_controller.cc + ../../../flutter/LICENSE
2997229974
ORIGIN: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h + ../../../flutter/LICENSE
@@ -32840,6 +32842,8 @@ FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/fl_view.h
3284032842
FILE: ../../../flutter/shell/platform/linux/public/flutter_linux/flutter_linux.h
3284132843
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_windows.cc
3284232844
FILE: ../../../flutter/shell/platform/windows/accessibility_bridge_windows.h
32845+
FILE: ../../../flutter/shell/platform/windows/accessibility_plugin.cc
32846+
FILE: ../../../flutter/shell/platform/windows/accessibility_plugin.h
3284332847
FILE: ../../../flutter/shell/platform/windows/client_wrapper/flutter_engine.cc
3284432848
FILE: ../../../flutter/shell/platform/windows/client_wrapper/flutter_view_controller.cc
3284532849
FILE: ../../../flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h

shell/platform/windows/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ source_set("flutter_windows_source") {
4141
sources = [
4242
"accessibility_bridge_windows.cc",
4343
"accessibility_bridge_windows.h",
44+
"accessibility_plugin.cc",
45+
"accessibility_plugin.h",
4446
"compositor.h",
4547
"compositor_opengl.cc",
4648
"compositor_opengl.h",
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/windows/accessibility_plugin.h"
6+
7+
#include <variant>
8+
9+
#include "flutter/fml/logging.h"
10+
#include "flutter/fml/platform/win/wstring_conversion.h"
11+
#include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h"
12+
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
13+
#include "flutter/shell/platform/windows/flutter_windows_view.h"
14+
15+
namespace flutter {
16+
17+
namespace {
18+
19+
static constexpr char kAccessibilityChannelName[] = "flutter/accessibility";
20+
static constexpr char kTypeKey[] = "type";
21+
static constexpr char kDataKey[] = "data";
22+
static constexpr char kMessageKey[] = "message";
23+
static constexpr char kAnnounceValue[] = "announce";
24+
25+
// Handles messages like:
26+
// {"type": "announce", "data": {"message": "Hello"}}
27+
void HandleMessage(AccessibilityPlugin* plugin, const EncodableValue& message) {
28+
const auto* map = std::get_if<EncodableMap>(&message);
29+
if (!map) {
30+
FML_LOG(ERROR) << "Accessibility message must be a map.";
31+
return;
32+
}
33+
const auto& type_itr = map->find(EncodableValue{kTypeKey});
34+
const auto& data_itr = map->find(EncodableValue{kDataKey});
35+
if (type_itr == map->end()) {
36+
FML_LOG(ERROR) << "Accessibility message must have a 'type' property.";
37+
return;
38+
}
39+
if (data_itr == map->end()) {
40+
FML_LOG(ERROR) << "Accessibility message must have a 'data' property.";
41+
return;
42+
}
43+
const auto* type = std::get_if<std::string>(&type_itr->second);
44+
const auto* data = std::get_if<EncodableMap>(&data_itr->second);
45+
if (!type) {
46+
FML_LOG(ERROR) << "Accessibility message 'type' property must be a string.";
47+
return;
48+
}
49+
if (!data) {
50+
FML_LOG(ERROR) << "Accessibility message 'data' property must be a map.";
51+
return;
52+
}
53+
54+
if (type->compare(kAnnounceValue) == 0) {
55+
const auto& message_itr = data->find(EncodableValue{kMessageKey});
56+
if (message_itr == data->end()) {
57+
return;
58+
}
59+
const auto* message = std::get_if<std::string>(&message_itr->second);
60+
if (!message) {
61+
return;
62+
}
63+
64+
plugin->Announce(*message);
65+
} else {
66+
FML_LOG(ERROR) << "Accessibility message type '" << *type
67+
<< "' is not supported.";
68+
}
69+
}
70+
71+
} // namespace
72+
73+
AccessibilityPlugin::AccessibilityPlugin(FlutterWindowsEngine* engine)
74+
: engine_(engine) {}
75+
76+
void AccessibilityPlugin::SetUp(BinaryMessenger* binary_messenger,
77+
AccessibilityPlugin* plugin) {
78+
BasicMessageChannel<> channel{binary_messenger, kAccessibilityChannelName,
79+
&StandardMessageCodec::GetInstance()};
80+
81+
channel.SetMessageHandler(
82+
[plugin](const EncodableValue& message,
83+
const MessageReply<EncodableValue>& reply) {
84+
HandleMessage(plugin, message);
85+
86+
// The accessibility channel does not support error handling.
87+
// Always return an empty response even on failure.
88+
reply(EncodableValue{std::monostate{}});
89+
});
90+
}
91+
92+
void AccessibilityPlugin::Announce(const std::string_view message) {
93+
if (!engine_->semantics_enabled()) {
94+
return;
95+
}
96+
97+
// TODO(loicsharma): Remove implicit view assumption.
98+
// https://github.com/flutter/flutter/issues/142845
99+
auto view = engine_->view(kImplicitViewId);
100+
if (!view) {
101+
return;
102+
}
103+
104+
std::wstring wide_text = fml::Utf8ToWideString(message);
105+
view->AnnounceAlert(wide_text);
106+
}
107+
108+
} // namespace flutter
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_PLUGIN_H_
6+
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_PLUGIN_H_
7+
8+
#include <string_view>
9+
10+
#include "flutter/fml/macros.h"
11+
#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h"
12+
13+
namespace flutter {
14+
15+
class FlutterWindowsEngine;
16+
17+
// Handles messages on the flutter/accessibility channel.
18+
//
19+
// See:
20+
// https://api.flutter.dev/flutter/semantics/SemanticsService-class.html
21+
class AccessibilityPlugin {
22+
public:
23+
explicit AccessibilityPlugin(FlutterWindowsEngine* engine);
24+
25+
// Begin handling accessibility messages on the `binary_messenger`.
26+
static void SetUp(BinaryMessenger* binary_messenger,
27+
AccessibilityPlugin* plugin);
28+
29+
// Announce a message through the assistive technology.
30+
virtual void Announce(const std::string_view message);
31+
32+
private:
33+
// The engine that owns this plugin.
34+
FlutterWindowsEngine* engine_ = nullptr;
35+
36+
FML_DISALLOW_COPY_AND_ASSIGN(AccessibilityPlugin);
37+
};
38+
39+
} // namespace flutter
40+
41+
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_PLUGIN_H_

shell/platform/windows/fixtures/main.dart

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,36 +59,65 @@ void sendAccessibilityAnnouncement() async {
5959
await semanticsChanged;
6060
}
6161

62-
// Serializers for data types are in the framework, so this will be hardcoded.
62+
// Standard message codec magic number identifiers.
63+
// See: https://github.com/flutter/flutter/blob/ee94fe262b63b0761e8e1f889ae52322fef068d2/packages/flutter/lib/src/services/message_codecs.dart#L262
6364
const int valueMap = 13, valueString = 7;
64-
// Corresponds to:
65-
// Map<String, Object> data =
66-
// {"type": "announce", "data": {"message": ""}};
65+
66+
// Corresponds to: {"type": "announce", "data": {"message": "hello"}}
67+
// See: https://github.com/flutter/flutter/blob/b781da9b5822de1461a769c3b245075359f5464d/packages/flutter/lib/src/semantics/semantics_event.dart#L86
68+
final Uint8List data = Uint8List.fromList([
69+
// Map with 2 entries
70+
valueMap, 2,
71+
// Map key: "type"
72+
valueString, 'type'.length, ...'type'.codeUnits,
73+
// Map value: "announce"
74+
valueString, 'announce'.length, ...'announce'.codeUnits,
75+
// Map key: "data"
76+
valueString, 'data'.length, ...'data'.codeUnits,
77+
// Map value: map with 1 entry
78+
valueMap, 1,
79+
// Map key: "message"
80+
valueString, 'message'.length, ...'message'.codeUnits,
81+
// Map value: "hello"
82+
valueString, 'hello'.length, ...'hello'.codeUnits,
83+
]);
84+
final ByteData byteData = data.buffer.asByteData();
85+
86+
ui.PlatformDispatcher.instance.sendPlatformMessage(
87+
'flutter/accessibility',
88+
byteData,
89+
(ByteData? _) => signal(),
90+
);
91+
}
92+
93+
@pragma('vm:entry-point')
94+
void sendAccessibilityTooltipEvent() async {
95+
// Wait until semantics are enabled.
96+
if (!ui.PlatformDispatcher.instance.semanticsEnabled) {
97+
await semanticsChanged;
98+
}
99+
100+
// Standard message codec magic number identifiers.
101+
// See: https://github.com/flutter/flutter/blob/ee94fe262b63b0761e8e1f889ae52322fef068d2/packages/flutter/lib/src/services/message_codecs.dart#L262
102+
const int valueMap = 13, valueString = 7;
103+
104+
// Corresponds to: {"type": "tooltip", "data": {"message": "hello"}}
105+
// See: https://github.com/flutter/flutter/blob/b781da9b5822de1461a769c3b245075359f5464d/packages/flutter/lib/src/semantics/semantics_event.dart#L120
67106
final Uint8List data = Uint8List.fromList([
68-
valueMap, // _valueMap
69-
2, // Size
70-
// key: "type"
71-
valueString,
72-
'type'.length,
73-
...'type'.codeUnits,
74-
// value: "announce"
75-
valueString,
76-
'announce'.length,
77-
...'announce'.codeUnits,
78-
// key: "data"
79-
valueString,
80-
'data'.length,
81-
...'data'.codeUnits,
82-
// value: map
83-
valueMap, // _valueMap
84-
1, // Size
85-
// key: "message"
86-
valueString,
87-
'message'.length,
88-
...'message'.codeUnits,
89-
// value: ""
90-
valueString,
91-
0, // Length of empty string == 0.
107+
// Map with 2 entries
108+
valueMap, 2,
109+
// Map key: "type"
110+
valueString, 'type'.length, ...'type'.codeUnits,
111+
// Map value: "tooltip"
112+
valueString, 'tooltip'.length, ...'tooltip'.codeUnits,
113+
// Map key: "data"
114+
valueString, 'data'.length, ...'data'.codeUnits,
115+
// Map value: map with 1 entry
116+
valueMap, 1,
117+
// Map key: "message"
118+
valueString, 'message'.length, ...'message'.codeUnits,
119+
// Map value: "hello"
120+
valueString, 'hello'.length, ...'hello'.codeUnits,
92121
]);
93122
final ByteData byteData = data.buffer.asByteData();
94123

shell/platform/windows/flutter_windows_engine.cc

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,6 @@ FlutterWindowsEngine::FlutterWindowsEngine(
183183
std::make_unique<BinaryMessengerImpl>(messenger_->ToRef());
184184
message_dispatcher_ =
185185
std::make_unique<IncomingMessageDispatcher>(messenger_->ToRef());
186-
message_dispatcher_->SetMessageCallback(
187-
kAccessibilityChannelName,
188-
[](FlutterDesktopMessengerRef messenger,
189-
const FlutterDesktopMessage* message, void* data) {
190-
FlutterWindowsEngine* engine = static_cast<FlutterWindowsEngine*>(data);
191-
engine->HandleAccessibilityMessage(messenger, message);
192-
},
193-
static_cast<void*>(this));
194186

195187
texture_registrar_ =
196188
std::make_unique<FlutterWindowsTextureRegistrar>(this, gl_);
@@ -219,6 +211,11 @@ FlutterWindowsEngine::FlutterWindowsEngine(
219211
// https://github.com/flutter/flutter/issues/71099
220212
internal_plugin_registrar_ =
221213
std::make_unique<PluginRegistrar>(plugin_registrar_.get());
214+
215+
accessibility_plugin_ = std::make_unique<AccessibilityPlugin>(this);
216+
AccessibilityPlugin::SetUp(messenger_wrapper_.get(),
217+
accessibility_plugin_.get());
218+
222219
cursor_handler_ =
223220
std::make_unique<CursorHandler>(messenger_wrapper_.get(), this);
224221
platform_handler_ =
@@ -765,7 +762,9 @@ void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) {
765762
if (engine_ && semantics_enabled_ != enabled) {
766763
semantics_enabled_ = enabled;
767764
embedder_api_.UpdateSemanticsEnabled(engine_, enabled);
768-
view_->UpdateSemanticsEnabled(enabled);
765+
if (view_) {
766+
view_->UpdateSemanticsEnabled(enabled);
767+
}
769768
}
770769
}
771770

@@ -813,27 +812,6 @@ void FlutterWindowsEngine::SendAccessibilityFeatures() {
813812
engine_, static_cast<FlutterAccessibilityFeature>(flags));
814813
}
815814

816-
void FlutterWindowsEngine::HandleAccessibilityMessage(
817-
FlutterDesktopMessengerRef messenger,
818-
const FlutterDesktopMessage* message) {
819-
const auto& codec = StandardMessageCodec::GetInstance();
820-
auto data = codec.DecodeMessage(message->message, message->message_size);
821-
EncodableMap map = std::get<EncodableMap>(*data);
822-
std::string type = std::get<std::string>(map.at(EncodableValue("type")));
823-
if (type.compare("announce") == 0) {
824-
if (semantics_enabled_) {
825-
EncodableMap data_map =
826-
std::get<EncodableMap>(map.at(EncodableValue("data")));
827-
std::string text =
828-
std::get<std::string>(data_map.at(EncodableValue("message")));
829-
std::wstring wide_text = fml::Utf8ToWideString(text);
830-
view_->AnnounceAlert(wide_text);
831-
}
832-
}
833-
SendPlatformMessageResponse(message->response_handle,
834-
reinterpret_cast<const uint8_t*>(""), 0);
835-
}
836-
837815
void FlutterWindowsEngine::RequestApplicationQuit(HWND hwnd,
838816
WPARAM wparam,
839817
LPARAM lparam,

shell/platform/windows/flutter_windows_engine.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "flutter/shell/platform/common/incoming_message_dispatcher.h"
2323
#include "flutter/shell/platform/embedder/embedder.h"
2424
#include "flutter/shell/platform/windows/accessibility_bridge_windows.h"
25+
#include "flutter/shell/platform/windows/accessibility_plugin.h"
2526
#include "flutter/shell/platform/windows/compositor.h"
2627
#include "flutter/shell/platform/windows/cursor_handler.h"
2728
#include "flutter/shell/platform/windows/egl/manager.h"
@@ -338,9 +339,6 @@ class FlutterWindowsEngine {
338339
// Send the currently enabled accessibility features to the engine.
339340
void SendAccessibilityFeatures();
340341

341-
void HandleAccessibilityMessage(FlutterDesktopMessengerRef messenger,
342-
const FlutterDesktopMessage* message);
343-
344342
// The handle to the embedder.h engine instance.
345343
FLUTTER_API_SYMBOL(FlutterEngine) engine_ = nullptr;
346344

@@ -384,6 +382,9 @@ class FlutterWindowsEngine {
384382
// The plugin registrar managing internal plugins.
385383
std::unique_ptr<PluginRegistrar> internal_plugin_registrar_;
386384

385+
// Handler for accessibility events.
386+
std::unique_ptr<AccessibilityPlugin> accessibility_plugin_;
387+
387388
// Handler for cursor events.
388389
std::unique_ptr<CursorHandler> cursor_handler_;
389390

0 commit comments

Comments
 (0)