| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/accessibility/platform/inspect/ax_transform_mac.h" |
| |
| #include "base/strings/sys_string_conversions.h" |
| #include "ui/accessibility/ax_range.h" |
| #include "ui/accessibility/platform/ax_platform_node.h" |
| #include "ui/accessibility/platform/ax_platform_node_cocoa.h" |
| #include "ui/accessibility/platform/ax_platform_node_delegate.h" |
| #include "ui/accessibility/platform/ax_platform_tree_manager.h" |
| #include "ui/accessibility/platform/ax_utils_mac.h" |
| #include "ui/accessibility/platform/inspect/ax_element_wrapper_mac.h" |
| #include "ui/accessibility/platform/inspect/ax_inspect_utils.h" |
| |
| namespace ui { |
| |
| constexpr char kHeightDictKey[] = "h"; |
| constexpr char kNilValue[] = "_const_NULL"; |
| constexpr char kRangeLenDictKey[] = "len"; |
| constexpr char kRangeLocDictKey[] = "loc"; |
| constexpr char kWidthDictKey[] = "w"; |
| constexpr char kXCoordDictKey[] = "x"; |
| constexpr char kYCoordDictKey[] = "y"; |
| |
| base::Value AXNSObjectToBaseValue(id value, const AXTreeIndexerMac* indexer) { |
| if (!value) { |
| return AXNilToBaseValue(); |
| } |
| |
| // NSArray |
| if ([value isKindOfClass:[NSArray class]]) { |
| return base::Value(AXNSArrayToBaseValue((NSArray*)value, indexer)); |
| } |
| |
| // AXCustomContent |
| if (@available(macOS 11.0, *)) { |
| if ([value isKindOfClass:[AXCustomContent class]]) { |
| return base::Value(AXCustomContentToBaseValue((AXCustomContent*)value)); |
| } |
| } |
| |
| // NSDictionary |
| if ([value isKindOfClass:[NSDictionary class]]) { |
| return base::Value( |
| AXNSDictionaryToBaseValue((NSDictionary*)value, indexer)); |
| } |
| |
| // NSNumber |
| if ([value isKindOfClass:[NSNumber class]]) { |
| return base::Value([value intValue]); |
| } |
| |
| // NSRange, NSSize |
| if ([value isKindOfClass:[NSValue class]]) { |
| if (0 == strcmp([value objCType], @encode(NSRange))) { |
| return base::Value(AXNSRangeToBaseValue([value rangeValue])); |
| } |
| if (0 == strcmp([value objCType], @encode(NSSize))) { |
| return base::Value(AXNSSizeToBaseValue([value sizeValue])); |
| } |
| } |
| |
| // NSAttributedString |
| if ([value isKindOfClass:[NSAttributedString class]]) { |
| return NSAttributedStringToBaseValue((NSAttributedString*)value, indexer); |
| } |
| |
| // CGColorRef |
| if (CFGetTypeID(value) == CGColorGetTypeID()) { |
| return base::Value(CGColorRefToBaseValue(static_cast<CGColorRef>(value))); |
| } |
| |
| // AXValue |
| if (CFGetTypeID(value) == AXValueGetTypeID()) { |
| AXValueType type = AXValueGetType(static_cast<AXValueRef>(value)); |
| switch (type) { |
| case kAXValueCGPointType: { |
| NSPoint point; |
| if (AXValueGetValue(static_cast<AXValueRef>(value), type, &point)) { |
| return base::Value(AXNSPointToBaseValue(point)); |
| } |
| } break; |
| case kAXValueCGSizeType: { |
| NSSize size; |
| if (AXValueGetValue(static_cast<AXValueRef>(value), type, &size)) { |
| return base::Value(AXNSSizeToBaseValue(size)); |
| } |
| } break; |
| case kAXValueCGRectType: { |
| NSRect rect; |
| if (AXValueGetValue(static_cast<AXValueRef>(value), type, &rect)) { |
| return base::Value(AXNSRectToBaseValue(rect)); |
| } |
| } break; |
| case kAXValueCFRangeType: { |
| NSRange range; |
| if (AXValueGetValue(static_cast<AXValueRef>(value), type, &range)) { |
| return base::Value(AXNSRangeToBaseValue(range)); |
| } |
| } break; |
| default: |
| break; |
| } |
| } |
| |
| // AXTextMarker |
| if (IsAXTextMarker(value)) { |
| return AXTextMarkerToBaseValue(value, indexer); |
| } |
| |
| // AXTextMarkerRange |
| if (IsAXTextMarkerRange(value)) |
| return AXTextMarkerRangeToBaseValue(value, indexer); |
| |
| // Accessible object |
| if (AXElementWrapper::IsValidElement(value)) { |
| return AXElementToBaseValue(value, indexer); |
| } |
| |
| // Scalar value. |
| return base::Value( |
| base::SysNSStringToUTF16([NSString stringWithFormat:@"%@", value])); |
| } |
| |
| base::Value AXElementToBaseValue(id node, const AXTreeIndexerMac* indexer) { |
| return base::Value(AXMakeConst(indexer->IndexBy(node))); |
| } |
| |
| base::Value AXPositionToBaseValue( |
| const AXPlatformNodeDelegate::AXPosition& position, |
| const AXTreeIndexerMac* indexer) { |
| if (position->IsNullPosition()) |
| return AXNilToBaseValue(); |
| |
| const AXPlatformTreeManager* manager = |
| static_cast<AXPlatformTreeManager*>(position->GetManager()); |
| if (!manager) |
| return AXNilToBaseValue(); |
| |
| AXPlatformNode* platform_node_anchor = |
| manager->GetPlatformNodeFromTree(position->anchor_id()); |
| if (!platform_node_anchor) |
| return AXNilToBaseValue(); |
| |
| AXPlatformNodeCocoa* cocoa_anchor = static_cast<AXPlatformNodeCocoa*>( |
| platform_node_anchor->GetNativeViewAccessible()); |
| if (!cocoa_anchor) |
| return AXNilToBaseValue(); |
| |
| std::string affinity; |
| switch (position->affinity()) { |
| case ax::mojom::TextAffinity::kNone: |
| affinity = "none"; |
| break; |
| case ax::mojom::TextAffinity::kDownstream: |
| affinity = "down"; |
| break; |
| case ax::mojom::TextAffinity::kUpstream: |
| affinity = "up"; |
| break; |
| } |
| |
| base::Value::Dict value; |
| value.Set(AXMakeSetKey(AXMakeOrderedKey("anchor", 0)), |
| AXElementToBaseValue(static_cast<id>(cocoa_anchor), indexer)); |
| value.Set(AXMakeSetKey(AXMakeOrderedKey("offset", 1)), |
| position->text_offset()); |
| value.Set(AXMakeSetKey(AXMakeOrderedKey("affinity", 2)), |
| AXMakeConst(affinity)); |
| return base::Value(std::move(value)); |
| } |
| |
| base::Value AXTextMarkerToBaseValue(id text_marker, |
| const AXTreeIndexerMac* indexer) { |
| return AXPositionToBaseValue(AXTextMarkerToAXPosition(text_marker), indexer); |
| } |
| |
| base::Value AXTextMarkerRangeToBaseValue(id text_marker_range, |
| const AXTreeIndexerMac* indexer) { |
| AXPlatformNodeDelegate::AXRange ax_range = |
| AXTextMarkerRangeToAXRange(text_marker_range); |
| if (ax_range.IsNull()) |
| return AXNilToBaseValue(); |
| |
| base::Value::Dict value; |
| value.Set("anchor", |
| AXPositionToBaseValue(ax_range.anchor()->Clone(), indexer)); |
| value.Set("focus", AXPositionToBaseValue(ax_range.focus()->Clone(), indexer)); |
| return base::Value(std::move(value)); |
| } |
| |
| base::Value NSAttributedStringToBaseValue(NSAttributedString* attr_string, |
| const AXTreeIndexerMac* indexer) { |
| __block base::Value::Dict result; |
| |
| [attr_string |
| enumerateAttributesInRange:NSMakeRange(0, [attr_string length]) |
| options: |
| NSAttributedStringEnumerationLongestEffectiveRangeNotRequired |
| usingBlock:^(NSDictionary* attrs, NSRange nsRange, |
| BOOL* stop) { |
| __block base::Value::Dict base_attrs; |
| [attrs enumerateKeysAndObjectsUsingBlock:^( |
| NSString* key, id attr, BOOL* dict_stop) { |
| base_attrs.Set( |
| std::string(base::SysNSStringToUTF8(key)), |
| AXNSObjectToBaseValue(attr, indexer)); |
| }]; |
| |
| result.Set(std::string(base::SysNSStringToUTF8( |
| [[attr_string string] |
| substringWithRange:nsRange])), |
| std::move(base_attrs)); |
| }]; |
| return base::Value(std::move(result)); |
| } |
| |
| base::Value CGColorRefToBaseValue(CGColorRef color) { |
| const CGFloat* color_components = CGColorGetComponents(color); |
| return base::Value(base::SysNSStringToUTF16( |
| [NSString stringWithFormat:@"CGColor(%1.2f, %1.2f, %1.2f, %1.2f)", |
| color_components[0], color_components[1], |
| color_components[2], color_components[3]])); |
| } |
| |
| base::Value AXNilToBaseValue() { |
| return base::Value(kNilValue); |
| } |
| |
| base::Value::List AXNSArrayToBaseValue(NSArray* node_array, |
| const AXTreeIndexerMac* indexer) { |
| base::Value::List list; |
| for (NSUInteger i = 0; i < [node_array count]; i++) |
| list.Append(AXNSObjectToBaseValue([node_array objectAtIndex:i], indexer)); |
| return list; |
| } |
| |
| base::Value::Dict AXCustomContentToBaseValue(AXCustomContent* content) { |
| base::Value::Dict value; |
| value.Set("label", base::SysNSStringToUTF16(content.label)); |
| value.Set("value", base::SysNSStringToUTF16(content.value)); |
| return value; |
| } |
| |
| base::Value::Dict AXNSDictionaryToBaseValue(NSDictionary* dictionary_value, |
| const AXTreeIndexerMac* indexer) { |
| base::Value::Dict dictionary; |
| for (NSString* key in dictionary_value) { |
| dictionary.SetByDottedPath( |
| base::SysNSStringToUTF8(key), |
| AXNSObjectToBaseValue(dictionary_value[key], indexer)); |
| } |
| return dictionary; |
| } |
| |
| base::Value::Dict AXNSPointToBaseValue(NSPoint point_value) { |
| base::Value::Dict point; |
| point.Set(kXCoordDictKey, static_cast<int>(point_value.x)); |
| point.Set(kYCoordDictKey, static_cast<int>(point_value.y)); |
| return point; |
| } |
| |
| base::Value::Dict AXNSSizeToBaseValue(NSSize size_value) { |
| base::Value::Dict size; |
| size.Set(AXMakeOrderedKey(kWidthDictKey, 0), |
| static_cast<int>(size_value.width)); |
| size.Set(AXMakeOrderedKey(kHeightDictKey, 1), |
| static_cast<int>(size_value.height)); |
| return size; |
| } |
| |
| base::Value::Dict AXNSRectToBaseValue(NSRect rect_value) { |
| base::Value::Dict rect; |
| rect.Set(AXMakeOrderedKey(kXCoordDictKey, 0), |
| static_cast<int>(rect_value.origin.x)); |
| rect.Set(AXMakeOrderedKey(kYCoordDictKey, 1), |
| static_cast<int>(rect_value.origin.y)); |
| rect.Set(AXMakeOrderedKey(kWidthDictKey, 2), |
| static_cast<int>(rect_value.size.width)); |
| rect.Set(AXMakeOrderedKey(kHeightDictKey, 3), |
| static_cast<int>(rect_value.size.height)); |
| return rect; |
| } |
| |
| base::Value::Dict AXNSRangeToBaseValue(NSRange node_range) { |
| base::Value::Dict range; |
| range.Set(AXMakeOrderedKey(kRangeLocDictKey, 0), |
| static_cast<int>(node_range.location)); |
| range.Set(AXMakeOrderedKey(kRangeLenDictKey, 1), |
| static_cast<int>(node_range.length)); |
| return range; |
| } |
| |
| } // namespace ui |