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

Commit dec8b52

Browse files
Preliminary implementation of UIA for A11y on Windows (#37754)
* Initial * Comment TODOs * Shim windowsx * Comment testing * UIA works at most basic * Get Native target in win delegate * Rework events * FlutterWindowsView unit tests for UIA * Enable UIA unit tests * Conditional UIA implementation * Text selection MSAA event * License * PR * Before change to AccessibilityBridgeWindows * AccessibilityBridgeWindows in FlutterWindowsEngine * Use AccessibilityBridgeWindows for AXFragmentRootDelegateWin * Format * Remove unneeded windowsx_shim imports * PR Comment
1 parent bd8bcf9 commit dec8b52

28 files changed

+348
-99
lines changed

ci/licenses_golden/licenses_flutter

+1
Original file line numberDiff line numberDiff line change
@@ -3277,6 +3277,7 @@ FILE: ../../../flutter/shell/platform/windows/windows_proc_table.cc
32773277
FILE: ../../../flutter/shell/platform/windows/windows_proc_table.h
32783278
FILE: ../../../flutter/shell/platform/windows/windows_registry.cc
32793279
FILE: ../../../flutter/shell/platform/windows/windows_registry.h
3280+
FILE: ../../../flutter/shell/platform/windows/windowsx_shim.h
32803281
FILE: ../../../flutter/shell/profiling/sampling_profiler.cc
32813282
FILE: ../../../flutter/shell/profiling/sampling_profiler.h
32823283
FILE: ../../../flutter/shell/profiling/sampling_profiler_unittest.cc

shell/platform/windows/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ source_set("flutter_windows_source") {
111111
"windows_proc_table.h",
112112
"windows_registry.cc",
113113
"windows_registry.h",
114+
"windowsx_shim.h",
114115
]
115116

116117
libs = [

shell/platform/windows/accessibility_bridge_windows.cc

+44-17
Original file line numberDiff line numberDiff line change
@@ -31,53 +31,67 @@ void AccessibilityBridgeWindows::OnAccessibilityEvent(
3131

3232
switch (event_type) {
3333
case ui::AXEventGenerator::Event::ALERT:
34-
DispatchWinAccessibilityEvent(win_delegate, EVENT_SYSTEM_ALERT);
34+
DispatchWinAccessibilityEvent(win_delegate, ax::mojom::Event::kAlert);
3535
break;
3636
case ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED:
37-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_VALUECHANGE);
37+
DispatchWinAccessibilityEvent(win_delegate,
38+
ax::mojom::Event::kValueChanged);
3839
break;
3940
case ui::AXEventGenerator::Event::CHILDREN_CHANGED:
40-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_REORDER);
41+
DispatchWinAccessibilityEvent(win_delegate,
42+
ax::mojom::Event::kChildrenChanged);
43+
break;
44+
case ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED:
45+
DispatchWinAccessibilityEvent(
46+
win_delegate, ax::mojom::Event::kDocumentSelectionChanged);
4147
break;
4248
case ui::AXEventGenerator::Event::FOCUS_CHANGED:
43-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_FOCUS);
49+
DispatchWinAccessibilityEvent(win_delegate, ax::mojom::Event::kFocus);
4450
SetFocus(win_delegate);
4551
break;
4652
case ui::AXEventGenerator::Event::IGNORED_CHANGED:
4753
if (ax_node->IsIgnored()) {
48-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_HIDE);
54+
DispatchWinAccessibilityEvent(win_delegate, ax::mojom::Event::kHide);
4955
}
5056
break;
5157
case ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
52-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_NAMECHANGE);
58+
DispatchWinAccessibilityEvent(win_delegate,
59+
ax::mojom::Event::kTextChanged);
5360
break;
5461
case ui::AXEventGenerator::Event::LIVE_REGION_CHANGED:
5562
DispatchWinAccessibilityEvent(win_delegate,
56-
EVENT_OBJECT_LIVEREGIONCHANGED);
63+
ax::mojom::Event::kLiveRegionChanged);
5764
break;
5865
case ui::AXEventGenerator::Event::NAME_CHANGED:
59-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_NAMECHANGE);
66+
DispatchWinAccessibilityEvent(win_delegate,
67+
ax::mojom::Event::kTextChanged);
6068
break;
6169
case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
62-
DispatchWinAccessibilityEvent(win_delegate, EVENT_SYSTEM_SCROLLINGEND);
70+
DispatchWinAccessibilityEvent(win_delegate,
71+
ax::mojom::Event::kScrollPositionChanged);
6372
break;
6473
case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
65-
DispatchWinAccessibilityEvent(win_delegate, EVENT_SYSTEM_SCROLLINGEND);
74+
DispatchWinAccessibilityEvent(win_delegate,
75+
ax::mojom::Event::kScrollPositionChanged);
6676
break;
6777
case ui::AXEventGenerator::Event::SELECTED_CHANGED:
68-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_VALUECHANGE);
78+
DispatchWinAccessibilityEvent(win_delegate,
79+
ax::mojom::Event::kValueChanged);
6980
break;
7081
case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
71-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_SELECTIONWITHIN);
82+
DispatchWinAccessibilityEvent(win_delegate,
83+
ax::mojom::Event::kSelectedChildrenChanged);
7284
break;
7385
case ui::AXEventGenerator::Event::SUBTREE_CREATED:
74-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_SHOW);
86+
DispatchWinAccessibilityEvent(win_delegate, ax::mojom::Event::kShow);
7587
break;
7688
case ui::AXEventGenerator::Event::VALUE_CHANGED:
77-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_VALUECHANGE);
89+
DispatchWinAccessibilityEvent(win_delegate,
90+
ax::mojom::Event::kValueChanged);
7891
break;
7992
case ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED:
80-
DispatchWinAccessibilityEvent(win_delegate, EVENT_OBJECT_STATECHANGE);
93+
DispatchWinAccessibilityEvent(win_delegate,
94+
ax::mojom::Event::kStateChanged);
8195
break;
8296
case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED:
8397
case ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
@@ -90,7 +104,6 @@ void AccessibilityBridgeWindows::OnAccessibilityEvent(
90104
case ui::AXEventGenerator::Event::CONTROLS_CHANGED:
91105
case ui::AXEventGenerator::Event::DESCRIBED_BY_CHANGED:
92106
case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED:
93-
case ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED:
94107
case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
95108
case ui::AXEventGenerator::Event::DROPEFFECT_CHANGED:
96109
case ui::AXEventGenerator::Event::ENABLED_CHANGED:
@@ -151,7 +164,7 @@ AccessibilityBridgeWindows::CreateFlutterPlatformNodeDelegate() {
151164

152165
void AccessibilityBridgeWindows::DispatchWinAccessibilityEvent(
153166
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
154-
DWORD event_type) {
167+
ax::mojom::Event event_type) {
155168
node_delegate->DispatchWinAccessibilityEvent(event_type);
156169
}
157170

@@ -160,4 +173,18 @@ void AccessibilityBridgeWindows::SetFocus(
160173
node_delegate->SetFocus();
161174
}
162175

176+
gfx::NativeViewAccessible
177+
AccessibilityBridgeWindows::GetChildOfAXFragmentRoot() {
178+
return view_->GetNativeViewAccessible();
179+
}
180+
181+
gfx::NativeViewAccessible
182+
AccessibilityBridgeWindows::GetParentOfAXFragmentRoot() {
183+
return nullptr;
184+
}
185+
186+
bool AccessibilityBridgeWindows::IsAXFragmentRootAControlElement() {
187+
return true;
188+
}
189+
163190
} // namespace flutter

shell/platform/windows/accessibility_bridge_windows.h

+14-5
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66
#define FLUTTER_SHELL_PLATFORM_WINDOWS_ACCESSIBILITY_BRIDGE_WINDOWS_H_
77

88
#include "flutter/shell/platform/common/accessibility_bridge.h"
9-
10-
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
11-
#include "flutter/shell/platform/windows/flutter_windows_view.h"
9+
#include "flutter/third_party/accessibility/ax/platform/ax_fragment_root_delegate_win.h"
1210

1311
namespace flutter {
1412

1513
class FlutterWindowsEngine;
14+
class FlutterWindowsView;
1615
class FlutterPlatformNodeDelegateWindows;
1716

1817
// The Win32 implementation of AccessibilityBridge.
@@ -24,7 +23,8 @@ class FlutterPlatformNodeDelegateWindows;
2423
///
2524
/// AccessibilityBridgeWindows must be created as a shared_ptr, since some
2625
/// methods acquires its weak_ptr.
27-
class AccessibilityBridgeWindows : public AccessibilityBridge {
26+
class AccessibilityBridgeWindows : public AccessibilityBridge,
27+
public ui::AXFragmentRootDelegateWin {
2828
public:
2929
AccessibilityBridgeWindows(FlutterWindowsEngine* engine,
3030
FlutterWindowsView* view);
@@ -41,7 +41,7 @@ class AccessibilityBridgeWindows : public AccessibilityBridge {
4141
// This is a virtual method for the convenience of unit tests.
4242
virtual void DispatchWinAccessibilityEvent(
4343
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
44-
DWORD event_type);
44+
ax::mojom::Event event_type);
4545

4646
// Sets the accessibility focus to the accessibility node associated with the
4747
// specified semantics node.
@@ -50,6 +50,15 @@ class AccessibilityBridgeWindows : public AccessibilityBridge {
5050
virtual void SetFocus(
5151
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate);
5252

53+
// |AXFragmentRootDelegateWin|
54+
gfx::NativeViewAccessible GetChildOfAXFragmentRoot() override;
55+
56+
// |AXFragmentRootDelegateWin|
57+
gfx::NativeViewAccessible GetParentOfAXFragmentRoot() override;
58+
59+
// |AXFragmentRootDelegateWin|
60+
bool IsAXFragmentRootAControlElement() override;
61+
5362
protected:
5463
// |AccessibilityBridge|
5564
void OnAccessibilityEvent(

shell/platform/windows/accessibility_bridge_windows_unittests.cc

+19-18
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ namespace {
2929
// A structure representing a Win32 MSAA event targeting a specified node.
3030
struct MsaaEvent {
3131
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate;
32-
DWORD event_type;
32+
ax::mojom::Event event_type;
3333
};
3434

3535
// Accessibility bridge delegate that captures events dispatched to the OS.
@@ -43,7 +43,7 @@ class AccessibilityBridgeWindowsSpy : public AccessibilityBridgeWindows {
4343

4444
void DispatchWinAccessibilityEvent(
4545
std::shared_ptr<FlutterPlatformNodeDelegateWindows> node_delegate,
46-
DWORD event_type) override {
46+
ax::mojom::Event event_type) override {
4747
dispatched_events_.push_back({node_delegate, event_type});
4848
}
4949

@@ -76,7 +76,7 @@ class FlutterWindowsEngineSpy : public FlutterWindowsEngine {
7676
: FlutterWindowsEngine(project) {}
7777

7878
protected:
79-
virtual std::shared_ptr<AccessibilityBridge> CreateAccessibilityBridge(
79+
virtual std::shared_ptr<AccessibilityBridgeWindows> CreateAccessibilityBridge(
8080
FlutterWindowsEngine* engine,
8181
FlutterWindowsView* view) override {
8282
return std::make_shared<AccessibilityBridgeWindowsSpy>(engine, view);
@@ -169,7 +169,7 @@ std::shared_ptr<AccessibilityBridgeWindowsSpy> GetAccessibilityBridgeSpy(
169169

170170
void ExpectWinEventFromAXEvent(int32_t node_id,
171171
ui::AXEventGenerator::Event ax_event,
172-
DWORD expected_event) {
172+
ax::mojom::Event expected_event) {
173173
auto window_binding_handler =
174174
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
175175
FlutterWindowsView view(std::move(window_binding_handler));
@@ -246,12 +246,12 @@ TEST(AccessibilityBridgeWindows, DispatchAccessibilityAction) {
246246

247247
TEST(AccessibilityBridgeWindows, OnAccessibilityEventAlert) {
248248
ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::ALERT,
249-
EVENT_SYSTEM_ALERT);
249+
ax::mojom::Event::kAlert);
250250
}
251251

252252
TEST(AccessibilityBridgeWindows, OnAccessibilityEventChildrenChanged) {
253253
ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::CHILDREN_CHANGED,
254-
EVENT_OBJECT_REORDER);
254+
ax::mojom::Event::kChildrenChanged);
255255
}
256256

257257
TEST(AccessibilityBridgeWindows, OnAccessibilityEventFocusChanged) {
@@ -270,7 +270,8 @@ TEST(AccessibilityBridgeWindows, OnAccessibilityEventFocusChanged) {
270270
ax::mojom::EventFrom::kNone,
271271
{}}});
272272
ASSERT_EQ(bridge->dispatched_events().size(), 1);
273-
EXPECT_EQ(bridge->dispatched_events()[0].event_type, EVENT_OBJECT_FOCUS);
273+
EXPECT_EQ(bridge->dispatched_events()[0].event_type,
274+
ax::mojom::Event::kFocus);
274275

275276
ASSERT_EQ(bridge->focused_nodes().size(), 1);
276277
EXPECT_EQ(bridge->focused_nodes()[0], 1);
@@ -279,62 +280,62 @@ TEST(AccessibilityBridgeWindows, OnAccessibilityEventFocusChanged) {
279280
TEST(AccessibilityBridgeWindows, OnAccessibilityEventIgnoredChanged) {
280281
// Static test nodes with no text, hint, or scrollability are ignored.
281282
ExpectWinEventFromAXEvent(4, ui::AXEventGenerator::Event::IGNORED_CHANGED,
282-
EVENT_OBJECT_HIDE);
283+
ax::mojom::Event::kHide);
283284
}
284285

285286
TEST(AccessibilityBridgeWindows, OnAccessibilityImageAnnotationChanged) {
286287
ExpectWinEventFromAXEvent(
287288
1, ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED,
288-
EVENT_OBJECT_NAMECHANGE);
289+
ax::mojom::Event::kTextChanged);
289290
}
290291

291292
TEST(AccessibilityBridgeWindows, OnAccessibilityLiveRegionChanged) {
292293
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
293-
EVENT_OBJECT_LIVEREGIONCHANGED);
294+
ax::mojom::Event::kLiveRegionChanged);
294295
}
295296

296297
TEST(AccessibilityBridgeWindows, OnAccessibilityNameChanged) {
297298
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::NAME_CHANGED,
298-
EVENT_OBJECT_NAMECHANGE);
299+
ax::mojom::Event::kTextChanged);
299300
}
300301

301302
TEST(AccessibilityBridgeWindows, OnAccessibilityHScrollPosChanged) {
302303
ExpectWinEventFromAXEvent(
303304
1, ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED,
304-
EVENT_SYSTEM_SCROLLINGEND);
305+
ax::mojom::Event::kScrollPositionChanged);
305306
}
306307

307308
TEST(AccessibilityBridgeWindows, OnAccessibilityVScrollPosChanged) {
308309
ExpectWinEventFromAXEvent(
309310
1, ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED,
310-
EVENT_SYSTEM_SCROLLINGEND);
311+
ax::mojom::Event::kScrollPositionChanged);
311312
}
312313

313314
TEST(AccessibilityBridgeWindows, OnAccessibilitySelectedChanged) {
314315
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::SELECTED_CHANGED,
315-
EVENT_OBJECT_VALUECHANGE);
316+
ax::mojom::Event::kValueChanged);
316317
}
317318

