| // Copyright (c) 2015 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 "content/browser/accessibility/accessibility_tree_formatter_blink.h" |
| |
| #include <cmath> |
| #include <cstddef> |
| |
| #include <utility> |
| |
| #include "base/optional.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 "base/values.h" |
| #include "content/browser/accessibility/browser_accessibility.h" |
| #include "content/browser/accessibility/browser_accessibility_manager.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/accessibility/ax_tree_manager_map.h" |
| #include "ui/accessibility/platform/ax_platform_node_delegate.h" |
| #include "ui/accessibility/platform/compute_attributes.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| #include "ui/gfx/transform.h" |
| |
| namespace content { |
| namespace { |
| |
| base::Optional<std::string> GetStringAttribute( |
| const ui::AXNode& node, |
| ax::mojom::StringAttribute attr) { |
| // Language is different from other string attributes as it inherits and has |
| // a method to compute it. |
| if (attr == ax::mojom::StringAttribute::kLanguage) { |
| std::string value = node.GetLanguage(); |
| if (value.empty()) { |
| return base::nullopt; |
| } |
| return value; |
| } |
| |
| // Font Family is different from other string attributes as it inherits. |
| if (attr == ax::mojom::StringAttribute::kFontFamily) { |
| std::string value = node.GetInheritedStringAttribute(attr); |
| if (value.empty()) { |
| return base::nullopt; |
| } |
| return value; |
| } |
| |
| // Always return the attribute if the node has it, even if the value is an |
| // empty string. |
| std::string value; |
| if (node.GetStringAttribute(attr, &value)) { |
| return value; |
| } |
| return base::nullopt; |
| } |
| |
| std::string FormatColor(int argb) { |
| // Don't output the alpha component; only the red, green and blue |
| // actually matter. |
| int rgb = (static_cast<uint32_t>(argb) & 0xffffff); |
| return base::StringPrintf("%06x", rgb); |
| } |
| |
| std::string IntAttrToString(const ui::AXNode& node, |
| ax::mojom::IntAttribute attr, |
| int32_t value) { |
| if (ui::IsNodeIdIntAttribute(attr)) { |
| // Relation |
| ui::AXTreeID tree_id = node.tree()->GetAXTreeID(); |
| ui::AXNode* target = ui::AXTreeManagerMap::GetInstance() |
| .GetManager(tree_id) |
| ->GetNodeFromTree(tree_id, value); |
| if (!target) |
| return "null"; |
| |
| std::string result = ui::ToString(target->data().role); |
| // Provide some extra info about the related object via the name or |
| // possibly the class (if an element). |
| // TODO(accessibility) Include all relational attributes here. |
| // TODO(accessibility) Consider using line numbers from the results instead. |
| if (attr == ax::mojom::IntAttribute::kNextOnLineId || |
| attr == ax::mojom::IntAttribute::kPreviousOnLineId) { |
| if (target->data().HasStringAttribute( |
| ax::mojom::StringAttribute::kName)) { |
| result += ":\""; |
| result += target->data().GetStringAttribute( |
| ax::mojom::StringAttribute::kName); |
| result += "\""; |
| } else if (target->data().HasStringAttribute( |
| ax::mojom::StringAttribute::kClassName)) { |
| result += "."; |
| result += target->data().GetStringAttribute( |
| ax::mojom::StringAttribute::kClassName); |
| } |
| } |
| return result; |
| } |
| |
| switch (attr) { |
| case ax::mojom::IntAttribute::kAriaCurrentState: |
| return ui::ToString(static_cast<ax::mojom::AriaCurrentState>(value)); |
| case ax::mojom::IntAttribute::kCheckedState: |
| return ui::ToString(static_cast<ax::mojom::CheckedState>(value)); |
| case ax::mojom::IntAttribute::kDefaultActionVerb: |
| return ui::ToString(static_cast<ax::mojom::DefaultActionVerb>(value)); |
| case ax::mojom::IntAttribute::kDescriptionFrom: |
| return ui::ToString(static_cast<ax::mojom::DescriptionFrom>(value)); |
| case ax::mojom::IntAttribute::kDropeffect: |
| return node.data().DropeffectBitfieldToString(); |
| case ax::mojom::IntAttribute::kHasPopup: |
| return ui::ToString(static_cast<ax::mojom::HasPopup>(value)); |
| case ax::mojom::IntAttribute::kInvalidState: |
| return ui::ToString(static_cast<ax::mojom::InvalidState>(value)); |
| case ax::mojom::IntAttribute::kListStyle: |
| return ui::ToString(static_cast<ax::mojom::ListStyle>(value)); |
| case ax::mojom::IntAttribute::kNameFrom: |
| return ui::ToString(static_cast<ax::mojom::NameFrom>(value)); |
| case ax::mojom::IntAttribute::kRestriction: |
| return ui::ToString(static_cast<ax::mojom::Restriction>(value)); |
| case ax::mojom::IntAttribute::kSortDirection: |
| return ui::ToString(static_cast<ax::mojom::SortDirection>(value)); |
| case ax::mojom::IntAttribute::kTextAlign: |
| return ui::ToString(static_cast<ax::mojom::TextAlign>(value)); |
| case ax::mojom::IntAttribute::kTextOverlineStyle: |
| case ax::mojom::IntAttribute::kTextStrikethroughStyle: |
| case ax::mojom::IntAttribute::kTextUnderlineStyle: |
| return ui::ToString(static_cast<ax::mojom::TextDecorationStyle>(value)); |
| case ax::mojom::IntAttribute::kTextDirection: |
| return ui::ToString(static_cast<ax::mojom::WritingDirection>(value)); |
| case ax::mojom::IntAttribute::kTextPosition: |
| return ui::ToString(static_cast<ax::mojom::TextPosition>(value)); |
| case ax::mojom::IntAttribute::kImageAnnotationStatus: |
| return ui::ToString(static_cast<ax::mojom::ImageAnnotationStatus>(value)); |
| case ax::mojom::IntAttribute::kBackgroundColor: |
| return FormatColor(node.ComputeBackgroundColor()); |
| case ax::mojom::IntAttribute::kColor: |
| return FormatColor(node.ComputeColor()); |
| // No pretty printing necessary for these: |
| case ax::mojom::IntAttribute::kActivedescendantId: |
| case ax::mojom::IntAttribute::kAriaCellColumnIndex: |
| case ax::mojom::IntAttribute::kAriaCellRowIndex: |
| case ax::mojom::IntAttribute::kAriaColumnCount: |
| case ax::mojom::IntAttribute::kAriaCellColumnSpan: |
| case ax::mojom::IntAttribute::kAriaCellRowSpan: |
| case ax::mojom::IntAttribute::kAriaRowCount: |
| case ax::mojom::IntAttribute::kColorValue: |
| case ax::mojom::IntAttribute::kDOMNodeId: |
| case ax::mojom::IntAttribute::kErrormessageId: |
| case ax::mojom::IntAttribute::kHierarchicalLevel: |
| case ax::mojom::IntAttribute::kInPageLinkTargetId: |
| case ax::mojom::IntAttribute::kMemberOfId: |
| case ax::mojom::IntAttribute::kNextFocusId: |
| case ax::mojom::IntAttribute::kNextOnLineId: |
| case ax::mojom::IntAttribute::kPosInSet: |
| case ax::mojom::IntAttribute::kPopupForId: |
| case ax::mojom::IntAttribute::kPreviousFocusId: |
| case ax::mojom::IntAttribute::kPreviousOnLineId: |
| case ax::mojom::IntAttribute::kScrollX: |
| case ax::mojom::IntAttribute::kScrollXMax: |
| case ax::mojom::IntAttribute::kScrollXMin: |
| case ax::mojom::IntAttribute::kScrollY: |
| case ax::mojom::IntAttribute::kScrollYMax: |
| case ax::mojom::IntAttribute::kScrollYMin: |
| case ax::mojom::IntAttribute::kSetSize: |
| case ax::mojom::IntAttribute::kTableCellColumnIndex: |
| case ax::mojom::IntAttribute::kTableCellColumnSpan: |
| case ax::mojom::IntAttribute::kTableCellRowIndex: |
| case ax::mojom::IntAttribute::kTableCellRowSpan: |
| case ax::mojom::IntAttribute::kTableColumnCount: |
| case ax::mojom::IntAttribute::kTableColumnHeaderId: |
| case ax::mojom::IntAttribute::kTableColumnIndex: |
| case ax::mojom::IntAttribute::kTableHeaderId: |
| case ax::mojom::IntAttribute::kTableRowCount: |
| case ax::mojom::IntAttribute::kTableRowHeaderId: |
| case ax::mojom::IntAttribute::kTableRowIndex: |
| case ax::mojom::IntAttribute::kTextSelEnd: |
| case ax::mojom::IntAttribute::kTextSelStart: |
| case ax::mojom::IntAttribute::kTextStyle: |
| case ax::mojom::IntAttribute::kNone: |
| break; |
| } |
| |
| // Just return the number |
| return std::to_string(value); |
| } |
| |
| } // namespace |
| |
| AccessibilityTreeFormatterBlink::AccessibilityTreeFormatterBlink() = default; |
| AccessibilityTreeFormatterBlink::~AccessibilityTreeFormatterBlink() = default; |
| |
| void AccessibilityTreeFormatterBlink::AddDefaultFilters( |
| std::vector<AXPropertyFilter>* property_filters) { |
| // Noisy, perhaps add later: |
| // editable, focus*, horizontal, linked, richlyEditable, vertical |
| // Too flaky: hovered, offscreen |
| // States |
| AddPropertyFilter(property_filters, "collapsed"); |
| AddPropertyFilter(property_filters, "invisible"); |
| AddPropertyFilter(property_filters, "multiline"); |
| AddPropertyFilter(property_filters, "protected"); |
| AddPropertyFilter(property_filters, "required"); |
| AddPropertyFilter(property_filters, "select*"); |
| AddPropertyFilter(property_filters, "selectedFromFocus=*", |
| AXPropertyFilter::DENY); |
| AddPropertyFilter(property_filters, "visited"); |
| // Other attributes |
| AddPropertyFilter(property_filters, "busy=true"); |
| AddPropertyFilter(property_filters, "valueForRange*"); |
| AddPropertyFilter(property_filters, "minValueForRange*"); |
| AddPropertyFilter(property_filters, "maxValueForRange*"); |
| AddPropertyFilter(property_filters, "autoComplete*"); |
| AddPropertyFilter(property_filters, "restriction*"); |
| AddPropertyFilter(property_filters, "keyShortcuts*"); |
| AddPropertyFilter(property_filters, "activedescendantId*"); |
| AddPropertyFilter(property_filters, "controlsIds*"); |
| AddPropertyFilter(property_filters, "flowtoIds*"); |
| AddPropertyFilter(property_filters, "detailsIds*"); |
| AddPropertyFilter(property_filters, "invalidState=*"); |
| AddPropertyFilter(property_filters, "ignored*"); |
| AddPropertyFilter(property_filters, "invalidState=false", |
| AXPropertyFilter::DENY); // Don't show false value |
| AddPropertyFilter(property_filters, "roleDescription=*"); |
| AddPropertyFilter(property_filters, "errormessageId=*"); |
| AddPropertyFilter(property_filters, "virtualContent=*"); |
| } |
| |
| const char* const TREE_DATA_ATTRIBUTES[] = {"TreeData.textSelStartOffset", |
| "TreeData.textSelEndOffset"}; |
| |
| const char* STATE_FOCUSED = "focused"; |
| const char* STATE_OFFSCREEN = "offscreen"; |
| |
| base::Value AccessibilityTreeFormatterBlink::BuildTree( |
| ui::AXPlatformNodeDelegate* root) const { |
| CHECK(root); |
| BrowserAccessibility* root_internal = |
| BrowserAccessibility::FromAXPlatformNodeDelegate(root); |
| base::Value dict(base::Value::Type::DICTIONARY); |
| RecursiveBuildTree(*root_internal, &dict); |
| return dict; |
| } |
| base::Value AccessibilityTreeFormatterBlink::BuildTreeForWindow( |
| gfx::AcceleratedWidget widget) const { |
| NOTREACHED(); |
| return base::Value(base::Value::Type::DICTIONARY); |
| } |
| |
| base::Value AccessibilityTreeFormatterBlink::BuildTreeForSelector( |
| const AXTreeSelector& selector) const { |
| NOTREACHED(); |
| return base::Value(base::Value::Type::DICTIONARY); |
| } |
| |
| base::Value AccessibilityTreeFormatterBlink::BuildTreeForNode( |
| ui::AXNode* node) const { |
| CHECK(node); |
| base::Value dict(base::Value::Type::DICTIONARY); |
| RecursiveBuildTree(*node, &dict); |
| return dict; |
| } |
| |
| base::Value AccessibilityTreeFormatterBlink::BuildNode( |
| ui::AXPlatformNodeDelegate* node) const { |
| CHECK(node); |
| base::DictionaryValue dict; |
| AddProperties(*BrowserAccessibility::FromAXPlatformNodeDelegate(node), &dict); |
| return std::move(dict); |
| } |
| |
| std::string AccessibilityTreeFormatterBlink::DumpInternalAccessibilityTree( |
| ui::AXTreeID tree_id, |
| const std::vector<AXPropertyFilter>& property_filters) { |
| ui::AXTreeManager* ax_mgr = |
| ui::AXTreeManagerMap::GetInstance().GetManager(tree_id); |
| DCHECK(ax_mgr); |
| SetPropertyFilters(property_filters, kFiltersDefaultSet); |
| base::Value dict = BuildTreeForNode(ax_mgr->GetRootAsAXNode()); |
| return FormatTree(dict); |
| } |
| |
| void AccessibilityTreeFormatterBlink::RecursiveBuildTree( |
| const BrowserAccessibility& node, |
| base::Value* dict) const { |
| AddProperties(node, static_cast<base::DictionaryValue*>(dict)); |
| |
| base::Value children(base::Value::Type::LIST); |
| for (size_t i = 0; i < ChildCount(node); ++i) { |
| BrowserAccessibility* child_node = GetChild(node, i); |
| base::Value child_dict(base::Value::Type::DICTIONARY); |
| RecursiveBuildTree(*child_node, &child_dict); |
| children.Append(std::move(child_dict)); |
| } |
| dict->SetKey(kChildrenDictAttr, std::move(children)); |
| } |
| |
| void AccessibilityTreeFormatterBlink::RecursiveBuildTree( |
| const ui::AXNode& node, |
| base::Value* dict) const { |
| AddProperties(node, static_cast<base::DictionaryValue*>(dict)); |
| |
| base::Value children(base::Value::Type::LIST); |
| for (ui::AXNode* child_node : node.children()) { |
| base::Value child_dict(base::Value::Type::DICTIONARY); |
| RecursiveBuildTree(*child_node, &child_dict); |
| children.Append(std::move(child_dict)); |
| } |
| dict->SetKey(kChildrenDictAttr, std::move(children)); |
| } |
| |
| uint32_t AccessibilityTreeFormatterBlink::ChildCount( |
| const BrowserAccessibility& node) const { |
| if (node.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) |
| return node.PlatformChildCount(); |
| // We don't want to use InternalGetChild as we want to include |
| // ignored nodes in the tree for tests. |
| return node.node()->children().size(); |
| } |
| |
| BrowserAccessibility* AccessibilityTreeFormatterBlink::GetChild( |
| const BrowserAccessibility& node, |
| uint32_t i) const { |
| if (node.HasStringAttribute(ax::mojom::StringAttribute::kChildTreeId)) |
| return node.PlatformGetChild(i); |
| // We don't want to use InternalGetChild as we want to include |
| // ignored nodes in the tree for tests. |
| if (i < 0 && i >= node.node()->children().size()) |
| return nullptr; |
| ui::AXNode* child_node = node.node()->children()[i]; |
| DCHECK(child_node); |
| return node.manager()->GetFromAXNode(child_node); |
| } |
| |
| void AccessibilityTreeFormatterBlink::AddProperties( |
| const BrowserAccessibility& node, |
| base::DictionaryValue* dict) const { |
| int id = node.GetId(); |
| dict->SetInteger("id", id); |
| |
| dict->SetString("internalRole", ui::ToString(node.GetData().role)); |
| |
| gfx::Rect bounds = |
| gfx::ToEnclosingRect(node.GetData().relative_bounds.bounds); |
| dict->SetInteger("boundsX", bounds.x()); |
| dict->SetInteger("boundsY", bounds.y()); |
| dict->SetInteger("boundsWidth", bounds.width()); |
| dict->SetInteger("boundsHeight", bounds.height()); |
| |
| ui::AXOffscreenResult offscreen_result = ui::AXOffscreenResult::kOnscreen; |
| gfx::Rect page_bounds = node.GetClippedRootFrameBoundsRect(&offscreen_result); |
| dict->SetInteger("pageBoundsX", page_bounds.x()); |
| dict->SetInteger("pageBoundsY", page_bounds.y()); |
| dict->SetInteger("pageBoundsWidth", page_bounds.width()); |
| dict->SetInteger("pageBoundsHeight", page_bounds.height()); |
| |
| dict->SetBoolean("transform", |
| node.GetData().relative_bounds.transform && |
| !node.GetData().relative_bounds.transform->IsIdentity()); |
| |
| gfx::Rect unclipped_bounds = |
| node.GetUnclippedRootFrameBoundsRect(&offscreen_result); |
| dict->SetInteger("unclippedBoundsX", unclipped_bounds.x()); |
| dict->SetInteger("unclippedBoundsY", unclipped_bounds.y()); |
| dict->SetInteger("unclippedBoundsWidth", unclipped_bounds.width()); |
| dict->SetInteger("unclippedBoundsHeight", unclipped_bounds.height()); |
| |
| for (int32_t state_index = static_cast<int32_t>(ax::mojom::State::kNone); |
| state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue); |
| ++state_index) { |
| auto state = static_cast<ax::mojom::State>(state_index); |
| if (node.HasState(state)) |
| dict->SetBoolean(ui::ToString(state), true); |
| } |
| |
| if (offscreen_result == ui::AXOffscreenResult::kOffscreen) |
| dict->SetBoolean(STATE_OFFSCREEN, true); |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::StringAttribute::kNone); |
| attr_index <= |
| static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::StringAttribute>(attr_index); |
| auto maybe_value = GetStringAttribute(*node.node(), attr); |
| if (maybe_value.has_value()) |
| dict->SetString(ui::ToString(attr), maybe_value.value()); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::IntAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::IntAttribute>(attr_index); |
| auto maybe_value = ui::ComputeAttribute(&node, attr); |
| if (maybe_value.has_value()) { |
| dict->SetString(ui::ToString(attr), |
| IntAttrToString(*node.node(), attr, maybe_value.value())); |
| } |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::FloatAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index); |
| if (node.HasFloatAttribute(attr) && |
| std::isfinite(node.GetFloatAttribute(attr))) |
| dict->SetDouble(ui::ToString(attr), node.GetFloatAttribute(attr)); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::BoolAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index); |
| if (node.HasBoolAttribute(attr)) |
| dict->SetBoolean(ui::ToString(attr), node.GetBoolAttribute(attr)); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::IntListAttribute::kNone); |
| attr_index <= |
| static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index); |
| if (node.HasIntListAttribute(attr)) { |
| std::vector<int32_t> values; |
| node.GetIntListAttribute(attr, &values); |
| auto value_list = std::make_unique<base::ListValue>(); |
| for (size_t i = 0; i < values.size(); ++i) { |
| if (ui::IsNodeIdIntListAttribute(attr)) { |
| BrowserAccessibility* target = node.manager()->GetFromID(values[i]); |
| if (target) |
| value_list->AppendString(ui::ToString(target->GetData().role)); |
| else |
| value_list->AppendString("null"); |
| } else { |
| value_list->AppendInteger(values[i]); |
| } |
| } |
| dict->Set(ui::ToString(attr), std::move(value_list)); |
| } |
| } |
| |
| // Check for relevant rich text selection info in AXTreeData |
| ui::AXTree::Selection unignored_selection = |
| node.manager()->ax_tree()->GetUnignoredSelection(); |
| int anchor_id = unignored_selection.anchor_object_id; |
| if (id == anchor_id) { |
| int anchor_offset = unignored_selection.anchor_offset; |
| dict->SetInteger("TreeData.textSelStartOffset", anchor_offset); |
| } |
| int focus_id = unignored_selection.focus_object_id; |
| if (id == focus_id) { |
| int focus_offset = unignored_selection.focus_offset; |
| dict->SetInteger("TreeData.textSelEndOffset", focus_offset); |
| } |
| |
| std::vector<std::string> actions_strings; |
| for (int32_t action_index = |
| static_cast<int32_t>(ax::mojom::Action::kNone) + 1; |
| action_index <= static_cast<int32_t>(ax::mojom::Action::kMaxValue); |
| ++action_index) { |
| auto action = static_cast<ax::mojom::Action>(action_index); |
| if (node.HasAction(action)) |
| actions_strings.push_back(ui::ToString(action)); |
| } |
| if (!actions_strings.empty()) |
| dict->SetString("actions", base::JoinString(actions_strings, ",")); |
| } |
| |
| void AccessibilityTreeFormatterBlink::AddProperties( |
| const ui::AXNode& node, |
| base::DictionaryValue* dict) const { |
| int id = node.id(); |
| dict->SetInteger("id", id); |
| dict->SetString("internalRole", ui::ToString(node.data().role)); |
| |
| gfx::Rect bounds = gfx::ToEnclosingRect(node.data().relative_bounds.bounds); |
| dict->SetInteger("boundsX", bounds.x()); |
| dict->SetInteger("boundsY", bounds.y()); |
| dict->SetInteger("boundsWidth", bounds.width()); |
| dict->SetInteger("boundsHeight", bounds.height()); |
| |
| // TODO(kschmi): Add support for the following (potentially via AXTree): |
| // GetClippedRootFrameBoundsRect |
| // pageBoundsX |
| // pageBoundsY |
| // pageBoundsWidth |
| // pageBoundsHeight |
| // GetUnclippedRootFrameBoundsRect |
| // unclippedBoundsX |
| // unclippedBoundsY |
| // unclippedBoundsWidth |
| // unclippedBoundsHeight |
| // STATE_OFFSCREEN |
| // ComputeAttribute |
| // TableCellAriaColIndex |
| // TableCellAriaRowIndex |
| // TableCellColIndex |
| // TableCellRowIndex |
| // TableCellColSpan |
| // TableCellRowSpan |
| // TableRowRowIndex |
| // TableColCount |
| // TableRowCount |
| // TableAriaColCount |
| // TableAriaRowCount |
| // PosInSet |
| // SetSize |
| // SetSize |
| |
| dict->SetBoolean("transform", |
| node.data().relative_bounds.transform && |
| !node.data().relative_bounds.transform->IsIdentity()); |
| |
| for (int32_t state_index = static_cast<int32_t>(ax::mojom::State::kNone); |
| state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue); |
| ++state_index) { |
| auto state = static_cast<ax::mojom::State>(state_index); |
| if (node.data().HasState(state)) |
| dict->SetBoolean(ui::ToString(state), true); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::StringAttribute::kNone); |
| attr_index <= |
| static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::StringAttribute>(attr_index); |
| auto maybe_value = GetStringAttribute(node, attr); |
| if (maybe_value.has_value()) |
| dict->SetString(ui::ToString(attr), maybe_value.value()); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::IntAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::IntAttribute>(attr_index); |
| int32_t value; |
| if (node.data().GetIntAttribute(attr, &value)) { |
| dict->SetString(ui::ToString(attr), IntAttrToString(node, attr, value)); |
| } |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::FloatAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index); |
| if (node.HasFloatAttribute(attr) && |
| std::isfinite(node.GetFloatAttribute(attr))) |
| dict->SetDouble(ui::ToString(attr), node.GetFloatAttribute(attr)); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::BoolAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index); |
| if (node.HasBoolAttribute(attr)) |
| dict->SetBoolean(ui::ToString(attr), node.GetBoolAttribute(attr)); |
| } |
| |
| for (int32_t attr_index = |
| static_cast<int32_t>(ax::mojom::IntListAttribute::kNone); |
| attr_index <= |
| static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index); |
| if (node.HasIntListAttribute(attr)) { |
| std::vector<int32_t> values; |
| node.GetIntListAttribute(attr, &values); |
| auto value_list = std::make_unique<base::ListValue>(); |
| for (auto value : values) { |
| if (ui::IsNodeIdIntListAttribute(attr)) { |
| ui::AXTreeID tree_id = node.tree()->GetAXTreeID(); |
| ui::AXNode* target = ui::AXTreeManagerMap::GetInstance() |
| .GetManager(tree_id) |
| ->GetNodeFromTree(tree_id, node.id()); |
| |
| if (target) |
| value_list->AppendString(ui::ToString(target->data().role)); |
| else |
| value_list->AppendString("null"); |
| } else { |
| value_list->AppendInteger(value); |
| } |
| } |
| dict->Set(ui::ToString(attr), std::move(value_list)); |
| } |
| } |
| |
| // Check for relevant rich text selection info in AXTreeData |
| ui::AXTree::Selection unignored_selection = |
| node.tree()->GetUnignoredSelection(); |
| int anchor_id = unignored_selection.anchor_object_id; |
| if (id == anchor_id) { |
| int anchor_offset = unignored_selection.anchor_offset; |
| dict->SetInteger("TreeData.textSelStartOffset", anchor_offset); |
| } |
| int focus_id = unignored_selection.focus_object_id; |
| if (id == focus_id) { |
| int focus_offset = unignored_selection.focus_offset; |
| dict->SetInteger("TreeData.textSelEndOffset", focus_offset); |
| } |
| |
| std::vector<std::string> actions_strings; |
| for (int32_t action_index = |
| static_cast<int32_t>(ax::mojom::Action::kNone) + 1; |
| action_index <= static_cast<int32_t>(ax::mojom::Action::kMaxValue); |
| ++action_index) { |
| auto action = static_cast<ax::mojom::Action>(action_index); |
| if (node.data().HasAction(action)) |
| actions_strings.push_back(ui::ToString(action)); |
| } |
| if (!actions_strings.empty()) |
| dict->SetString("actions", base::JoinString(actions_strings, ",")); |
| } |
| |
| std::string AccessibilityTreeFormatterBlink::ProcessTreeForOutput( |
| const base::DictionaryValue& dict) const { |
| std::string error_value; |
| if (dict.GetString("error", &error_value)) |
| return error_value; |
| |
| std::string line; |
| |
| if (show_ids()) { |
| int id_value; |
| dict.GetInteger("id", &id_value); |
| WriteAttribute(true, base::NumberToString(id_value), &line); |
| } |
| |
| std::string role_value; |
| dict.GetString("internalRole", &role_value); |
| WriteAttribute(true, role_value, &line); |
| |
| for (int state_index = static_cast<int32_t>(ax::mojom::State::kNone); |
| state_index <= static_cast<int32_t>(ax::mojom::State::kMaxValue); |
| ++state_index) { |
| auto state = static_cast<ax::mojom::State>(state_index); |
| const base::Value* value; |
| if (!dict.Get(ui::ToString(state), &value)) |
| continue; |
| |
| WriteAttribute(false, ui::ToString(state), &line); |
| } |
| |
| // Offscreen and Focused states are not in the state list. |
| bool offscreen = false; |
| dict.GetBoolean(STATE_OFFSCREEN, &offscreen); |
| if (offscreen) |
| WriteAttribute(false, STATE_OFFSCREEN, &line); |
| bool focused = false; |
| dict.GetBoolean(STATE_FOCUSED, &focused); |
| if (focused) |
| WriteAttribute(false, STATE_FOCUSED, &line); |
| |
| if (dict.FindKey("boundsX") && dict.FindKey("boundsY")) { |
| WriteAttribute(false, |
| FormatCoordinates(dict, "location", "boundsX", "boundsY"), |
| &line); |
| } |
| |
| if (dict.FindKey("boundsWidth") && dict.FindKey("boundsHeight")) { |
| WriteAttribute( |
| false, FormatCoordinates(dict, "size", "boundsWidth", "boundsHeight"), |
| &line); |
| } |
| |
| bool ignored = false; |
| dict.GetBoolean("ignored", &ignored); |
| if (!ignored) { |
| if (dict.FindKey("pageBoundsX") && dict.FindKey("pageBoundsY")) { |
| WriteAttribute( |
| false, |
| FormatCoordinates(dict, "pageLocation", "pageBoundsX", "pageBoundsY"), |
| &line); |
| } |
| if (dict.FindKey("pageBoundsWidth") && dict.FindKey("pageBoundsHeight")) { |
| WriteAttribute(false, |
| FormatCoordinates(dict, "pageSize", "pageBoundsWidth", |
| "pageBoundsHeight"), |
| &line); |
| } |
| if (dict.FindKey("unclippedBoundsX") && dict.FindKey("unclippedBoundsY")) { |
| WriteAttribute(false, |
| FormatCoordinates(dict, "unclippedLocation", |
| "unclippedBoundsX", "unclippedBoundsY"), |
| &line); |
| } |
| if (dict.FindKey("unclippedBoundsWidth") && |
| dict.FindKey("unclippedBoundsHeight")) { |
| WriteAttribute( |
| false, |
| FormatCoordinates(dict, "unclippedSize", "unclippedBoundsWidth", |
| "unclippedBoundsHeight"), |
| &line); |
| } |
| } |
| |
| bool transform; |
| if (dict.GetBoolean("transform", &transform) && transform) |
| WriteAttribute(false, "transform", &line); |
| |
| for (int attr_index = static_cast<int32_t>(ax::mojom::StringAttribute::kNone); |
| attr_index <= |
| static_cast<int32_t>(ax::mojom::StringAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::StringAttribute>(attr_index); |
| std::string string_value; |
| if (!dict.GetString(ui::ToString(attr), &string_value)) |
| continue; |
| WriteAttribute( |
| false, |
| base::StringPrintf("%s='%s'", ui::ToString(attr), string_value.c_str()), |
| &line); |
| } |
| |
| for (int attr_index = static_cast<int32_t>(ax::mojom::IntAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::IntAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::IntAttribute>(attr_index); |
| std::string string_value; |
| if (!dict.GetString(ui::ToString(attr), &string_value)) |
| continue; |
| WriteAttribute( |
| false, |
| base::StringPrintf("%s=%s", ui::ToString(attr), string_value.c_str()), |
| &line); |
| } |
| |
| for (int attr_index = static_cast<int32_t>(ax::mojom::BoolAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::BoolAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::BoolAttribute>(attr_index); |
| bool bool_value; |
| if (!dict.GetBoolean(ui::ToString(attr), &bool_value)) |
| continue; |
| WriteAttribute(false, |
| base::StringPrintf("%s=%s", ui::ToString(attr), |
| bool_value ? "true" : "false"), |
| &line); |
| } |
| |
| for (int attr_index = static_cast<int32_t>(ax::mojom::FloatAttribute::kNone); |
| attr_index <= static_cast<int32_t>(ax::mojom::FloatAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::FloatAttribute>(attr_index); |
| double float_value; |
| if (!dict.GetDouble(ui::ToString(attr), &float_value)) |
| continue; |
| WriteAttribute( |
| false, base::StringPrintf("%s=%.2f", ui::ToString(attr), float_value), |
| &line); |
| } |
| |
| for (int attr_index = |
| static_cast<int32_t>(ax::mojom::IntListAttribute::kNone); |
| attr_index <= |
| static_cast<int32_t>(ax::mojom::IntListAttribute::kMaxValue); |
| ++attr_index) { |
| auto attr = static_cast<ax::mojom::IntListAttribute>(attr_index); |
| const base::ListValue* value; |
| if (!dict.GetList(ui::ToString(attr), &value)) |
| continue; |
| std::string attr_string(ui::ToString(attr)); |
| attr_string.push_back('='); |
| for (size_t i = 0; i < value->GetSize(); ++i) { |
| if (i > 0) |
| attr_string += ","; |
| if (ui::IsNodeIdIntListAttribute(attr)) { |
| std::string string_value; |
| value->GetString(i, &string_value); |
| attr_string += string_value; |
| } else { |
| int int_value; |
| value->GetInteger(i, &int_value); |
| attr_string += base::NumberToString(int_value); |
| } |
| } |
| WriteAttribute(false, attr_string, &line); |
| } |
| |
| std::string actions_value; |
| if (dict.GetString("actions", &actions_value)) { |
| WriteAttribute( |
| false, base::StringPrintf("%s=%s", "actions", actions_value.c_str()), |
| &line); |
| } |
| |
| for (const char* attribute_name : TREE_DATA_ATTRIBUTES) { |
| const base::Value* value; |
| if (!dict.Get(attribute_name, &value)) |
| continue; |
| |
| switch (value->type()) { |
| case base::Value::Type::STRING: { |
| std::string string_value; |
| value->GetAsString(&string_value); |
| WriteAttribute( |
| false, |
| base::StringPrintf("%s=%s", attribute_name, string_value.c_str()), |
| &line); |
| break; |
| } |
| case base::Value::Type::INTEGER: { |
| int int_value = 0; |
| value->GetAsInteger(&int_value); |
| WriteAttribute(false, |
| base::StringPrintf("%s=%d", attribute_name, int_value), |
| &line); |
| break; |
| } |
| case base::Value::Type::DOUBLE: { |
| double double_value = 0.0; |
| value->GetAsDouble(&double_value); |
| WriteAttribute( |
| false, base::StringPrintf("%s=%.2f", attribute_name, double_value), |
| &line); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| return line; |
| } |
| |
| } // namespace content |