mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
83c86873fe
commit
cb01defdcf
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user