318319
TEST(AccessibilityBridgeWindows, OnAccessibilitySelectedChildrenChanged) {
319320
ExpectWinEventFromAXEvent(
320321
2, ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED,
321-
EVENT_OBJECT_SELECTIONWITHIN);
322+
ax::mojom::Event::kSelectedChildrenChanged);
322323
}
323324

324325
TEST(AccessibilityBridgeWindows, OnAccessibilitySubtreeCreated) {
325326
ExpectWinEventFromAXEvent(0, ui::AXEventGenerator::Event::SUBTREE_CREATED,
326-
EVENT_OBJECT_SHOW);
327+
ax::mojom::Event::kShow);
327328
}
328329

329330
TEST(AccessibilityBridgeWindows, OnAccessibilityValueChanged) {
330331
ExpectWinEventFromAXEvent(1, ui::AXEventGenerator::Event::VALUE_CHANGED,
331-
EVENT_OBJECT_VALUECHANGE);
332+
ax::mojom::Event::kValueChanged);
332333
}
333334

334335
TEST(AccessibilityBridgeWindows, OnAccessibilityStateChanged) {
335336
ExpectWinEventFromAXEvent(
336337
1, ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED,
337-
EVENT_OBJECT_STATECHANGE);
338+
ax::mojom::Event::kStateChanged);
338339
}
339340

