// Copyright 2017 The Chromium Embedded Framework 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 "libcef/browser/osr/osr_accessibility_util.h" #include #include #include #include "base/json/string_escape.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "content/public/browser/ax_event_notification_details.h" #include "ui/accessibility/ax_enum_util.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_text_utils.h" #include "ui/accessibility/ax_tree_update.h" #include "ui/gfx/transform.h" namespace { using ui::ToString; template CefRefPtr ToCefValue(const std::vector& vecData); template <> CefRefPtr ToCefValue(const std::vector& vecData) { CefRefPtr value = CefListValue::Create(); for (size_t i = 0; i < vecData.size(); i++) value->SetInt(i, vecData[i]); return value; } // Helper function for AXNodeData::ToCefValue - Converts AXState attributes to // CefListValue. CefRefPtr ToCefValue(uint32_t state) { CefRefPtr value = CefListValue::Create(); int index = 0; // Iterate and find which states are set. for (unsigned i = static_cast(ax::mojom::Role::kMinValue) + 1; i <= static_cast(ax::mojom::Role::kMaxValue); i++) { if (state & (1 << i)) value->SetString(index++, ToString(static_cast(i))); } return value; } // Helper function for AXNodeData::ToCefValue - converts GfxRect to // CefDictionaryValue. CefRefPtr ToCefValue(const gfx::RectF& bounds) { CefRefPtr value = CefDictionaryValue::Create(); value->SetDouble("x", bounds.x()); value->SetDouble("y", bounds.y()); value->SetDouble("width", bounds.width()); value->SetDouble("height", bounds.height()); return value; } // Helper Functor for adding AxNodeData::attributes to AXNodeData::ToCefValue. struct PopulateAxNodeAttributes { CefRefPtr attributes; explicit PopulateAxNodeAttributes(CefRefPtr attrs) : attributes(attrs) {} // Int Attributes void operator()(const std::pair attr) { if (attr.first == ax::mojom::IntAttribute::kNone) return; switch (attr.first) { case ax::mojom::IntAttribute::kNone: break; 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::kHasPopup: case ax::mojom::IntAttribute::kHierarchicalLevel: case ax::mojom::IntAttribute::kTextSelStart: case ax::mojom::IntAttribute::kTextSelEnd: case ax::mojom::IntAttribute::kAriaColumnCount: case ax::mojom::IntAttribute::kAriaCellColumnIndex: case ax::mojom::IntAttribute::kAriaRowCount: case ax::mojom::IntAttribute::kAriaCellRowIndex: case ax::mojom::IntAttribute::kTableRowCount: case ax::mojom::IntAttribute::kTableColumnCount: case ax::mojom::IntAttribute::kTableCellColumnIndex: case ax::mojom::IntAttribute::kTableCellRowIndex: case ax::mojom::IntAttribute::kTableCellColumnSpan: case ax::mojom::IntAttribute::kTableCellRowSpan: case ax::mojom::IntAttribute::kTableColumnHeaderId: case ax::mojom::IntAttribute::kTableColumnIndex: case ax::mojom::IntAttribute::kTableHeaderId: case ax::mojom::IntAttribute::kTableRowHeaderId: case ax::mojom::IntAttribute::kTableRowIndex: case ax::mojom::IntAttribute::kActivedescendantId: case ax::mojom::IntAttribute::kInPageLinkTargetId: case ax::mojom::IntAttribute::kErrormessageId: case ax::mojom::IntAttribute::kDetailsId: case ax::mojom::IntAttribute::kDropeffect: case ax::mojom::IntAttribute::kMemberOfId: case ax::mojom::IntAttribute::kNextFocusId: case ax::mojom::IntAttribute::kNextOnLineId: case ax::mojom::IntAttribute::kPreviousFocusId: case ax::mojom::IntAttribute::kPreviousOnLineId: case ax::mojom::IntAttribute::kSetSize: case ax::mojom::IntAttribute::kPosInSet: case ax::mojom::IntAttribute::kPopupForId: attributes->SetInt(ToString(attr.first), attr.second); break; case ax::mojom::IntAttribute::kDefaultActionVerb: attributes->SetString( ToString(attr.first), ui::ActionVerbToUnlocalizedString( static_cast(attr.second))); break; case ax::mojom::IntAttribute::kInvalidState: { auto state = static_cast(attr.second); if (ax::mojom::InvalidState::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kCheckedState: { auto state = static_cast(attr.second); if (ax::mojom::CheckedState::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kRestriction: attributes->SetString( ToString(attr.first), ToString(static_cast(attr.second))); break; case ax::mojom::IntAttribute::kListStyle: { auto state = static_cast(attr.second); if (ax::mojom::ListStyle::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kSortDirection: { auto state = static_cast(attr.second); if (ax::mojom::SortDirection::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kNameFrom: attributes->SetString( ToString(attr.first), ToString(static_cast(attr.second))); break; case ax::mojom::IntAttribute::kColorValue: case ax::mojom::IntAttribute::kBackgroundColor: case ax::mojom::IntAttribute::kColor: attributes->SetString(ToString(attr.first), base::StringPrintf("0x%X", attr.second)); break; case ax::mojom::IntAttribute::kDescriptionFrom: attributes->SetString( ToString(attr.first), ToString(static_cast(attr.second))); break; case ax::mojom::IntAttribute::kAriaCurrentState: { auto state = static_cast(attr.second); if (ax::mojom::AriaCurrentState::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kTextDirection: { auto state = static_cast(attr.second); if (ax::mojom::TextDirection::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kTextPosition: { auto state = static_cast(attr.second); if (ax::mojom::TextPosition::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kTextStyle: { static ax::mojom::TextStyle textStyleArr[] = { ax::mojom::TextStyle::kBold, ax::mojom::TextStyle::kItalic, ax::mojom::TextStyle::kUnderline, ax::mojom::TextStyle::kLineThrough, ax::mojom::TextStyle::kOverline}; CefRefPtr list = CefListValue::Create(); int index = 0; // Iterate and find which states are set. for (unsigned i = 0; i < base::size(textStyleArr); i++) { if (attr.second & static_cast(textStyleArr[i])) list->SetString(index++, ToString(textStyleArr[i])); } attributes->SetList(ToString(attr.first), list); } break; case ax::mojom::IntAttribute::kTextOverlineStyle: case ax::mojom::IntAttribute::kTextStrikethroughStyle: case ax::mojom::IntAttribute::kTextUnderlineStyle: { auto state = static_cast(attr.second); if (ax::mojom::TextDecorationStyle::kNone != state) { attributes->SetString(ToString(attr.first), ToString(state)); } } break; case ax::mojom::IntAttribute::kAriaCellColumnSpan: case ax::mojom::IntAttribute::kAriaCellRowSpan: case ax::mojom::IntAttribute::kImageAnnotationStatus: { // TODO(cef): Implement support for Image Annotation Status, // kAriaCellColumnSpan and kAriaCellRowSpan } break; } } // Set Bool Attributes. void operator()(const std::pair attr) { if (attr.first != ax::mojom::BoolAttribute::kNone) attributes->SetBool(ToString(attr.first), attr.second); } // Set String Attributes. void operator()( const std::pair& attr) { if (attr.first != ax::mojom::StringAttribute::kNone) attributes->SetString(ToString(attr.first), attr.second); } // Set Float attributes. void operator()(const std::pair& attr) { if (attr.first != ax::mojom::FloatAttribute::kNone) attributes->SetDouble(ToString(attr.first), attr.second); } // Set Int list attributes. void operator()(const std::pair>& attr) { if (attr.first != ax::mojom::IntListAttribute::kNone) { CefRefPtr list; if (ax::mojom::IntListAttribute::kMarkerTypes == attr.first) { list = CefListValue::Create(); int index = 0; for (size_t i = 0; i < attr.second.size(); ++i) { auto type = static_cast(attr.second[i]); if (type == ax::mojom::MarkerType::kNone) continue; static ax::mojom::MarkerType marktypeArr[] = { ax::mojom::MarkerType::kSpelling, ax::mojom::MarkerType::kGrammar, ax::mojom::MarkerType::kTextMatch}; // Iterate and find which markers are set. for (unsigned j = 0; j < base::size(marktypeArr); j++) { if (attr.second[i] & static_cast(marktypeArr[j])) list->SetString(index++, ToString(marktypeArr[j])); } } } else { list = ToCefValue(attr.second); } attributes->SetList(ToString(attr.first), list); } } }; // Converts AXNodeData to CefDictionaryValue(like AXNodeData::ToString). CefRefPtr ToCefValue(const ui::AXNodeData& node) { CefRefPtr value = CefDictionaryValue::Create(); if (node.id != -1) value->SetInt("id", node.id); value->SetString("role", ToString(node.role)); value->SetList("state", ToCefValue(node.state)); if (node.relative_bounds.offset_container_id != -1) { value->SetInt("offset_container_id", node.relative_bounds.offset_container_id); } value->SetDictionary("location", ToCefValue(node.relative_bounds.bounds)); // Transform matrix is private, so we set the string that Clients can parse // and use if needed. if (node.relative_bounds.transform && !node.relative_bounds.transform->IsIdentity()) { value->SetString("transform", node.relative_bounds.transform->ToString()); } if (!node.child_ids.empty()) { value->SetList("child_ids", ToCefValue(node.child_ids)); } CefRefPtr actions_strings; size_t actions_idx = 0; for (int action_index = static_cast(ax::mojom::Action::kMinValue) + 1; action_index <= static_cast(ax::mojom::Action::kMaxValue); ++action_index) { auto action = static_cast(action_index); if (node.HasAction(action)) { if (!actions_strings) actions_strings = CefListValue::Create(); actions_strings->SetString(actions_idx++, ToString(action)); } } if (actions_strings) value->SetList("actions", actions_strings); CefRefPtr attributes = CefDictionaryValue::Create(); PopulateAxNodeAttributes func(attributes); // Poupulate Int Attributes. std::for_each(node.int_attributes.begin(), node.int_attributes.end(), func); // Poupulate String Attributes. std::for_each(node.string_attributes.begin(), node.string_attributes.end(), func); // Poupulate Float Attributes. std::for_each(node.float_attributes.begin(), node.float_attributes.end(), func); // Poupulate Bool Attributes. std::for_each(node.bool_attributes.begin(), node.bool_attributes.end(), func); // Populate int list attributes. std::for_each(node.intlist_attributes.begin(), node.intlist_attributes.end(), func); value->SetDictionary("attributes", attributes); return value; } // Converts AXTreeData to CefDictionaryValue(like AXTreeData::ToString). CefRefPtr ToCefValue(const ui::AXTreeData& treeData) { CefRefPtr value = CefDictionaryValue::Create(); if (!treeData.tree_id.ToString().empty()) value->SetString("tree_id", treeData.tree_id.ToString()); if (!treeData.parent_tree_id.ToString().empty()) value->SetString("parent_tree_id", treeData.parent_tree_id.ToString()); if (!treeData.focused_tree_id.ToString().empty()) value->SetString("focused_tree_id", treeData.focused_tree_id.ToString()); if (!treeData.doctype.empty()) value->SetString("doctype", treeData.doctype); value->SetBool("loaded", treeData.loaded); if (treeData.loading_progress != 0.0) value->SetDouble("loading_progress", treeData.loading_progress); if (!treeData.mimetype.empty()) value->SetString("mimetype", treeData.mimetype); if (!treeData.url.empty()) value->SetString("url", treeData.url); if (!treeData.title.empty()) value->SetString("title", treeData.title); if (treeData.sel_anchor_object_id != -1) { value->SetInt("sel_anchor_object_id", treeData.sel_anchor_object_id); value->SetInt("sel_anchor_offset", treeData.sel_anchor_offset); value->SetString("sel_anchor_affinity", ToString(treeData.sel_anchor_affinity)); } if (treeData.sel_focus_object_id != -1) { value->SetInt("sel_focus_object_id", treeData.sel_anchor_object_id); value->SetInt("sel_focus_offset", treeData.sel_anchor_offset); value->SetString("sel_focus_affinity", ToString(treeData.sel_anchor_affinity)); } if (treeData.focus_id != -1) value->SetInt("focus_id", treeData.focus_id); return value; } // Converts AXTreeUpdate to CefDictionaryValue(like AXTreeUpdate::ToString). CefRefPtr ToCefValue(const ui::AXTreeUpdate& update) { CefRefPtr value = CefDictionaryValue::Create(); if (update.has_tree_data) { value->SetBool("has_tree_data", true); value->SetDictionary("tree_data", ToCefValue(update.tree_data)); } if (update.node_id_to_clear != 0) value->SetInt("node_id_to_clear", update.node_id_to_clear); if (update.root_id != 0) value->SetInt("root_id", update.root_id); value->SetList("nodes", ToCefValue(update.nodes)); return value; } // Converts AXEvent to CefDictionaryValue. CefRefPtr ToCefValue(const ui::AXEvent& event) { CefRefPtr value = CefDictionaryValue::Create(); if (event.event_type != ax::mojom::Event::kNone) value->SetString("event_type", ToString(event.event_type)); if (event.id != -1) value->SetInt("id", event.id); if (event.event_from != ax::mojom::EventFrom::kNone) value->SetString("event_from", ToString(event.event_from)); if (event.action_request_id != -1) value->SetInt("action_request_id", event.action_request_id); return value; } // Convert AXEventNotificationDetails to CefDictionaryValue. CefRefPtr ToCefValue( const content::AXEventNotificationDetails& eventData) { CefRefPtr value = CefDictionaryValue::Create(); if (!eventData.ax_tree_id.ToString().empty()) value->SetString("ax_tree_id", eventData.ax_tree_id.ToString()); if (eventData.updates.size() > 0) { CefRefPtr updates = CefListValue::Create(); updates->SetSize(eventData.updates.size()); size_t i = 0; for (const auto& update : eventData.updates) { updates->SetDictionary(i++, ToCefValue(update)); } value->SetList("updates", updates); } if (eventData.events.size() > 0) { CefRefPtr events = CefListValue::Create(); events->SetSize(eventData.events.size()); size_t i = 0; for (const auto& event : eventData.events) { events->SetDictionary(i++, ToCefValue(event)); } value->SetList("events", events); } return value; } // Convert AXRelativeBounds to CefDictionaryValue. Similar to // AXRelativeBounds::ToString. See that for more details CefRefPtr ToCefValue(const ui::AXRelativeBounds& location) { CefRefPtr value = CefDictionaryValue::Create(); if (location.offset_container_id != -1) value->SetInt("offset_container_id", location.offset_container_id); value->SetDictionary("bounds", ToCefValue(location.bounds)); // Transform matrix is private, so we set the string that Clients can parse // and use if needed. if (location.transform && !location.transform->IsIdentity()) value->SetString("transform", location.transform->ToString()); return value; } // Convert AXLocationChangeNotificationDetails to CefDictionaryValue. CefRefPtr ToCefValue( const content::AXLocationChangeNotificationDetails& locData) { CefRefPtr value = CefDictionaryValue::Create(); if (locData.id != -1) value->SetInt("id", locData.id); if (!locData.ax_tree_id.ToString().empty()) value->SetString("ax_tree_id", locData.ax_tree_id.ToString()); value->SetDictionary("new_location", ToCefValue(locData.new_location)); return value; } template CefRefPtr ToCefValue(const std::vector& vecData) { CefRefPtr value = CefListValue::Create(); for (size_t i = 0; i < vecData.size(); i++) value->SetDictionary(i, ToCefValue(vecData[i])); return value; } } // namespace namespace osr_accessibility_util { CefRefPtr ParseAccessibilityEventData( const content::AXEventNotificationDetails& data) { CefRefPtr value = CefValue::Create(); value->SetDictionary(ToCefValue(data)); return value; } CefRefPtr ParseAccessibilityLocationData( const std::vector& data) { CefRefPtr value = CefValue::Create(); value->SetList(ToCefValue(data)); return value; } } // namespace osr_accessibility_util