// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/accessibility/ax_node_data.h" #include #include #include #include #include "base/containers/cxx20_erase.h" #include "base/no_destructor.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_enum_util.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_role_properties.h" #include "ui/accessibility/ax_tree_id.h" #include "ui/gfx/transform.h" namespace ui { namespace { bool IsFlagSet(uint32_t bitfield, uint32_t flag) { return (bitfield & (1U << flag)) != 0; } bool IsFlagSet(uint64_t bitfield, uint32_t flag) { return (bitfield & (1ULL << flag)) != 0; } uint32_t ModifyFlag(uint32_t bitfield, uint32_t flag, bool set) { return set ? (bitfield |= (1U << flag)) : (bitfield &= ~(1U << flag)); } uint64_t ModifyFlag(uint64_t bitfield, uint32_t flag, bool set) { return set ? (bitfield |= (1ULL << flag)) : (bitfield &= ~(1ULL << flag)); } std::string StateBitfieldToString(uint32_t state_enum) { std::string str; for (uint32_t i = static_cast(ax::mojom::State::kNone) + 1; i <= static_cast(ax::mojom::State::kMaxValue); ++i) { if (IsFlagSet(state_enum, i)) str += " " + base::ToUpperASCII(ui::ToString(static_cast(i))); } return str; } std::string ActionsBitfieldToString(uint64_t actions) { std::string str; for (uint32_t i = static_cast(ax::mojom::Action::kNone) + 1; i <= static_cast(ax::mojom::Action::kMaxValue); ++i) { if (IsFlagSet(actions, i)) { str += ui::ToString(static_cast(i)); actions = ModifyFlag(actions, i, false); str += actions ? "," : ""; } } return str; } std::string IntVectorToString(const std::vector& items) { std::string str; for (size_t i = 0; i < items.size(); ++i) { if (i > 0) str += ","; str += base::NumberToString(items[i]); } return str; } // Predicate that returns true if the first value of a pair is |first|. template struct FirstIs { explicit FirstIs(FirstType first) : first_(first) {} bool operator()(std::pair const& p) { return p.first == first_; } FirstType first_; }; // Helper function that finds a key in a vector of pairs by matching on the // first value, and returns an iterator. template typename std::vector>::const_iterator FindInVectorOfPairs( FirstType first, const std::vector>& vector) { return std::find_if(vector.begin(), vector.end(), FirstIs(first)); } } // namespace // Return true if |attr| is a node ID that would need to be mapped when // renumbering the ids in a combined tree. bool IsNodeIdIntAttribute(ax::mojom::IntAttribute attr) { switch (attr) { case ax::mojom::IntAttribute::kActivedescendantId: case ax::mojom::IntAttribute::kErrormessageId: case ax::mojom::IntAttribute::kInPageLinkTargetId: case ax::mojom::IntAttribute::kMemberOfId: case ax::mojom::IntAttribute::kNextOnLineId: case ax::mojom::IntAttribute::kPopupForId: case ax::mojom::IntAttribute::kPreviousOnLineId: case ax::mojom::IntAttribute::kTableHeaderId: case ax::mojom::IntAttribute::kTableColumnHeaderId: case ax::mojom::IntAttribute::kTableRowHeaderId: case ax::mojom::IntAttribute::kNextFocusId: case ax::mojom::IntAttribute::kPreviousFocusId: return true; // Note: all of the attributes are included here explicitly, // rather than using "default:", so that it's a compiler error to // add a new attribute without explicitly considering whether it's // a node id attribute or not. case ax::mojom::IntAttribute::kNone: case ax::mojom::IntAttribute::kDefaultActionVerb: case ax::mojom::IntAttribute::kScrollX: case ax::mojom::IntAttribute::kScrollXMin: case ax::mojom::IntAttribute::kScrollXMax: case ax::mojom::IntAttribute::kScrollY: case ax::mojom::IntAttribute::kScrollYMin: case ax::mojom::IntAttribute::kScrollYMax: case ax::mojom::IntAttribute::kTextSelStart: case ax::mojom::IntAttribute::kTextSelEnd: case ax::mojom::IntAttribute::kTableRowCount: case ax::mojom::IntAttribute::kTableColumnCount: case ax::mojom::IntAttribute::kTableRowIndex: case ax::mojom::IntAttribute::kTableColumnIndex: case ax::mojom::IntAttribute::kTableCellColumnIndex: case ax::mojom::IntAttribute::kTableCellColumnSpan: case ax::mojom::IntAttribute::kTableCellRowIndex: case ax::mojom::IntAttribute::kTableCellRowSpan: case ax::mojom::IntAttribute::kSortDirection: case ax::mojom::IntAttribute::kHierarchicalLevel: case ax::mojom::IntAttribute::kNameFrom: case ax::mojom::IntAttribute::kDescriptionFrom: case ax::mojom::IntAttribute::kSetSize: case ax::mojom::IntAttribute::kPosInSet: case ax::mojom::IntAttribute::kColorValue: case ax::mojom::IntAttribute::kAriaCurrentState: case ax::mojom::IntAttribute::kHasPopup: case ax::mojom::IntAttribute::kBackgroundColor: case ax::mojom::IntAttribute::kColor: case ax::mojom::IntAttribute::kInvalidState: case ax::mojom::IntAttribute::kCheckedState: case ax::mojom::IntAttribute::kRestriction: case ax::mojom::IntAttribute::kListStyle: case ax::mojom::IntAttribute::kTextAlign: case ax::mojom::IntAttribute::kTextDirection: case ax::mojom::IntAttribute::kTextPosition: case ax::mojom::IntAttribute::kTextStyle: case ax::mojom::IntAttribute::kTextOverlineStyle: case ax::mojom::IntAttribute::kTextStrikethroughStyle: case ax::mojom::IntAttribute::kTextUnderlineStyle: case ax::mojom::IntAttribute::kAriaColumnCount: case ax::mojom::IntAttribute::kAriaCellColumnIndex: case ax::mojom::IntAttribute::kAriaCellColumnSpan: case ax::mojom::IntAttribute::kAriaRowCount: case ax::mojom::IntAttribute::kAriaCellRowIndex: case ax::mojom::IntAttribute::kAriaCellRowSpan: case ax::mojom::IntAttribute::kImageAnnotationStatus: case ax::mojom::IntAttribute::kDropeffect: case ax::mojom::IntAttribute::kDOMNodeId: return false; } NOTREACHED(); return false; } // Return true if |attr| contains a vector of node ids that would need // to be mapped when renumbering the ids in a combined tree. bool IsNodeIdIntListAttribute(ax::mojom::IntListAttribute attr) { switch (attr) { case ax::mojom::IntListAttribute::kControlsIds: case ax::mojom::IntListAttribute::kDetailsIds: case ax::mojom::IntListAttribute::kDescribedbyIds: case ax::mojom::IntListAttribute::kFlowtoIds: case ax::mojom::IntListAttribute::kIndirectChildIds: case ax::mojom::IntListAttribute::kLabelledbyIds: case ax::mojom::IntListAttribute::kRadioGroupIds: return true; // Note: all of the attributes are included here explicitly, // rather than using "default:", so that it's a compiler error to // add a new attribute without explicitly considering whether it's // a node id attribute or not. case ax::mojom::IntListAttribute::kNone: case ax::mojom::IntListAttribute::kMarkerTypes: case ax::mojom::IntListAttribute::kMarkerStarts: case ax::mojom::IntListAttribute::kMarkerEnds: case ax::mojom::IntListAttribute::kCharacterOffsets: case ax::mojom::IntListAttribute::kCachedLineStarts: case ax::mojom::IntListAttribute::kWordStarts: case ax::mojom::IntListAttribute::kWordEnds: case ax::mojom::IntListAttribute::kCustomActionIds: return false; } NOTREACHED(); return false; } AXNodeData::AXNodeData() : role(ax::mojom::Role::kUnknown) {} AXNodeData::~AXNodeData() = default; AXNodeData::AXNodeData(const AXNodeData& other) { id = other.id; role = other.role; state = other.state; actions = other.actions; string_attributes = other.string_attributes; int_attributes = other.int_attributes; float_attributes = other.float_attributes; bool_attributes = other.bool_attributes; intlist_attributes = other.intlist_attributes; stringlist_attributes = other.stringlist_attributes; html_attributes = other.html_attributes; child_ids = other.child_ids; relative_bounds = other.relative_bounds; } AXNodeData::AXNodeData(AXNodeData&& other) { id = other.id; role = other.role; state = other.state; actions = other.actions; string_attributes.swap(other.string_attributes); int_attributes.swap(other.int_attributes); float_attributes.swap(other.float_attributes); bool_attributes.swap(other.bool_attributes); intlist_attributes.swap(other.intlist_attributes); stringlist_attributes.swap(other.stringlist_attributes); html_attributes.swap(other.html_attributes); child_ids.swap(other.child_ids); relative_bounds = other.relative_bounds; } AXNodeData& AXNodeData::operator=(AXNodeData other) { id = other.id; role = other.role; state = other.state; actions = other.actions; string_attributes = other.string_attributes; int_attributes = other.int_attributes; float_attributes = other.float_attributes; bool_attributes = other.bool_attributes; intlist_attributes = other.intlist_attributes; stringlist_attributes = other.stringlist_attributes; html_attributes = other.html_attributes; child_ids = other.child_ids; relative_bounds = other.relative_bounds; return *this; } bool AXNodeData::HasBoolAttribute(ax::mojom::BoolAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, bool_attributes); return iter != bool_attributes.end(); } bool AXNodeData::GetBoolAttribute(ax::mojom::BoolAttribute attribute) const { bool result; if (GetBoolAttribute(attribute, &result)) return result; return false; } bool AXNodeData::GetBoolAttribute(ax::mojom::BoolAttribute attribute, bool* value) const { auto iter = FindInVectorOfPairs(attribute, bool_attributes); if (iter != bool_attributes.end()) { *value = iter->second; return true; } return false; } bool AXNodeData::HasFloatAttribute(ax::mojom::FloatAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, float_attributes); return iter != float_attributes.end(); } float AXNodeData::GetFloatAttribute(ax::mojom::FloatAttribute attribute) const { float result; if (GetFloatAttribute(attribute, &result)) return result; return 0.0; } bool AXNodeData::GetFloatAttribute(ax::mojom::FloatAttribute attribute, float* value) const { auto iter = FindInVectorOfPairs(attribute, float_attributes); if (iter != float_attributes.end()) { *value = iter->second; return true; } return false; } bool AXNodeData::HasIntAttribute(ax::mojom::IntAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, int_attributes); return iter != int_attributes.end(); } int AXNodeData::GetIntAttribute(ax::mojom::IntAttribute attribute) const { int result; if (GetIntAttribute(attribute, &result)) return result; return 0; } bool AXNodeData::GetIntAttribute(ax::mojom::IntAttribute attribute, int* value) const { auto iter = FindInVectorOfPairs(attribute, int_attributes); if (iter != int_attributes.end()) { *value = int{iter->second}; return true; } return false; } bool AXNodeData::HasStringAttribute( ax::mojom::StringAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, string_attributes); return iter != string_attributes.end(); } const std::string& AXNodeData::GetStringAttribute( ax::mojom::StringAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, string_attributes); return iter != string_attributes.end() ? iter->second : base::EmptyString(); } bool AXNodeData::GetStringAttribute(ax::mojom::StringAttribute attribute, std::string* value) const { auto iter = FindInVectorOfPairs(attribute, string_attributes); if (iter != string_attributes.end()) { *value = iter->second; return true; } return false; } std::u16string AXNodeData::GetString16Attribute( ax::mojom::StringAttribute attribute) const { std::string value_utf8; if (!GetStringAttribute(attribute, &value_utf8)) return std::u16string(); return base::UTF8ToUTF16(value_utf8); } bool AXNodeData::GetString16Attribute(ax::mojom::StringAttribute attribute, std::u16string* value) const { std::string value_utf8; if (!GetStringAttribute(attribute, &value_utf8)) return false; *value = base::UTF8ToUTF16(value_utf8); return true; } bool AXNodeData::HasIntListAttribute( ax::mojom::IntListAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, intlist_attributes); return iter != intlist_attributes.end(); } const std::vector& AXNodeData::GetIntListAttribute( ax::mojom::IntListAttribute attribute) const { static const base::NoDestructor> empty_vector; auto iter = FindInVectorOfPairs(attribute, intlist_attributes); if (iter != intlist_attributes.end()) return iter->second; return *empty_vector; } bool AXNodeData::GetIntListAttribute(ax::mojom::IntListAttribute attribute, std::vector* value) const { auto iter = FindInVectorOfPairs(attribute, intlist_attributes); if (iter != intlist_attributes.end()) { *value = iter->second; return true; } return false; } bool AXNodeData::HasStringListAttribute( ax::mojom::StringListAttribute attribute) const { auto iter = FindInVectorOfPairs(attribute, stringlist_attributes); return iter != stringlist_attributes.end(); } const std::vector& AXNodeData::GetStringListAttribute( ax::mojom::StringListAttribute attribute) const { static const base::NoDestructor> empty_vector; auto iter = FindInVectorOfPairs(attribute, stringlist_attributes); if (iter != stringlist_attributes.end()) return iter->second; return *empty_vector; } bool AXNodeData::GetStringListAttribute( ax::mojom::StringListAttribute attribute, std::vector* value) const { auto iter = FindInVectorOfPairs(attribute, stringlist_attributes); if (iter != stringlist_attributes.end()) { *value = iter->second; return true; } return false; } bool AXNodeData::GetHtmlAttribute(const char* attribute, std::string* value) const { for (const std::pair& html_attribute : html_attributes) { const std::string& attr = html_attribute.first; if (base::LowerCaseEqualsASCII(attr, attribute)) { *value = html_attribute.second; return true; } } return false; } bool AXNodeData::GetHtmlAttribute(const char* attribute, std::u16string* value) const { std::string value_utf8; if (!GetHtmlAttribute(attribute, &value_utf8)) return false; *value = base::UTF8ToUTF16(value_utf8); return true; } void AXNodeData::AddChildTreeId(const AXTreeID& tree_id) { ax::mojom::StringAttribute attribute = ax::mojom::StringAttribute::kChildTreeId; if (HasStringAttribute(attribute)) RemoveStringAttribute(attribute); string_attributes.emplace_back(attribute, tree_id.ToString()); } void AXNodeData::AddBoolAttribute(ax::mojom::BoolAttribute attribute, bool value) { DCHECK_NE(attribute, ax::mojom::BoolAttribute::kNone); if (HasBoolAttribute(attribute)) RemoveBoolAttribute(attribute); bool_attributes.emplace_back(attribute, value); } void AXNodeData::AddIntAttribute(ax::mojom::IntAttribute attribute, int value) { DCHECK_NE(attribute, ax::mojom::IntAttribute::kNone); if (HasIntAttribute(attribute)) RemoveIntAttribute(attribute); int_attributes.emplace_back(attribute, value); } void AXNodeData::AddFloatAttribute(ax::mojom::FloatAttribute attribute, float value) { DCHECK_NE(attribute, ax::mojom::FloatAttribute::kNone); if (HasFloatAttribute(attribute)) RemoveFloatAttribute(attribute); float_attributes.emplace_back(attribute, value); } void AXNodeData::AddStringAttribute(ax::mojom::StringAttribute attribute, const std::string& value) { DCHECK_NE(attribute, ax::mojom::StringAttribute::kNone); DCHECK_NE(attribute, ax::mojom::StringAttribute::kChildTreeId) << "Use AddChildTreeId."; if (HasStringAttribute(attribute)) RemoveStringAttribute(attribute); string_attributes.emplace_back(attribute, value); } void AXNodeData::AddIntListAttribute(ax::mojom::IntListAttribute attribute, const std::vector& value) { DCHECK_NE(attribute, ax::mojom::IntListAttribute::kNone); if (HasIntListAttribute(attribute)) RemoveIntListAttribute(attribute); intlist_attributes.emplace_back(attribute, value); } void AXNodeData::AddStringListAttribute( ax::mojom::StringListAttribute attribute, const std::vector& value) { DCHECK_NE(attribute, ax::mojom::StringListAttribute::kNone); if (HasStringListAttribute(attribute)) RemoveStringListAttribute(attribute); stringlist_attributes.emplace_back(attribute, value); } void AXNodeData::RemoveBoolAttribute(ax::mojom::BoolAttribute attribute) { DCHECK_NE(attribute, ax::mojom::BoolAttribute::kNone); base::EraseIf(bool_attributes, [attribute](const auto& bool_attribute) { return bool_attribute.first == attribute; }); } void AXNodeData::RemoveIntAttribute(ax::mojom::IntAttribute attribute) { DCHECK_NE(attribute, ax::mojom::IntAttribute::kNone); base::EraseIf(int_attributes, [attribute](const auto& int_attribute) { return int_attribute.first == attribute; }); } void AXNodeData::RemoveFloatAttribute(ax::mojom::FloatAttribute attribute) { DCHECK_NE(attribute, ax::mojom::FloatAttribute::kNone); base::EraseIf(float_attributes, [attribute](const auto& float_attribute) { return float_attribute.first == attribute; }); } void AXNodeData::RemoveStringAttribute(ax::mojom::StringAttribute attribute) { DCHECK_NE(attribute, ax::mojom::StringAttribute::kNone); base::EraseIf(string_attributes, [attribute](const auto& string_attribute) { return string_attribute.first == attribute; }); } void AXNodeData::RemoveIntListAttribute(ax::mojom::IntListAttribute attribute) { DCHECK_NE(attribute, ax::mojom::IntListAttribute::kNone); base::EraseIf(intlist_attributes, [attribute](const auto& intlist_attribute) { return intlist_attribute.first == attribute; }); } void AXNodeData::RemoveStringListAttribute( ax::mojom::StringListAttribute attribute) { DCHECK_NE(attribute, ax::mojom::StringListAttribute::kNone); base::EraseIf(stringlist_attributes, [attribute](const auto& stringlist_attribute) { return stringlist_attribute.first == attribute; }); } AXTextAttributes AXNodeData::GetTextAttributes() const { AXTextAttributes text_attributes; // This overload of `GetIntAttribute` does not set the return value to 0 if // the attribute is not present, hence maintaining the corresponding member in // `AXTextAttributes` as `AXTextAttributes::kUnsetValue`. GetIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, &text_attributes.background_color); GetIntAttribute(ax::mojom::IntAttribute::kColor, &text_attributes.color); GetIntAttribute(ax::mojom::IntAttribute::kInvalidState, &text_attributes.invalid_state); GetIntAttribute(ax::mojom::IntAttribute::kTextOverlineStyle, &text_attributes.overline_style); GetIntAttribute(ax::mojom::IntAttribute::kTextDirection, &text_attributes.text_direction); GetIntAttribute(ax::mojom::IntAttribute::kTextPosition, &text_attributes.text_position); GetIntAttribute(ax::mojom::IntAttribute::kTextStrikethroughStyle, &text_attributes.strikethrough_style); GetIntAttribute(ax::mojom::IntAttribute::kTextStyle, &text_attributes.text_style); GetIntAttribute(ax::mojom::IntAttribute::kTextUnderlineStyle, &text_attributes.underline_style); GetFloatAttribute(ax::mojom::FloatAttribute::kFontSize, &text_attributes.font_size); GetFloatAttribute(ax::mojom::FloatAttribute::kFontWeight, &text_attributes.font_weight); GetStringAttribute(ax::mojom::StringAttribute::kFontFamily, &text_attributes.font_family); return text_attributes; } void AXNodeData::SetName(const std::string& name) { DCHECK_NE(role, ax::mojom::Role::kNone) << "A valid role is required before setting the name attribute, because " "the role is used for setting the required NameFrom attribute."; auto iter = std::find_if(string_attributes.begin(), string_attributes.end(), [](const auto& string_attribute) { return string_attribute.first == ax::mojom::StringAttribute::kName; }); if (iter == string_attributes.end()) { string_attributes.emplace_back(ax::mojom::StringAttribute::kName, name); } else { iter->second = name; } if (HasIntAttribute(ax::mojom::IntAttribute::kNameFrom)) return; // Since this method is mostly used by tests which don't always set the // "NameFrom" attribute, we need to set it here to the most likely value if // not set, otherwise code that tries to calculate the node's inner text, its // hypertext, or even its value, might not know whether to include the name in // the result or not. // // For example, if there is a text field, but it is empty, i.e. it has no // value, its value could be its name if "NameFrom" is set to "kPlaceholder" // or to "kContents" but not if it's set to "kAttribute". Similarly, if there // is a button without any unignored children, it's name can only be // equivalent to its inner text if "NameFrom" is set to "kContents" or to // "kValue", but not if it is set to "kAttribute". if (IsText(role)) { SetNameFrom(ax::mojom::NameFrom::kContents); } else { SetNameFrom(ax::mojom::NameFrom::kAttribute); } } void AXNodeData::SetName(const std::u16string& name) { SetName(base::UTF16ToUTF8(name)); } void AXNodeData::SetNameExplicitlyEmpty() { SetNameFrom(ax::mojom::NameFrom::kAttributeExplicitlyEmpty); } void AXNodeData::SetDescription(const std::string& description) { AddStringAttribute(ax::mojom::StringAttribute::kDescription, description); } void AXNodeData::SetDescription(const std::u16string& description) { SetDescription(base::UTF16ToUTF8(description)); } void AXNodeData::SetValue(const std::string& value) { AddStringAttribute(ax::mojom::StringAttribute::kValue, value); } void AXNodeData::SetValue(const std::u16string& value) { SetValue(base::UTF16ToUTF8(value)); } bool AXNodeData::HasState(ax::mojom::State state_enum) const { return IsFlagSet(state, static_cast(state_enum)); } bool AXNodeData::HasAction(ax::mojom::Action action) const { return IsFlagSet(actions, static_cast(action)); } bool AXNodeData::HasTextStyle(ax::mojom::TextStyle text_style_enum) const { int32_t style = GetIntAttribute(ax::mojom::IntAttribute::kTextStyle); return IsFlagSet(static_cast(style), static_cast(text_style_enum)); } bool AXNodeData::HasDropeffect(ax::mojom::Dropeffect dropeffect_enum) const { int32_t dropeffect = GetIntAttribute(ax::mojom::IntAttribute::kDropeffect); return IsFlagSet(static_cast(dropeffect), static_cast(dropeffect_enum)); } void AXNodeData::AddState(ax::mojom::State state_enum) { DCHECK_GT(static_cast(state_enum), static_cast(ax::mojom::State::kNone)); DCHECK_LE(static_cast(state_enum), static_cast(ax::mojom::State::kMaxValue)); state = ModifyFlag(state, static_cast(state_enum), true); } void AXNodeData::RemoveState(ax::mojom::State state_enum) { DCHECK_GT(static_cast(state_enum), static_cast(ax::mojom::State::kNone)); DCHECK_LE(static_cast(state_enum), static_cast(ax::mojom::State::kMaxValue)); state = ModifyFlag(state, static_cast(state_enum), false); } void AXNodeData::AddAction(ax::mojom::Action action_enum) { switch (action_enum) { case ax::mojom::Action::kNone: NOTREACHED(); break; // Note: all of the attributes are included here explicitly, rather than // using "default:", so that it's a compiler error to add a new action // without explicitly considering whether there are mutually exclusive // actions that can be performed on a UI control at the same time. case ax::mojom::Action::kBlur: case ax::mojom::Action::kFocus: { ax::mojom::Action excluded_action = (action_enum == ax::mojom::Action::kBlur) ? ax::mojom::Action::kFocus : ax::mojom::Action::kBlur; DCHECK(!HasAction(excluded_action)) << excluded_action; break; } case ax::mojom::Action::kClearAccessibilityFocus: case ax::mojom::Action::kCollapse: case ax::mojom::Action::kCustomAction: case ax::mojom::Action::kDecrement: case ax::mojom::Action::kDoDefault: case ax::mojom::Action::kExpand: case ax::mojom::Action::kGetImageData: case ax::mojom::Action::kHitTest: case ax::mojom::Action::kIncrement: case ax::mojom::Action::kInternalInvalidateTree: case ax::mojom::Action::kLoadInlineTextBoxes: case ax::mojom::Action::kReplaceSelectedText: case ax::mojom::Action::kScrollToMakeVisible: case ax::mojom::Action::kScrollToPoint: case ax::mojom::Action::kSetAccessibilityFocus: case ax::mojom::Action::kSetScrollOffset: case ax::mojom::Action::kSetSelection: case ax::mojom::Action::kSetSequentialFocusNavigationStartingPoint: case ax::mojom::Action::kSetValue: case ax::mojom::Action::kShowContextMenu: case ax::mojom::Action::kScrollBackward: case ax::mojom::Action::kScrollForward: case ax::mojom::Action::kScrollUp: case ax::mojom::Action::kScrollDown: case ax::mojom::Action::kScrollLeft: case ax::mojom::Action::kScrollRight: case ax::mojom::Action::kGetTextLocation: case ax::mojom::Action::kAnnotatePageImages: case ax::mojom::Action::kSignalEndOfTest: case ax::mojom::Action::kHideTooltip: case ax::mojom::Action::kShowTooltip: case ax::mojom::Action::kResumeMedia: case ax::mojom::Action::kStartDuckingMedia: case ax::mojom::Action::kStopDuckingMedia: case ax::mojom::Action::kSuspendMedia: break; } actions = ModifyFlag(actions, static_cast(action_enum), true); } void AXNodeData::AddTextStyle(ax::mojom::TextStyle text_style_enum) { DCHECK_GE(static_cast(text_style_enum), static_cast(ax::mojom::TextStyle::kMinValue)); DCHECK_LE(static_cast(text_style_enum), static_cast(ax::mojom::TextStyle::kMaxValue)); int32_t style = GetIntAttribute(ax::mojom::IntAttribute::kTextStyle); style = ModifyFlag(static_cast(style), static_cast(text_style_enum), true); RemoveIntAttribute(ax::mojom::IntAttribute::kTextStyle); AddIntAttribute(ax::mojom::IntAttribute::kTextStyle, style); } void AXNodeData::AddDropeffect(ax::mojom::Dropeffect dropeffect_enum) { DCHECK_GE(static_cast(dropeffect_enum), static_cast(ax::mojom::Dropeffect::kMinValue)); DCHECK_LE(static_cast(dropeffect_enum), static_cast(ax::mojom::Dropeffect::kMaxValue)); int32_t dropeffect = GetIntAttribute(ax::mojom::IntAttribute::kDropeffect); dropeffect = ModifyFlag(static_cast(dropeffect), static_cast(dropeffect_enum), true); RemoveIntAttribute(ax::mojom::IntAttribute::kDropeffect); AddIntAttribute(ax::mojom::IntAttribute::kDropeffect, dropeffect); } ax::mojom::CheckedState AXNodeData::GetCheckedState() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kCheckedState)); } void AXNodeData::SetCheckedState(ax::mojom::CheckedState checked_state) { if (HasCheckedState()) RemoveIntAttribute(ax::mojom::IntAttribute::kCheckedState); if (checked_state != ax::mojom::CheckedState::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kCheckedState, static_cast(checked_state)); } } bool AXNodeData::HasCheckedState() const { return HasIntAttribute(ax::mojom::IntAttribute::kCheckedState); } ax::mojom::DefaultActionVerb AXNodeData::GetDefaultActionVerb() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb)); } void AXNodeData::SetDefaultActionVerb( ax::mojom::DefaultActionVerb default_action_verb) { if (HasIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb)) RemoveIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb); if (default_action_verb != ax::mojom::DefaultActionVerb::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb, static_cast(default_action_verb)); } } ax::mojom::HasPopup AXNodeData::GetHasPopup() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kHasPopup)); } void AXNodeData::SetHasPopup(ax::mojom::HasPopup has_popup) { if (HasIntAttribute(ax::mojom::IntAttribute::kHasPopup)) RemoveIntAttribute(ax::mojom::IntAttribute::kHasPopup); if (has_popup != ax::mojom::HasPopup::kFalse) { AddIntAttribute(ax::mojom::IntAttribute::kHasPopup, static_cast(has_popup)); } } ax::mojom::InvalidState AXNodeData::GetInvalidState() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kInvalidState)); } void AXNodeData::SetInvalidState(ax::mojom::InvalidState invalid_state) { if (HasIntAttribute(ax::mojom::IntAttribute::kInvalidState)) RemoveIntAttribute(ax::mojom::IntAttribute::kInvalidState); if (invalid_state != ax::mojom::InvalidState::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kInvalidState, static_cast(invalid_state)); } } ax::mojom::NameFrom AXNodeData::GetNameFrom() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kNameFrom)); } void AXNodeData::SetNameFrom(ax::mojom::NameFrom name_from) { if (HasIntAttribute(ax::mojom::IntAttribute::kNameFrom)) RemoveIntAttribute(ax::mojom::IntAttribute::kNameFrom); if (name_from != ax::mojom::NameFrom::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kNameFrom, static_cast(name_from)); } } ax::mojom::DescriptionFrom AXNodeData::GetDescriptionFrom() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom)); } void AXNodeData::SetDescriptionFrom( ax::mojom::DescriptionFrom description_from) { if (HasIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom)) RemoveIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom); if (description_from != ax::mojom::DescriptionFrom::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom, static_cast(description_from)); } } ax::mojom::TextPosition AXNodeData::GetTextPosition() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kTextPosition)); } void AXNodeData::SetTextPosition(ax::mojom::TextPosition text_position) { if (HasIntAttribute(ax::mojom::IntAttribute::kTextPosition)) RemoveIntAttribute(ax::mojom::IntAttribute::kTextPosition); if (text_position != ax::mojom::TextPosition::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kTextPosition, static_cast(text_position)); } } ax::mojom::ImageAnnotationStatus AXNodeData::GetImageAnnotationStatus() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus)); } void AXNodeData::SetImageAnnotationStatus( ax::mojom::ImageAnnotationStatus status) { if (HasIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus)) RemoveIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus); if (status != ax::mojom::ImageAnnotationStatus::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kImageAnnotationStatus, static_cast(status)); } } ax::mojom::Restriction AXNodeData::GetRestriction() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kRestriction)); } void AXNodeData::SetRestriction(ax::mojom::Restriction restriction) { if (HasIntAttribute(ax::mojom::IntAttribute::kRestriction)) RemoveIntAttribute(ax::mojom::IntAttribute::kRestriction); if (restriction != ax::mojom::Restriction::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kRestriction, static_cast(restriction)); } } ax::mojom::ListStyle AXNodeData::GetListStyle() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kListStyle)); } void AXNodeData::SetListStyle(ax::mojom::ListStyle list_style) { if (HasIntAttribute(ax::mojom::IntAttribute::kListStyle)) RemoveIntAttribute(ax::mojom::IntAttribute::kListStyle); if (list_style != ax::mojom::ListStyle::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kListStyle, static_cast(list_style)); } } ax::mojom::TextAlign AXNodeData::GetTextAlign() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kTextAlign)); } void AXNodeData::SetTextAlign(ax::mojom::TextAlign text_align) { if (HasIntAttribute(ax::mojom::IntAttribute::kTextAlign)) RemoveIntAttribute(ax::mojom::IntAttribute::kTextAlign); AddIntAttribute(ax::mojom::IntAttribute::kTextAlign, static_cast(text_align)); } ax::mojom::WritingDirection AXNodeData::GetTextDirection() const { return static_cast( GetIntAttribute(ax::mojom::IntAttribute::kTextDirection)); } void AXNodeData::SetTextDirection(ax::mojom::WritingDirection text_direction) { if (HasIntAttribute(ax::mojom::IntAttribute::kTextDirection)) RemoveIntAttribute(ax::mojom::IntAttribute::kTextDirection); if (text_direction != ax::mojom::WritingDirection::kNone) { AddIntAttribute(ax::mojom::IntAttribute::kTextDirection, static_cast(text_direction)); } } bool AXNodeData::IsActivatable() const { return IsTextField() || role == ax::mojom::Role::kListBox; } bool AXNodeData::IsActiveLiveRegionRoot() const { std::string aria_live_status; if (GetStringAttribute(ax::mojom::StringAttribute::kLiveStatus, &aria_live_status)) { return aria_live_status != "off"; } return false; } bool AXNodeData::IsButtonPressed() const { // Currently there is no internal representation for |aria-pressed|, and // we map |aria-pressed="true"| to ax::mojom::CheckedState::kTrue for a native // button or role="button". // https://www.w3.org/TR/wai-aria-1.1/#aria-pressed if (IsButton(role) && GetCheckedState() == ax::mojom::CheckedState::kTrue) return true; return false; } bool AXNodeData::IsClickable() const { // If it has a custom default action verb except for // ax::mojom::DefaultActionVerb::kClickAncestor, it's definitely clickable. // ax::mojom::DefaultActionVerb::kClickAncestor is used when an element with a // click listener is present in its ancestry chain. if (HasIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb) && (GetDefaultActionVerb() != ax::mojom::DefaultActionVerb::kClickAncestor)) return true; return ui::IsClickable(role); } bool AXNodeData::IsContainedInActiveLiveRegion() const { std::string aria_container_live_status; if (GetStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus, &aria_container_live_status)) { return aria_container_live_status != "off" && HasStringAttribute(ax::mojom::StringAttribute::kName); } return false; } bool AXNodeData::IsSelectable() const { // It's selectable if it has the attribute, whether it's true or false. return HasBoolAttribute(ax::mojom::BoolAttribute::kSelected) && GetRestriction() != ax::mojom::Restriction::kDisabled; } bool AXNodeData::IsIgnored() const { return HasState(ax::mojom::State::kIgnored) || role == ax::mojom::Role::kNone; } bool AXNodeData::IsInvisible() const { return HasState(ax::mojom::State::kInvisible); } bool AXNodeData::IsInvisibleOrIgnored() const { return IsIgnored() || IsInvisible(); } bool AXNodeData::IsInvocable() const { // A control is "invocable" if it initiates an action when activated but // does not maintain any state. A control that maintains state when activated // would be considered a toggle or expand-collapse element - these elements // are "clickable" but not "invocable". Similarly, if the action only involves // activating the control, such as when clicking a text field, the control is // not considered "invocable". return IsClickable() && !IsActivatable() && !SupportsExpandCollapse() && !SupportsToggle(role); } bool AXNodeData::IsMenuButton() const { // According to the WAI-ARIA spec, a menu button is a native button or an ARIA // role="button" that opens a menu. Although ARIA does not include a role // specifically for menu buttons, screen readers identify buttons that have // aria-haspopup="true" or aria-haspopup="menu" as menu buttons, and Blink // maps both to HasPopup::kMenu. // https://www.w3.org/TR/wai-aria-practices/#menubutton // https://www.w3.org/TR/wai-aria-1.1/#aria-haspopup if (IsButton(role) && GetHasPopup() == ax::mojom::HasPopup::kMenu) return true; return false; } bool AXNodeData::IsTextField() const { return IsAtomicTextField() || IsNonAtomicTextField(); } bool AXNodeData::IsPasswordField() const { return IsTextField() && HasState(ax::mojom::State::kProtected); } bool AXNodeData::IsAtomicTextField() const { // The ARIA spec suggests a textbox is a simple text field, like an or //