340341
} // namespace testing

shell/platform/windows/flutter_platform_node_delegate_windows.cc

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "flutter/shell/platform/windows/flutter_windows_view.h"
1111
#include "flutter/third_party/accessibility/ax/ax_clipping_behavior.h"
1212
#include "flutter/third_party/accessibility/ax/ax_coordinate_system.h"
13+
#include "flutter/third_party/accessibility/ax/platform/ax_fragment_root_win.h"
1314

1415
namespace flutter {
1516

@@ -90,14 +91,8 @@ gfx::Rect FlutterPlatformNodeDelegateWindows::GetBoundsRect(
9091
}
9192

9293
void FlutterPlatformNodeDelegateWindows::DispatchWinAccessibilityEvent(
93-
DWORD event_type) {
94-
HWND hwnd = view_->GetPlatformWindow();
95-
if (!hwnd) {
96-
return;
97-
}
98-
assert(ax_platform_node_);
99-
::NotifyWinEvent(event_type, hwnd, OBJID_CLIENT,
100-
-ax_platform_node_->GetUniqueId());
94+
ax::mojom::Event event_type) {
95+
ax_platform_node_->NotifyAccessibilityEvent(event_type);
10196
}
10297

10398
void FlutterPlatformNodeDelegateWindows::SetFocus() {
@@ -107,4 +102,9 @@ void FlutterPlatformNodeDelegateWindows::SetFocus() {
107102
GetNativeViewAccessible()->accSelect(SELFLAG_TAKEFOCUS, varchild);
108103
}
109104

105+
gfx::AcceleratedWidget
106+
FlutterPlatformNodeDelegateWindows::GetTargetForNativeAccessibilityEvent() {
107+
return view_->GetPlatformWindow();
108+
}
109+
110110
} // namespace flutter

shell/platform/windows/flutter_platform_node_delegate_windows.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,15 @@ class FlutterPlatformNodeDelegateWindows : public FlutterPlatformNodeDelegate {
4242
// Dispatches a Windows accessibility event of the specified type, generated
4343
// by the accessibility node associated with this object. This is a
4444
// convenience wrapper around |NotifyWinEvent|.
45-
virtual void DispatchWinAccessibilityEvent(DWORD event_type);
45+
virtual void DispatchWinAccessibilityEvent(ax::mojom::Event event_type);
4646

4747
// Sets the accessibility focus to the accessibility node associated with
4848
// this object.
4949
void SetFocus();
5050

51+
// | AXPlatformNodeDelegate |
52+
gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override;
53+
5154
private:
5255
ui::AXPlatformNode* ax_platform_node_;
5356
std::weak_ptr<AccessibilityBridge> bridge_;

shell/platform/windows/flutter_window.cc

+4
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,8 @@ AccessibilityRootNode* FlutterWindow::GetAccessibilityRootNode() {
303303
return accessibility_root_;
304304
}
305305

306+
ui::AXFragmentRootDelegateWin* FlutterWindow::GetAxFragmentRootDelegate() {
307+
return binding_handler_delegate_->GetAxFragmentRootDelegate();
308+
}
309+
306310
} // namespace flutter

0 commit comments

Comments
 (0)