From cb01defdcf93cb62e5f51daaefadf24bc8ebbf2d Mon Sep 17 00:00:00 2001 From: Hannah Jin Date: Tue, 10 Jun 2025 11:11:07 -0700 Subject: [PATCH] [a11y] Semantics flag refactor step 2: embedder part (#167738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue: https://github.com/flutter/flutter/issues/166101, overall goal is to update semantics flag to be a struct/class to support more than 32 flags. step 1: https://github.com/flutter/flutter/pull/167421 updated semantics_node.h and dart:ui step 2(this PR): Update Embedder part to use a struct instead of a int bit mask. step 3: https://github.com/flutter/flutter/pull/167771 Update Framework use the SemanticsFlags class instead of bitmask step 4 https://github.com/flutter/flutter/pull/168852 Update web engine to use the new class and update SemanticsUpdateBuilder.updateNode to pass the new class instead of bitmask ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Chris Bracken Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com> --- .../platform/common/accessibility_bridge.cc | 93 +++++++--------- .../platform/common/accessibility_bridge.h | 2 +- .../common/accessibility_bridge_unittests.cc | 60 +++++++---- ...lutter_platform_node_delegate_unittests.cc | 25 ++++- .../Source/AccessibilityBridgeMacTest.mm | 14 ++- .../framework/Source/FlutterEngineTest.mm | 12 ++- .../FlutterPlatformNodeDelegateMacTest.mm | 37 ++++--- .../shell/platform/embedder/embedder.h | 101 +++++++++++++++++- .../embedder/embedder_semantics_update.cc | 45 ++++++++ .../embedder/embedder_semantics_update.h | 1 + .../platform/linux/fl_view_accessible.cc | 4 +- .../accessibility_bridge_windows_unittests.cc | 6 ++ .../windows/flutter_windows_view_unittests.cc | 76 +++++++++---- 13 files changed, 354 insertions(+), 122 deletions(-) diff --git a/engine/src/flutter/shell/platform/common/accessibility_bridge.cc b/engine/src/flutter/shell/platform/common/accessibility_bridge.cc index b471da88178..6630da14e8d 100644 --- a/engine/src/flutter/shell/platform/common/accessibility_bridge.cc +++ b/engine/src/flutter/shell/platform/common/accessibility_bridge.cc @@ -311,43 +311,42 @@ void AccessibilityBridge::ConvertFlutterUpdate(const SemanticsNode& node, void AccessibilityBridge::SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { - FlutterSemanticsFlag flags = node.flags; - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsButton) { + const FlutterSemanticsFlags* flags = node.flags; + FML_DCHECK(flags) << "SemanticsNode::flags must not be null"; + if (flags->is_button) { node_data.role = ax::mojom::Role::kButton; return; } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && - !(flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly)) { + if (flags->is_text_field && !flags->is_read_only) { node_data.role = ax::mojom::Role::kTextField; return; } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsHeader) { + if (flags->is_header) { node_data.role = ax::mojom::Role::kHeader; return; } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsImage) { + if (flags->is_image) { node_data.role = ax::mojom::Role::kImage; return; } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsLink) { + if (flags->is_link) { node_data.role = ax::mojom::Role::kLink; return; } - if (flags & kFlutterSemanticsFlagIsInMutuallyExclusiveGroup && - flags & kFlutterSemanticsFlagHasCheckedState) { + if (flags->is_in_mutually_exclusive_group && flags->has_checked_state) { node_data.role = ax::mojom::Role::kRadioButton; return; } - if (flags & kFlutterSemanticsFlagHasCheckedState) { + if (flags->has_checked_state) { node_data.role = ax::mojom::Role::kCheckBox; return; } - if (flags & kFlutterSemanticsFlagHasToggledState) { + if (flags->has_toggled_state) { node_data.role = ax::mojom::Role::kSwitch; return; } - if (flags & kFlutterSemanticsFlagIsSlider) { + if (flags->is_slider) { node_data.role = ax::mojom::Role::kSlider; return; } @@ -362,17 +361,14 @@ void AccessibilityBridge::SetRoleFromFlutterUpdate(ui::AXNodeData& node_data, void AccessibilityBridge::SetStateFromFlutterUpdate(ui::AXNodeData& node_data, const SemanticsNode& node) { - FlutterSemanticsFlag flags = node.flags; + const FlutterSemanticsFlags* flags = node.flags; FlutterSemanticsAction actions = node.actions; - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState && - flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsExpanded) { + if (flags->has_expanded_state && flags->is_expanded) { node_data.AddState(ax::mojom::State::kExpanded); - } else if (flags & - FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState) { + } else if (flags->has_expanded_state) { node_data.AddState(ax::mojom::State::kCollapsed); } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && - (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0) { + if (flags->is_text_field && !flags->is_read_only) { node_data.AddState(ax::mojom::State::kEditable); } if (node_data.role == ax::mojom::Role::kStaticText && @@ -435,7 +431,7 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( ui::AXNodeData& node_data, const SemanticsNode& node) { FlutterSemanticsAction actions = node.actions; - FlutterSemanticsFlag flags = node.flags; + const FlutterSemanticsFlags* flags = node.flags; node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, actions & kHasScrollingAction); node_data.AddBoolAttribute( @@ -444,13 +440,10 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( // TODO(chunhtai): figure out if there is a node that does not clip overflow. node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren, !node.children_in_traversal_order.empty()); - node_data.AddBoolAttribute( - ax::mojom::BoolAttribute::kSelected, - flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsSelected); - node_data.AddBoolAttribute( - ax::mojom::BoolAttribute::kEditableRoot, - flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && - (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0); + node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, + flags->is_selected); + node_data.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, + flags->is_text_field && !flags->is_read_only); // Mark nodes as line breaking so that screen readers don't // merge all consecutive objects into one. // TODO(schectman): When should a node have this attribute set? @@ -462,15 +455,13 @@ void AccessibilityBridge::SetBooleanAttributesFromFlutterUpdate( void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( ui::AXNodeData& node_data, const SemanticsNode& node) { - FlutterSemanticsFlag flags = node.flags; + const FlutterSemanticsFlags* flags = node.flags; node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, node.text_direction); int sel_start = node.text_selection_base; int sel_end = node.text_selection_extent; - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && - (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly) == 0 && - !node.value.empty()) { + if (flags->is_text_field && !flags->is_read_only && !node.value.empty()) { // By default the text field selection should be at the end. sel_start = sel_start == -1 ? node.value.length() : sel_start; sel_end = sel_end == -1 ? node.value.length() : sel_end; @@ -483,18 +474,15 @@ void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( node_data.AddIntAttribute( ax::mojom::IntAttribute::kCheckedState, static_cast( - flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsCheckStateMixed - ? ax::mojom::CheckedState::kMixed - : flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked - ? ax::mojom::CheckedState::kTrue - : ax::mojom::CheckedState::kFalse)); + flags->is_check_state_mixed ? ax::mojom::CheckedState::kMixed + : flags->is_checked ? ax::mojom::CheckedState::kTrue + : ax::mojom::CheckedState::kFalse)); } else if (node_data.role == ax::mojom::Role::kSwitch) { node_data.AddIntAttribute( ax::mojom::IntAttribute::kCheckedState, - static_cast( - flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled - ? ax::mojom::CheckedState::kTrue - : ax::mojom::CheckedState::kFalse)); + static_cast(flags->is_toggled + ? ax::mojom::CheckedState::kTrue + : ax::mojom::CheckedState::kFalse)); } } @@ -548,13 +536,12 @@ void AccessibilityBridge::SetTooltipFromFlutterUpdate( void AccessibilityBridge::SetTreeData(const SemanticsNode& node, ui::AXTreeUpdate& tree_update) { - FlutterSemanticsFlag flags = node.flags; - // Set selection of the focused node if: - // 1. this text field has a valid selection - // 2. this text field doesn't have a valid selection but had selection stored - // in the tree. - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField && - flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused) { + const FlutterSemanticsFlags* flags = node.flags; + // Set selection of the focused node if: + // 1. this text field has a valid selection + // 2. this text field doesn't have a valid selection but had selection stored + // in the tree. + if (flags->is_text_field && flags->is_focused) { if (node.text_selection_base != -1) { tree_update.tree_data.sel_anchor_object_id = node.id; tree_update.tree_data.sel_anchor_offset = node.text_selection_base; @@ -570,13 +557,10 @@ void AccessibilityBridge::SetTreeData(const SemanticsNode& node, } } - if (flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused && - tree_update.tree_data.focus_id != node.id) { + if (flags->is_focused && tree_update.tree_data.focus_id != node.id) { tree_update.tree_data.focus_id = node.id; tree_update.has_tree_data = true; - } else if ((flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused) == - 0 && - tree_update.tree_data.focus_id == node.id) { + } else if (!flags->is_focused && tree_update.tree_data.focus_id == node.id) { tree_update.tree_data.focus_id = ui::AXNode::kInvalidAXID; tree_update.has_tree_data = true; } @@ -587,7 +571,10 @@ AccessibilityBridge::FromFlutterSemanticsNode( const FlutterSemanticsNode2& flutter_node) { SemanticsNode result; result.id = flutter_node.id; - result.flags = flutter_node.flags; + FML_DCHECK(flutter_node.flags2) + << "FlutterSemanticsNode2::flags2 must not be null"; + + result.flags = flutter_node.flags2; result.actions = flutter_node.actions; result.text_selection_base = flutter_node.text_selection_base; result.text_selection_extent = flutter_node.text_selection_extent; diff --git a/engine/src/flutter/shell/platform/common/accessibility_bridge.h b/engine/src/flutter/shell/platform/common/accessibility_bridge.h index 6b6f5758379..6a36e47675b 100644 --- a/engine/src/flutter/shell/platform/common/accessibility_bridge.h +++ b/engine/src/flutter/shell/platform/common/accessibility_bridge.h @@ -161,7 +161,7 @@ class AccessibilityBridge // See FlutterSemanticsNode in embedder.h typedef struct { int32_t id; - FlutterSemanticsFlag flags; + FlutterSemanticsFlags* flags; FlutterSemanticsAction actions; int32_t text_selection_base; int32_t text_selection_extent; diff --git a/engine/src/flutter/shell/platform/common/accessibility_bridge_unittests.cc b/engine/src/flutter/shell/platform/common/accessibility_bridge_unittests.cc index 796670e3ab9..56936539afd 100644 --- a/engine/src/flutter/shell/platform/common/accessibility_bridge_unittests.cc +++ b/engine/src/flutter/shell/platform/common/accessibility_bridge_unittests.cc @@ -15,13 +15,15 @@ namespace testing { using ::testing::Contains; +FlutterSemanticsFlags kEmptyFlags = FlutterSemanticsFlags{}; + FlutterSemanticsNode2 CreateSemanticsNode( int32_t id, const char* label, const std::vector* children = nullptr) { return { .id = id, - .flags = static_cast(0), + .flags__deprecated__ = static_cast(0), .actions = static_cast(0), .text_selection_base = -1, .text_selection_extent = -1, @@ -34,6 +36,7 @@ FlutterSemanticsNode2 CreateSemanticsNode( .children_in_traversal_order = children ? children->data() : nullptr, .custom_accessibility_actions_count = 0, .tooltip = "", + .flags2 = &kEmptyFlags, }; } @@ -209,9 +212,12 @@ TEST(AccessibilityBridgeTest, CanHandleSelectionChangeCorrectly) { std::shared_ptr bridge = std::make_shared(); FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused); + auto flags = FlutterSemanticsFlags{ + .is_text_field = true, + .is_focused = true, + }; + root.flags2 = &flags; + bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -241,9 +247,12 @@ TEST(AccessibilityBridgeTest, DoesNotAssignEditableRootToSelectableText) { std::shared_ptr bridge = std::make_shared(); FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly); + auto flags = FlutterSemanticsFlags{ + .is_text_field = true, + .is_read_only = true, + }; + root.flags2 = &flags; + bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -257,10 +266,14 @@ TEST(AccessibilityBridgeTest, SwitchHasSwitchRole) { std::shared_ptr bridge = std::make_shared(); FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState | - FlutterSemanticsFlag::kFlutterSemanticsFlagHasEnabledState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsEnabled); + auto flags = FlutterSemanticsFlags{ + + .has_enabled_state = true, + .is_enabled = true, + .has_toggled_state = true, + }; + + root.flags2 = &flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -272,11 +285,16 @@ TEST(AccessibilityBridgeTest, SliderHasSliderRole) { std::shared_ptr bridge = std::make_shared(); FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagIsSlider | - FlutterSemanticsFlag::kFlutterSemanticsFlagHasEnabledState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsEnabled | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocusable); + auto flags = FlutterSemanticsFlags{ + + .has_enabled_state = true, + .is_enabled = true, + .is_focusable = true, + .is_slider = true, + }; + + root.flags2 = &flags; + bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -293,9 +311,13 @@ TEST(AccessibilityBridgeTest, CanSetCheckboxChecked) { std::shared_ptr bridge = std::make_shared(); FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked); + auto flags = FlutterSemanticsFlags{ + + .has_checked_state = true, + .is_checked = true, + }; + + root.flags2 = &flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); diff --git a/engine/src/flutter/shell/platform/common/flutter_platform_node_delegate_unittests.cc b/engine/src/flutter/shell/platform/common/flutter_platform_node_delegate_unittests.cc index 00e3240fa4f..bd980c1cce7 100644 --- a/engine/src/flutter/shell/platform/common/flutter_platform_node_delegate_unittests.cc +++ b/engine/src/flutter/shell/platform/common/flutter_platform_node_delegate_unittests.cc @@ -19,14 +19,17 @@ TEST(FlutterPlatformNodeDelegateTest, NodeDelegateHasUniqueId) { // Add node 0: root. FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0}; std::vector node0_children{1}; + FlutterSemanticsFlags emptyFlags = FlutterSemanticsFlags{}; node0.child_count = node0_children.size(); node0.children_in_traversal_order = node0_children.data(); node0.children_in_hit_test_order = node0_children.data(); + node0.flags2 = &emptyFlags; // Add node 1: text child of node 0. FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1}; node1.label = "prefecture"; node1.value = "Kyoto"; + node1.flags2 = &emptyFlags; bridge->AddFlutterSemanticsNodeUpdate(node0); bridge->AddFlutterSemanticsNodeUpdate(node1); @@ -42,7 +45,9 @@ TEST(FlutterPlatformNodeDelegateTest, canPerfomActions) { std::make_shared(); FlutterSemanticsNode2 root; root.id = 0; - root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; + flags.is_text_field = true; + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -87,7 +92,9 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { std::make_shared(); FlutterSemanticsNode2 root; root.id = 0; - root.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; + flags.is_text_field = true; + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -110,6 +117,7 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) { TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { std::shared_ptr bridge = std::make_shared(); + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; FlutterSemanticsNode2 root; root.id = 0; root.label = "root"; @@ -119,6 +127,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { root.decreased_value = ""; root.tooltip = ""; root.child_count = 1; + root.flags2 = &flags; int32_t children[] = {1}; root.children_in_traversal_order = children; root.custom_accessibility_actions_count = 0; @@ -135,6 +144,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { child1.decreased_value = ""; child1.tooltip = ""; child1.child_count = 0; + child1.flags2 = &flags; child1.custom_accessibility_actions_count = 0; child1.rect = {0, 0, 50, 50}; // LTRB child1.transform = {0.5, 0, 0, 0, 0.5, 0, 0, 0, 1}; @@ -156,6 +166,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) { TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { std::shared_ptr bridge = std::make_shared(); + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; FlutterSemanticsNode2 root; root.id = 0; root.label = "root"; @@ -165,6 +176,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { root.decreased_value = ""; root.tooltip = ""; root.child_count = 1; + root.flags2 = &flags; int32_t children[] = {1}; root.children_in_traversal_order = children; root.custom_accessibility_actions_count = 0; @@ -181,6 +193,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { child1.decreased_value = ""; child1.tooltip = ""; child1.child_count = 0; + child1.flags2 = &flags; child1.custom_accessibility_actions_count = 0; child1.rect = {90, 90, 100, 100}; // LTRB child1.transform = {2, 0, 0, 0, 2, 0, 0, 0, 1}; @@ -202,6 +215,7 @@ TEST(FlutterPlatformNodeDelegateTest, canCalculateOffScreenBoundsCorrectly) { TEST(FlutterPlatformNodeDelegateTest, canUseOwnerBridge) { std::shared_ptr bridge = std::make_shared(); + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; FlutterSemanticsNode2 root; root.id = 0; root.label = "root"; @@ -211,6 +225,7 @@ TEST(FlutterPlatformNodeDelegateTest, canUseOwnerBridge) { root.decreased_value = ""; root.tooltip = ""; root.child_count = 1; + root.flags2 = &flags; int32_t children[] = {1}; root.children_in_traversal_order = children; root.custom_accessibility_actions_count = 0; @@ -227,6 +242,7 @@ TEST(FlutterPlatformNodeDelegateTest, canUseOwnerBridge) { child1.decreased_value = ""; child1.tooltip = ""; child1.child_count = 0; + child1.flags2 = &flags; child1.custom_accessibility_actions_count = 0; child1.rect = {0, 0, 50, 50}; // LTRB child1.transform = {0.5, 0, 0, 0, 0.5, 0, 0, 0, 1}; @@ -249,6 +265,7 @@ TEST(FlutterPlatformNodeDelegateTest, canUseOwnerBridge) { TEST(FlutterPlatformNodeDelegateTest, selfIsLowestPlatformAncestor) { std::shared_ptr bridge = std::make_shared(); + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; FlutterSemanticsNode2 root; root.id = 0; root.label = "root"; @@ -258,6 +275,7 @@ TEST(FlutterPlatformNodeDelegateTest, selfIsLowestPlatformAncestor) { root.decreased_value = ""; root.tooltip = ""; root.child_count = 0; + root.flags2 = &flags; root.children_in_traversal_order = nullptr; root.custom_accessibility_actions_count = 0; bridge->AddFlutterSemanticsNodeUpdate(root); @@ -271,6 +289,7 @@ TEST(FlutterPlatformNodeDelegateTest, selfIsLowestPlatformAncestor) { TEST(FlutterPlatformNodeDelegateTest, canGetFromNodeID) { std::shared_ptr bridge = std::make_shared(); + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; FlutterSemanticsNode2 root; root.id = 0; root.label = "root"; @@ -280,6 +299,7 @@ TEST(FlutterPlatformNodeDelegateTest, canGetFromNodeID) { root.decreased_value = ""; root.tooltip = ""; root.child_count = 1; + root.flags2 = &flags; int32_t children[] = {1}; root.children_in_traversal_order = children; root.custom_accessibility_actions_count = 0; @@ -294,6 +314,7 @@ TEST(FlutterPlatformNodeDelegateTest, canGetFromNodeID) { child1.decreased_value = ""; child1.tooltip = ""; child1.child_count = 0; + child1.flags2 = &flags; child1.custom_accessibility_actions_count = 0; bridge->AddFlutterSemanticsNodeUpdate(child1); diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm index 12a83ec030d..9b4cc37c38d 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm @@ -99,8 +99,9 @@ TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlu auto bridge = std::static_pointer_cast( viewController.accessibilityBridge.lock()); FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; root.id = 0; - root.flags = static_cast(0); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -157,9 +158,10 @@ TEST_F(AccessibilityBridgeMacWindowTest, NonZeroRootNodeId) { viewController.accessibilityBridge.lock()); FlutterSemanticsNode2 node1; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; std::vector node1_children{2}; node1.id = 1; - node1.flags = static_cast(0); + node1.flags2 = &flags; node1.actions = static_cast(0); node1.text_selection_base = -1; node1.text_selection_extent = -1; @@ -176,7 +178,7 @@ TEST_F(AccessibilityBridgeMacWindowTest, NonZeroRootNodeId) { FlutterSemanticsNode2 node2; node2.id = 2; - node2.flags = static_cast(0); + node2.flags2 = &flags; node2.actions = static_cast(0); node2.text_selection_base = -1; node2.text_selection_extent = -1; @@ -220,8 +222,9 @@ TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhe auto bridge = std::static_pointer_cast( viewController.accessibilityBridge.lock()); FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; root.id = 0; - root.flags = static_cast(0); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -266,8 +269,9 @@ TEST_F(AccessibilityBridgeMacTest, DoesNotSendAccessibilityCreateNotificationWhe auto bridge = std::static_pointer_cast( viewController.accessibilityBridge.lock()); FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; root.id = 0; - root.flags = static_cast(0); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index b2fc041d977..9c9a4c54761 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -319,8 +319,10 @@ TEST_F(FlutterEngineTest, CanToggleAccessibility) { EXPECT_TRUE(enabled_called); // Send flutter semantics updates. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; + FlutterSemanticsFlags child_flags = FlutterSemanticsFlags{0}; root.id = 0; - root.flags = static_cast(0); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -337,7 +339,7 @@ TEST_F(FlutterEngineTest, CanToggleAccessibility) { FlutterSemanticsNode2 child1; child1.id = 1; - child1.flags = static_cast(0); + child1.flags2 = &child_flags; child1.actions = static_cast(0); child1.text_selection_base = -1; child1.text_selection_extent = -1; @@ -409,8 +411,10 @@ TEST_F(FlutterEngineTest, CanToggleAccessibilityWhenHeadless) { EXPECT_TRUE(enabled_called); // Send flutter semantics updates. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; + FlutterSemanticsFlags child_flags = FlutterSemanticsFlags{0}; root.id = 0; - root.flags = static_cast(0); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -427,7 +431,7 @@ TEST_F(FlutterEngineTest, CanToggleAccessibilityWhenHeadless) { FlutterSemanticsNode2 child1; child1.id = 1; - child1.flags = static_cast(0); + child1.flags2 = &child_flags; child1.actions = static_cast(0); child1.text_selection_base = -1; child1.text_selection_extent = -1; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm index 405e24347ee..cb3204c26cb 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformNodeDelegateMacTest.mm @@ -36,9 +36,9 @@ TEST(FlutterPlatformNodeDelegateMac, Basics) { auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; root.id = 0; - root.flags = static_cast(0); - ; + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -72,10 +72,9 @@ TEST(FlutterPlatformNodeDelegateMac, SelectableTextHasCorrectSemantics) { auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{.is_text_field = true, .is_read_only = true}; root.id = 0; - root.flags = - static_cast(FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = 1; root.text_selection_extent = 3; @@ -114,10 +113,9 @@ TEST(FlutterPlatformNodeDelegateMac, SelectableTextWithoutSelectionReturnZeroRan auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{.is_text_field = true, .is_read_only = true}; root.id = 0; - root.flags = - static_cast(FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly); + root.flags2 = &flags; root.actions = static_cast(0); root.text_selection_base = -1; root.text_selection_extent = -1; @@ -161,6 +159,8 @@ TEST(FlutterPlatformNodeDelegateMac, CanPerformAction) { auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{}; + root.flags2 = &flags; root.id = 0; root.label = "root"; root.hint = ""; @@ -175,6 +175,8 @@ TEST(FlutterPlatformNodeDelegateMac, CanPerformAction) { bridge->AddFlutterSemanticsNodeUpdate(root); FlutterSemanticsNode2 child1; + FlutterSemanticsFlags child_flags = FlutterSemanticsFlags{}; + child1.flags2 = &child_flags; child1.id = 1; child1.label = "child 1"; child1.hint = ""; @@ -236,8 +238,10 @@ TEST(FlutterPlatformNodeDelegateMac, TextFieldUsesFlutterTextField) { auto bridge = viewController.accessibilityBridge.lock(); // Initialize ax node data. FlutterSemanticsNode2 root; + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; + FlutterSemanticsFlags child_flags = FlutterSemanticsFlags{.is_text_field = true}; root.id = 0; - root.flags = static_cast(0); + root.flags2 = &flags; root.actions = static_cast(0); root.label = "root"; root.hint = ""; @@ -258,7 +262,7 @@ TEST(FlutterPlatformNodeDelegateMac, TextFieldUsesFlutterTextField) { FlutterSemanticsNode2 child1; child1.id = 1; - child1.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + child1.flags2 = &child_flags; child1.actions = static_cast(0); child1.label = ""; child1.hint = ""; @@ -313,7 +317,8 @@ TEST(FlutterPlatformNodeDelegateMac, ChangingFlagsUpdatesNativeViewAccessible) { // Initialize ax node data. FlutterSemanticsNode2 root; root.id = 0; - root.flags = static_cast(0); + FlutterSemanticsFlags flags = FlutterSemanticsFlags{0}; + root.flags2 = &flags; root.actions = static_cast(0); root.label = "root"; root.hint = ""; @@ -333,8 +338,9 @@ TEST(FlutterPlatformNodeDelegateMac, ChangingFlagsUpdatesNativeViewAccessible) { double transformFactor = 0.5; FlutterSemanticsNode2 child1; + FlutterSemanticsFlags child_flags = FlutterSemanticsFlags{0}; + child1.flags2 = &child_flags; child1.id = 1; - child1.flags = static_cast(0); child1.actions = static_cast(0); child1.label = ""; child1.hint = ""; @@ -358,14 +364,17 @@ TEST(FlutterPlatformNodeDelegateMac, ChangingFlagsUpdatesNativeViewAccessible) { EXPECT_TRUE([[native_accessibility className] isEqualToString:@"AXPlatformNodeCocoa"]); // Converting child to text field should produce `FlutterTextField` native view accessible. - child1.flags = FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField; + + FlutterSemanticsFlags child_flags_updated_1 = FlutterSemanticsFlags{.is_text_field = true}; + child1.flags2 = &child_flags_updated_1; bridge->AddFlutterSemanticsNodeUpdate(child1); bridge->CommitUpdates(); native_accessibility = child_platform_node_delegate->GetNativeViewAccessible(); EXPECT_TRUE([native_accessibility isKindOfClass:[FlutterTextField class]]); - child1.flags = static_cast(0); + FlutterSemanticsFlags child_flags_updated_2 = FlutterSemanticsFlags{.is_text_field = false}; + child1.flags2 = &child_flags_updated_2; bridge->AddFlutterSemanticsNodeUpdate(child1); bridge->CommitUpdates(); diff --git a/engine/src/flutter/shell/platform/embedder/embedder.h b/engine/src/flutter/shell/platform/embedder/embedder.h index a0978917561..76841f90a33 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.h +++ b/engine/src/flutter/shell/platform/embedder/embedder.h @@ -174,6 +174,10 @@ typedef enum { /// The set of properties that may be associated with a semantics node. /// /// Must match the `SemanticsFlag` enum in semantics.dart. +/// +/// @deprecated Use `FlutterSemanticsFlags` instead. No new flags will +/// be added to `FlutterSemanticsFlag`. New flags will +/// continue to be added to `FlutterSemanticsFlags`. typedef enum { /// The semantics node has the quality of either being "checked" or /// "unchecked". @@ -260,6 +264,94 @@ typedef enum { kFlutterSemanticsFlagIsRequired = 1 << 30, } FlutterSemanticsFlag; +typedef struct { + /// The size of this struct. Must be sizeof(FlutterSemanticsFlags). + size_t struct_size; + /// The semantics node has the quality of either being "checked" or + /// "unchecked". + bool has_checked_state; + /// Whether a semantics node is checked. + bool is_checked; + /// Whether a semantics node is selected. + bool is_selected; + /// Whether the semantic node represents a button. + bool is_button; + /// Whether the semantic node represents a text field. + bool is_text_field; + /// Whether the semantic node currently holds the user's focus. + bool is_focused; + /// The semantics node has the quality of either being "enabled" or + /// "disabled". + bool has_enabled_state; + /// Whether a semantic node that hasEnabledState is currently enabled. + bool is_enabled; + /// Whether a semantic node is in a mutually exclusive group. + bool is_in_mutually_exclusive_group; + /// Whether a semantic node is a header that divides content into sections. + bool is_header; + /// Whether the value of the semantics node is obscured. + bool is_obscured; + /// Whether the semantics node is the root of a subtree for which a route name + /// should be announced. + bool scopes_route; + /// Whether the semantics node label is the name of a visually distinct route. + bool names_route; + /// Whether the semantics node is considered hidden. + bool is_hidden; + /// Whether the semantics node represents an image. + bool is_image; + /// Whether the semantics node is a live region. + bool is_live_region; + /// The semantics node has the quality of either being "on" or "off". + bool has_toggled_state; + /// If true, the semantics node is "on". If false, the semantics node is + /// "off". + bool is_toggled; + /// Whether the platform can scroll the semantics node when the user attempts + /// to move the accessibility focus to an offscreen child. + /// + /// For example, a `ListView` widget has implicit scrolling so that users can + /// easily move the accessibility focus to the next set of children. A + /// `PageView` widget does not have implicit scrolling, so that users don't + /// navigate to the next page when reaching the end of the current one. + bool has_implicit_scrolling; + /// Whether the value of the semantics node is coming from a multi-line text + /// field. + /// + /// This is used for text fields to distinguish single-line text fields from + /// multi-line ones. + bool is_multiline; + /// Whether the semantic node is read only. + /// + /// Only applicable when kFlutterSemanticsFlagIsTextField flag is on. + bool is_read_only; + /// Whether the semantic node can hold the user's focus. + bool is_focusable; + /// Whether the semantics node represents a link. + bool is_link; + /// Whether the semantics node represents a slider. + bool is_slider; + /// Whether the semantics node represents a keyboard key. + bool is_keyboard_key; + /// Whether the semantics node represents a tristate checkbox in mixed state. + bool is_check_state_mixed; + /// The semantics node has the quality of either being "expanded" or + /// "collapsed". + bool has_expanded_state; + /// Whether a semantic node that hasExpandedState is currently expanded. + bool is_expanded; + /// The semantics node has the quality of either being "selected" or + /// "not selected". + bool has_selected_state; + /// Whether a semantics node has the quality of being required. + bool has_required_state; + /// Whether user input is required on the semantics node before a form can be + /// submitted. + /// + /// Only applicable when kFlutterSemanticsFlagHasRequiredState flag is on. + bool is_required; +} FlutterSemanticsFlags; + typedef enum { /// Text has unknown text direction. kFlutterTextDirectionUnknown = 0, @@ -1512,7 +1604,11 @@ typedef struct { /// The unique identifier for this node. int32_t id; /// The set of semantics flags associated with this node. - FlutterSemanticsFlag flags; + /// + /// @deprecated Use `flags2` instead. No new flags will + /// be added to `FlutterSemanticsFlag`. New flags will + /// continue to be added to `FlutterSemanticsFlags`. + FlutterSemanticsFlag flags__deprecated__; /// The set of semantics actions applicable to this node. FlutterSemanticsAction actions; /// The position at which the text selection originates. @@ -1596,6 +1692,9 @@ typedef struct { // Array of string attributes associated with the `decreased_value`. // Has length `decreased_value_attribute_count`. const FlutterStringAttribute** decreased_value_attributes; + // The set of semantics flags associated with this node. Prefer to use this + // over `flags__deprecated__`. + FlutterSemanticsFlags* flags2; } FlutterSemanticsNode2; /// `FlutterSemanticsCustomAction` ID used as a sentinel to signal the end of a diff --git a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc index 6376d49eedb..069f83f0eef 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.cc @@ -4,6 +4,45 @@ #include "flutter/shell/platform/embedder/embedder_semantics_update.h" +namespace { +std::unique_ptr ConvertToFlutterSemanticsFlags( + const flutter::SemanticsFlags& source) { + return std::make_unique(FlutterSemanticsFlags{ + .has_checked_state = source.hasCheckedState, + .is_checked = source.isChecked, + .is_selected = source.isSelected, + .is_button = source.isButton, + .is_text_field = source.isTextField, + .is_focused = source.isFocused, + .has_enabled_state = source.hasEnabledState, + .is_enabled = source.isEnabled, + .is_in_mutually_exclusive_group = source.isInMutuallyExclusiveGroup, + .is_header = source.isHeader, + .is_obscured = source.isObscured, + .scopes_route = source.scopesRoute, + .names_route = source.namesRoute, + .is_hidden = source.isHidden, + .is_image = source.isImage, + .is_live_region = source.isLiveRegion, + .has_toggled_state = source.hasToggledState, + .is_toggled = source.isToggled, + .has_implicit_scrolling = source.hasImplicitScrolling, + .is_multiline = source.isMultiline, + .is_read_only = source.isReadOnly, + .is_focusable = source.isFocusable, + .is_link = source.isLink, + .is_slider = source.isSlider, + .is_keyboard_key = source.isKeyboardKey, + .is_check_state_mixed = source.isCheckStateMixed, + .has_expanded_state = source.hasExpandedState, + .is_expanded = source.isExpanded, + .has_selected_state = source.hasSelectedState, + .has_required_state = source.hasRequiredState, + .is_required = source.isRequired, + }); +} +} // namespace + namespace flutter { EmbedderSemanticsUpdate::EmbedderSemanticsUpdate( @@ -26,6 +65,9 @@ EmbedderSemanticsUpdate::EmbedderSemanticsUpdate( }; } +// This function is for backward compatibility and contains only a subset of +// the flags. New flags will be added only to `FlutterSemanticsFlags`, not +// `FlutterSemanticsFlag`. FlutterSemanticsFlag SemanticsFlagsToInt(const SemanticsFlags& flags) { int result = 0; @@ -192,6 +234,7 @@ EmbedderSemanticsUpdate2::EmbedderSemanticsUpdate2( const SemanticsNodeUpdates& nodes, const CustomAccessibilityActionUpdates& actions) { nodes_.reserve(nodes.size()); + flags_.reserve(nodes.size()); node_pointers_.reserve(nodes.size()); actions_.reserve(actions.size()); action_pointers_.reserve(actions.size()); @@ -238,6 +281,7 @@ void EmbedderSemanticsUpdate2::AddNode(const SemanticsNode& node) { CreateStringAttributes(node.increasedValueAttributes); auto decreased_value_attributes = CreateStringAttributes(node.decreasedValueAttributes); + flags_.emplace_back(ConvertToFlutterSemanticsFlags(node.flags)); nodes_.push_back({ sizeof(FlutterSemanticsNode2), @@ -279,6 +323,7 @@ void EmbedderSemanticsUpdate2::AddNode(const SemanticsNode& node) { increased_value_attributes.attributes, decreased_value_attributes.count, decreased_value_attributes.attributes, + flags_.back().get(), }); } diff --git a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h index 3dbc5a5b645..c9ab10ebcf4 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_semantics_update.h @@ -67,6 +67,7 @@ class EmbedderSemanticsUpdate2 { std::vector node_pointers_; std::vector actions_; std::vector action_pointers_; + std::vector> flags_; std::vector>> node_string_attributes_; diff --git a/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc b/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc index a7cb0c48c85..4f44f4634a2 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view_accessible.cc @@ -33,7 +33,7 @@ static FlAccessibleNode* create_node(FlViewAccessible* self, return nullptr; } - if (semantics->flags & kFlutterSemanticsFlagIsTextField) { + if (semantics->flags__deprecated__ & kFlutterSemanticsFlagIsTextField) { return fl_accessible_text_field_new(engine, self->view_id, semantics->id); } @@ -152,7 +152,7 @@ void fl_view_accessible_handle_update_semantics( FlutterSemanticsNode2* node = update->nodes[i]; FlAccessibleNode* atk_node = get_node(self, node); - fl_accessible_node_set_flags(atk_node, node->flags); + fl_accessible_node_set_flags(atk_node, node->flags__deprecated__); fl_accessible_node_set_actions(atk_node, node->actions); fl_accessible_node_set_name(atk_node, node->label); fl_accessible_node_set_extents( diff --git a/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc b/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc index 4edcffdbdf3..47183cce556 100644 --- a/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/accessibility_bridge_windows_unittests.cc @@ -140,15 +140,18 @@ std::unique_ptr GetTestEngine() { void PopulateAXTree(std::shared_ptr bridge) { // Add node 0: root. FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0}; + auto empty_flags = FlutterSemanticsFlags{}; std::vector node0_children{1, 2}; node0.child_count = node0_children.size(); node0.children_in_traversal_order = node0_children.data(); node0.children_in_hit_test_order = node0_children.data(); + node0.flags2 = &empty_flags; // Add node 1: text child of node 0. FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1}; node1.label = "prefecture"; node1.value = "Kyoto"; + node1.flags2 = &empty_flags; // Add node 2: subtree child of node 0. FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2}; @@ -156,14 +159,17 @@ void PopulateAXTree(std::shared_ptr bridge) { node2.child_count = node2_children.size(); node2.children_in_traversal_order = node2_children.data(); node2.children_in_hit_test_order = node2_children.data(); + node2.flags2 = &empty_flags; // Add node 3: text child of node 2. FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3}; node3.label = "city"; node3.value = "Uji"; + node3.flags2 = &empty_flags; // Add node 4: text child (with no text) of node 2. FlutterSemanticsNode2 node4{sizeof(FlutterSemanticsNode2), 4}; + node4.flags2 = &empty_flags; bridge->AddFlutterSemanticsNodeUpdate(node0); bridge->AddFlutterSemanticsNodeUpdate(node1); diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_view_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_view_unittests.cc index c382bdb4bc1..74b48863971 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_view_unittests.cc @@ -162,9 +162,12 @@ TEST(FlutterWindowsViewTest, SubMenuExpandedState) { root.decreased_value = ""; root.child_count = 0; root.custom_accessibility_actions_count = 0; - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsExpanded); + auto flags = FlutterSemanticsFlags{ + .has_expanded_state = true, + .is_expanded = true, + }; + root.flags2 = &flags; + bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -200,8 +203,11 @@ TEST(FlutterWindowsViewTest, SubMenuExpandedState) { } // Test collapsed too. - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasExpandedState); + auto updated_flags = FlutterSemanticsFlags{ + .has_expanded_state = true, + }; + root.flags2 = &updated_flags; + bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -381,6 +387,8 @@ TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdate) { node.label = "name"; node.value = "value"; node.platform_view_id = -1; + auto flags = FlutterSemanticsFlags{}; + node.flags2 = &flags; bridge->AddFlutterSemanticsNodeUpdate(node); bridge->CommitUpdates(); @@ -479,18 +487,23 @@ TEST(FlutterWindowsViewTest, AddSemanticsNodeUpdateWithChildren) { node0.child_count = node0_children.size(); node0.children_in_traversal_order = node0_children.data(); node0.children_in_hit_test_order = node0_children.data(); + auto empty_flags = FlutterSemanticsFlags{}; + node0.flags2 = &empty_flags; FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1}; node1.label = "prefecture"; node1.value = "Kyoto"; + node1.flags2 = &empty_flags; FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2}; std::vector node2_children{3}; node2.child_count = node2_children.size(); node2.children_in_traversal_order = node2_children.data(); node2.children_in_hit_test_order = node2_children.data(); + node2.flags2 = &empty_flags; FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3}; node3.label = "city"; node3.value = "Uji"; + node3.flags2 = &empty_flags; bridge->AddFlutterSemanticsNodeUpdate(node0); bridge->AddFlutterSemanticsNodeUpdate(node1); @@ -675,11 +688,13 @@ TEST(FlutterWindowsViewTest, NonZeroSemanticsRoot) { node1.child_count = node1_children.size(); node1.children_in_traversal_order = node1_children.data(); node1.children_in_hit_test_order = node1_children.data(); + auto empty_flags = FlutterSemanticsFlags{}; + node1.flags2 = &empty_flags; FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2}; node2.label = "prefecture"; node2.value = "Kyoto"; - + node2.flags2 = &empty_flags; bridge->AddFlutterSemanticsNodeUpdate(node1); bridge->AddFlutterSemanticsNodeUpdate(node2); bridge->CommitUpdates(); @@ -801,12 +816,14 @@ TEST(FlutterWindowsViewTest, AccessibilityHitTesting) { // Add root node at origin. Size 500x500. FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0}; + auto empty_flags = FlutterSemanticsFlags{}; std::vector node0_children{1, 2}; node0.rect = {0, 0, 500, 500}; node0.transform = kIdentityTransform; node0.child_count = node0_children.size(); node0.children_in_traversal_order = node0_children.data(); node0.children_in_hit_test_order = node0_children.data(); + node0.flags2 = &empty_flags; // Add node 1 located at 0,0 relative to node 0. Size 250x500. FlutterSemanticsNode2 node1{sizeof(FlutterSemanticsNode2), 1}; @@ -814,6 +831,7 @@ TEST(FlutterWindowsViewTest, AccessibilityHitTesting) { node1.transform = kIdentityTransform; node1.label = "prefecture"; node1.value = "Kyoto"; + node1.flags2 = &empty_flags; // Add node 2 located at 250,0 relative to node 0. Size 250x500. FlutterSemanticsNode2 node2{sizeof(FlutterSemanticsNode2), 2}; @@ -823,6 +841,7 @@ TEST(FlutterWindowsViewTest, AccessibilityHitTesting) { node2.child_count = node2_children.size(); node2.children_in_traversal_order = node2_children.data(); node2.children_in_hit_test_order = node2_children.data(); + node2.flags2 = &empty_flags; // Add node 3 located at 0,250 relative to node 2. Size 250, 250. FlutterSemanticsNode2 node3{sizeof(FlutterSemanticsNode2), 3}; @@ -830,6 +849,7 @@ TEST(FlutterWindowsViewTest, AccessibilityHitTesting) { node3.transform = {1, 0, 0, 0, 1, 250, 0, 0, 1}; node3.label = "city"; node3.value = "Uji"; + node3.flags2 = &empty_flags; bridge->AddFlutterSemanticsNodeUpdate(node0); bridge->AddFlutterSemanticsNodeUpdate(node1); @@ -1156,9 +1176,11 @@ TEST(FlutterWindowsViewTest, CheckboxNativeState) { root.decreased_value = ""; root.child_count = 0; root.custom_accessibility_actions_count = 0; - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked); + auto flags = FlutterSemanticsFlags{ + .has_checked_state = true, + .is_checked = true, + }; + root.flags2 = &flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -1196,8 +1218,10 @@ TEST(FlutterWindowsViewTest, CheckboxNativeState) { } // Test unchecked too. - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState); + auto updated_flags = FlutterSemanticsFlags{ + .has_checked_state = true, + }; + root.flags2 = &updated_flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -1234,9 +1258,11 @@ TEST(FlutterWindowsViewTest, CheckboxNativeState) { } // Now check mixed state. - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsCheckStateMixed); + auto updated_mixe_flags = FlutterSemanticsFlags{ + .has_checked_state = true, + .is_check_state_mixed = true, + }; + root.flags2 = &updated_mixe_flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -1300,9 +1326,12 @@ TEST(FlutterWindowsViewTest, SwitchNativeState) { root.decreased_value = ""; root.child_count = 0; root.custom_accessibility_actions_count = 0; - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState | - FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled); + + auto flags = FlutterSemanticsFlags{ + .has_toggled_state = true, + .is_toggled = true, + }; + root.flags2 = &flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -1351,8 +1380,11 @@ TEST(FlutterWindowsViewTest, SwitchNativeState) { } // Test unpressed too. - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState); + auto updated_flags = FlutterSemanticsFlags{ + .has_toggled_state = true, + }; + root.flags2 = &updated_flags; + bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates(); @@ -1418,8 +1450,10 @@ TEST(FlutterWindowsViewTest, TooltipNodeData) { root.tooltip = "tooltip"; root.child_count = 0; root.custom_accessibility_actions_count = 0; - root.flags = static_cast( - FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField); + auto flags = FlutterSemanticsFlags{ + .is_text_field = true, + }; + root.flags2 = &flags; bridge->AddFlutterSemanticsNodeUpdate(root); bridge->CommitUpdates();