[Windows] Remove accessibility root ID assumption (#39994)
[Windows] Remove another accessibility root ID assumption
diff --git a/shell/platform/windows/accessibility_bridge_windows.cc b/shell/platform/windows/accessibility_bridge_windows.cc
index fbdb79b..159ba22 100644
--- a/shell/platform/windows/accessibility_bridge_windows.cc
+++ b/shell/platform/windows/accessibility_bridge_windows.cc
@@ -190,7 +190,12 @@
gfx::NativeViewAccessible
AccessibilityBridgeWindows::GetChildOfAXFragmentRoot() {
- return view_->GetNativeViewAccessible();
+ ui::AXPlatformNodeDelegate* root_delegate = RootDelegate();
+ if (!root_delegate) {
+ return nullptr;
+ }
+
+ return root_delegate->GetNativeViewAccessible();
}
gfx::NativeViewAccessible
diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc
index cb24010..8eddb3f 100644
--- a/shell/platform/windows/flutter_windows_engine.cc
+++ b/shell/platform/windows/flutter_windows_engine.cc
@@ -679,17 +679,12 @@
}
}
-gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeAccessibleFromId(
- AccessibilityNodeId id) {
+gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeViewAccessible() {
if (!accessibility_bridge_) {
return nullptr;
}
- std::shared_ptr<FlutterPlatformNodeDelegate> node_delegate =
- accessibility_bridge_->GetFlutterPlatformNodeDelegateFromID(id).lock();
- if (!node_delegate) {
- return nullptr;
- }
- return node_delegate->GetNativeViewAccessible();
+
+ return accessibility_bridge_->GetChildOfAXFragmentRoot();
}
std::string FlutterWindowsEngine::GetExecutableName() const {
diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h
index f09a8d4..01de0d3 100644
--- a/shell/platform/windows/flutter_windows_engine.h
+++ b/shell/platform/windows/flutter_windows_engine.h
@@ -231,8 +231,9 @@
// Returns true if the high contrast feature is enabled.
bool high_contrast_enabled() const { return high_contrast_enabled_; }
- // Returns the native accessibility node with the given id.
- gfx::NativeViewAccessible GetNativeAccessibleFromId(AccessibilityNodeId id);
+ // Returns the native accessibility root node, or nullptr if one does not
+ // exist.
+ gfx::NativeViewAccessible GetNativeViewAccessible();
// Register a root isolate create callback.
//
diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc
index bf3bda8..84b6cf7 100644
--- a/shell/platform/windows/flutter_windows_view.cc
+++ b/shell/platform/windows/flutter_windows_view.cc
@@ -250,7 +250,7 @@
}
gfx::NativeViewAccessible FlutterWindowsView::GetNativeViewAccessible() {
- return engine_->GetNativeAccessibleFromId(AccessibilityBridge::kRootNodeId);
+ return engine_->GetNativeViewAccessible();
}
void FlutterWindowsView::OnCursorRectUpdated(const Rect& rect) {
diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc
index 0360eb0..c057f79 100644
--- a/shell/platform/windows/flutter_windows_view_unittests.cc
+++ b/shell/platform/windows/flutter_windows_view_unittests.cc
@@ -426,6 +426,126 @@
}
}
+// Flutter used to assume that the accessibility root had ID 0.
+// In a multi-view world, each view has its own accessibility root
+// with a globally unique node ID.
+//
+// node1
+// |
+// node2
+//
+// node1 is a grouping node, node0 is a static text node.
+TEST(FlutterWindowsView, NonZeroSemanticsRoot) {
+ std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
+ EngineModifier modifier(engine.get());
+ modifier.embedder_api().UpdateSemanticsEnabled =
+ [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) {
+ return kSuccess;
+ };
+
+ auto window_binding_handler =
+ std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>();
+ FlutterWindowsView view(std::move(window_binding_handler));
+ view.SetEngine(std::move(engine));
+
+ // Enable semantics to instantiate accessibility bridge.
+ view.OnUpdateSemanticsEnabled(true);
+
+ auto bridge = view.GetEngine()->accessibility_bridge().lock();
+ ASSERT_TRUE(bridge);
+
+ // Add root node.
+ FlutterSemanticsNode node1{sizeof(FlutterSemanticsNode), 1};
+ std::vector<int32_t> node1_children{2};
+ node1.child_count = node1_children.size();
+ node1.children_in_traversal_order = node1_children.data();
+ node1.children_in_hit_test_order = node1_children.data();
+
+ FlutterSemanticsNode node2{sizeof(FlutterSemanticsNode), 2};
+ node2.label = "prefecture";
+ node2.value = "Kyoto";
+
+ bridge->AddFlutterSemanticsNodeUpdate(&node1);
+ bridge->AddFlutterSemanticsNodeUpdate(&node2);
+ bridge->CommitUpdates();
+
+ // Look up the root windows node delegate.
+ auto root_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
+ ASSERT_TRUE(root_delegate);
+ EXPECT_EQ(root_delegate->GetChildCount(), 1);
+
+ // Look up the child node delegate
+ auto child_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
+ ASSERT_TRUE(child_delegate);
+ EXPECT_EQ(child_delegate->GetChildCount(), 0);
+
+ // Ensure a node with ID 0 does not exist.
+ auto fake_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
+ ASSERT_FALSE(fake_delegate);
+
+ // Get the root's native IAccessible object.
+ IAccessible* node1_accessible = root_delegate->GetNativeViewAccessible();
+ ASSERT_TRUE(node1_accessible != nullptr);
+
+ // Property lookups will be made against this node itself.
+ VARIANT varchild{};
+ varchild.vt = VT_I4;
+ varchild.lVal = CHILDID_SELF;
+
+ // Verify node type is a group.
+ VARIANT varrole{};
+ varrole.vt = VT_I4;
+ ASSERT_EQ(node1_accessible->get_accRole(varchild, &varrole), S_OK);
+ EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_GROUPING);
+
+ // Verify child count.
+ long node1_child_count = 0;
+ ASSERT_EQ(node1_accessible->get_accChildCount(&node1_child_count), S_OK);
+ EXPECT_EQ(node1_child_count, 1);
+
+ {
+ // Look up first child of node1 (node0), a static text node.
+ varchild.lVal = 1;
+ IDispatch* node2_dispatch = nullptr;
+ ASSERT_EQ(node1_accessible->get_accChild(varchild, &node2_dispatch), S_OK);
+ ASSERT_TRUE(node2_dispatch != nullptr);
+ IAccessible* node2_accessible = nullptr;
+ ASSERT_EQ(node2_dispatch->QueryInterface(
+ IID_IAccessible, reinterpret_cast<void**>(&node2_accessible)),
+ S_OK);
+ ASSERT_TRUE(node2_accessible != nullptr);
+
+ // Verify node name matches our label.
+ varchild.lVal = CHILDID_SELF;
+ BSTR bname = nullptr;
+ ASSERT_EQ(node2_accessible->get_accName(varchild, &bname), S_OK);
+ std::string name(_com_util::ConvertBSTRToString(bname));
+ EXPECT_EQ(name, "prefecture");
+
+ // Verify node value matches.
+ BSTR bvalue = nullptr;
+ ASSERT_EQ(node2_accessible->get_accValue(varchild, &bvalue), S_OK);
+ std::string value(_com_util::ConvertBSTRToString(bvalue));
+ EXPECT_EQ(value, "Kyoto");
+
+ // Verify node type is static text.
+ VARIANT varrole{};
+ varrole.vt = VT_I4;
+ ASSERT_EQ(node2_accessible->get_accRole(varchild, &varrole), S_OK);
+ EXPECT_EQ(varrole.lVal, ROLE_SYSTEM_STATICTEXT);
+
+ // Verify the parent node is the root.
+ IDispatch* parent_dispatch;
+ node2_accessible->get_accParent(&parent_dispatch);
+ IAccessible* parent_accessible;
+ ASSERT_EQ(
+ parent_dispatch->QueryInterface(
+ IID_IAccessible, reinterpret_cast<void**>(&parent_accessible)),
+ S_OK);
+ EXPECT_EQ(parent_accessible, node1_accessible);
+ }
+}
+
// Verify the native IAccessible accHitTest method returns the correct
// IAccessible COM object for the given coordinates.
//