[a11y] Semantics flag refactor step 2: embedder part (#167738)

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].

<!-- Links -->
[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 <chris@bracken.jp>
Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
This commit is contained in:
Hannah Jin 2025-06-10 11:11:07 -07:00 committed by GitHub
parent 83c86873fe
commit cb01defdcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 354 additions and 122 deletions

View File

@ -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<int32_t>(
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<int32_t>(
flags & FlutterSemanticsFlag::kFlutterSemanticsFlagIsToggled
? ax::mojom::CheckedState::kTrue
: ax::mojom::CheckedState::kFalse));
static_cast<int32_t>(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;

View File

@ -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;

View File

@ -15,13 +15,15 @@ namespace testing {
using ::testing::Contains;
FlutterSemanticsFlags kEmptyFlags = FlutterSemanticsFlags{};
FlutterSemanticsNode2 CreateSemanticsNode(
int32_t id,
const char* label,
const std::vector<int32_t>* children = nullptr) {
return {
.id = id,
.flags = static_cast<FlutterSemanticsFlag>(0),
.flags__deprecated__ = static_cast<FlutterSemanticsFlag>(0),
.actions = static_cast<FlutterSemanticsAction>(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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
root.flags = static_cast<FlutterSemanticsFlag>(
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
root.flags = static_cast<FlutterSemanticsFlag>(
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
root.flags = static_cast<FlutterSemanticsFlag>(
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
root.flags = static_cast<FlutterSemanticsFlag>(
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
root.flags = static_cast<FlutterSemanticsFlag>(
FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState |
FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked);
auto flags = FlutterSemanticsFlags{
.has_checked_state = true,
.is_checked = true,
};
root.flags2 = &flags;
bridge->AddFlutterSemanticsNodeUpdate(root);
bridge->CommitUpdates();

View File

@ -19,14 +19,17 @@ TEST(FlutterPlatformNodeDelegateTest, NodeDelegateHasUniqueId) {
// Add node 0: root.
FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0};
std::vector<int32_t> 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<TestAccessibilityBridge>();
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<FlutterSemanticsAction>(0);
root.text_selection_base = -1;
root.text_selection_extent = -1;
@ -87,7 +92,9 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) {
std::make_shared<TestAccessibilityBridge>();
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<FlutterSemanticsAction>(0);
root.text_selection_base = -1;
root.text_selection_extent = -1;
@ -110,6 +117,7 @@ TEST(FlutterPlatformNodeDelegateTest, canGetAXNode) {
TEST(FlutterPlatformNodeDelegateTest, canCalculateBoundsCorrectly) {
std::shared_ptr<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
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<TestAccessibilityBridge> bridge =
std::make_shared<TestAccessibilityBridge>();
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);

View File

@ -99,8 +99,9 @@ TEST_F(AccessibilityBridgeMacWindowTest, SendsAccessibilityCreateNotificationFlu
auto bridge = std::static_pointer_cast<AccessibilityBridgeMacSpy>(
viewController.accessibilityBridge.lock());
FlutterSemanticsNode2 root;
FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<int32_t> node1_children{2};
node1.id = 1;
node1.flags = static_cast<FlutterSemanticsFlag>(0);
node1.flags2 = &flags;
node1.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
node2.flags2 = &flags;
node2.actions = static_cast<FlutterSemanticsAction>(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<AccessibilityBridgeMacSpy>(
viewController.accessibilityBridge.lock());
FlutterSemanticsNode2 root;
FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<AccessibilityBridgeMacSpy>(
viewController.accessibilityBridge.lock());
FlutterSemanticsNode2 root;
FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
root.id = 0;
root.flags = static_cast<FlutterSemanticsFlag>(0);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(0);
root.text_selection_base = -1;
root.text_selection_extent = -1;

View File

@ -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<FlutterSemanticsFlag>(0);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
child1.flags2 = &child_flags;
child1.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
child1.flags2 = &child_flags;
child1.actions = static_cast<FlutterSemanticsAction>(0);
child1.text_selection_base = -1;
child1.text_selection_extent = -1;

View File

@ -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<FlutterSemanticsFlag>(0);
;
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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>(FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField |
FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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>(FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField |
FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
FlutterSemanticsFlags flags = FlutterSemanticsFlags{0};
root.flags2 = &flags;
root.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
child1.actions = static_cast<FlutterSemanticsAction>(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<FlutterSemanticsFlag>(0);
FlutterSemanticsFlags child_flags_updated_2 = FlutterSemanticsFlags{.is_text_field = false};
child1.flags2 = &child_flags_updated_2;
bridge->AddFlutterSemanticsNodeUpdate(child1);
bridge->CommitUpdates();

View File

@ -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

View File

@ -4,6 +4,45 @@
#include "flutter/shell/platform/embedder/embedder_semantics_update.h"
namespace {
std::unique_ptr<FlutterSemanticsFlags> ConvertToFlutterSemanticsFlags(
const flutter::SemanticsFlags& source) {
return std::make_unique<FlutterSemanticsFlags>(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(),
});
}

View File

@ -67,6 +67,7 @@ class EmbedderSemanticsUpdate2 {
std::vector<FlutterSemanticsNode2*> node_pointers_;
std::vector<FlutterSemanticsCustomAction2> actions_;
std::vector<FlutterSemanticsCustomAction2*> action_pointers_;
std::vector<std::unique_ptr<FlutterSemanticsFlags>> flags_;
std::vector<std::unique_ptr<std::vector<const FlutterStringAttribute*>>>
node_string_attributes_;

View File

@ -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(

View File

@ -140,15 +140,18 @@ std::unique_ptr<FlutterWindowsEngine> GetTestEngine() {
void PopulateAXTree(std::shared_ptr<AccessibilityBridge> bridge) {
// Add node 0: root.
FlutterSemanticsNode2 node0{sizeof(FlutterSemanticsNode2), 0};
auto empty_flags = FlutterSemanticsFlags{};
std::vector<int32_t> 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<AccessibilityBridge> 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);

View File

@ -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>(
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>(
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<int32_t> 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<int32_t> 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>(
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>(
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>(
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>(
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>(
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>(
FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField);
auto flags = FlutterSemanticsFlags{
.is_text_field = true,
};
root.flags2 = &flags;
bridge->AddFlutterSemanticsNodeUpdate(root);
bridge->CommitUpdates();