Announce text attributes in VoiceOver.
It is essential for users of VoiceOver on the Mac platform to have
access to stylistic information for all the text on a web page. This
change exposes font size, foreground color, background color, bold,
italic, underline, and strikethrough text attributes to AT users. "When
text attributes change:" needs to be set to "Speak Attributes" in
VoiceOver Utility to observe this change.
A later change will expose the remaining text attributes.
bold, italic, underline and strikethrough in VoiceOver.
Bug: 958811
Change-Id: I4f291fdd27466a71d2e65dff8add64475bd820f8
AX-Relnotes: Announce font size, foreground color, background color,
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4313423
Reviewed-by: David Tseng <[email protected]>
Commit-Queue: Sara Tang <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1119570}
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 998e63d..af0a50ab7 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -4407,6 +4407,22 @@
return GetAnchor()->GetRole();
}
+ AXTextAttributes GetTextAttributes() const {
+ // Check either the current anchor or its parent for text attributes.
+ AXTextAttributes current_anchor_text_attributes =
+ !IsNullPosition() ? GetAnchor()->GetTextAttributes()
+ : AXTextAttributes();
+ if (current_anchor_text_attributes.IsUnset()) {
+ AXPositionInstance parent_position =
+ AsTreePosition()->CreateParentPosition(
+ ax::mojom::MoveDirection::kBackward);
+ if (!parent_position->IsNullPosition()) {
+ return parent_position->GetAnchor()->GetTextAttributes();
+ }
+ }
+ return current_anchor_text_attributes;
+ }
+
protected:
AXPosition()
: kind_(AXPositionKind::NULL_POSITION),
@@ -4683,21 +4699,6 @@
ax::mojom::Role GetRole(AXNode* node) const { return node->GetRole(); }
- AXTextAttributes GetTextAttributes() const {
- // Check either the current anchor or its parent for text attributes.
- AXTextAttributes current_anchor_text_attributes =
- !IsNullPosition() ? GetAnchor()->GetTextAttributes()
- : AXTextAttributes();
- if (current_anchor_text_attributes.IsUnset()) {
- AXPositionInstance parent_position =
- AsTreePosition()->CreateParentPosition(
- ax::mojom::MoveDirection::kBackward);
- if (!parent_position->IsNullPosition())
- return parent_position->GetAnchor()->GetTextAttributes();
- }
- return current_anchor_text_attributes;
- }
-
const std::vector<int32_t>& GetWordStartOffsets() const {
if (IsNullPosition()) {
static const base::NoDestructor<std::vector<int32_t>> empty_word_starts;
diff --git a/ui/accessibility/ax_text_attributes.cc b/ui/accessibility/ax_text_attributes.cc
index ce95eeca..545a978 100644
--- a/ui/accessibility/ax_text_attributes.cc
+++ b/ui/accessibility/ax_text_attributes.cc
@@ -75,4 +75,11 @@
marker_types.size() == 0 && highlight_types.size() == 0;
}
+bool AXTextAttributes::HasTextStyle(
+ ax::mojom::TextStyle text_style_enum) const {
+ return text_style != kUnsetValue &&
+ (static_cast<uint32_t>(text_style) &
+ (1U << static_cast<uint32_t>(text_style_enum))) != 0;
+}
+
} // namespace ui
diff --git a/ui/accessibility/ax_text_attributes.h b/ui/accessibility/ax_text_attributes.h
index c298023..4c05e5a1e 100644
--- a/ui/accessibility/ax_text_attributes.h
+++ b/ui/accessibility/ax_text_attributes.h
@@ -9,6 +9,7 @@
#include <vector>
#include "ui/accessibility/ax_base_export.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
namespace ui {
@@ -36,6 +37,8 @@
bool IsUnset() const;
+ bool HasTextStyle(const ax::mojom::TextStyle text_style_enum) const;
+
int32_t background_color = kUnsetValue;
int32_t color = kUnsetValue;
int32_t invalid_state = kUnsetValue;
diff --git a/ui/accessibility/platform/DEPS b/ui/accessibility/platform/DEPS
index 301ade0..eb35f397 100644
--- a/ui/accessibility/platform/DEPS
+++ b/ui/accessibility/platform/DEPS
@@ -2,6 +2,9 @@
"atk_util_auralinux_x11\.cc": [
"+ui/events/x",
],
+ "ax_platform_node_cocoa\.mm": [
+ "+skia/ext",
+ ],
"ax_platform_node_win\.cc": [
"+skia/ext",
]
diff --git a/ui/accessibility/platform/ax_platform_node_cocoa.mm b/ui/accessibility/platform/ax_platform_node_cocoa.mm
index 4d43b44..bfe3733 100644
--- a/ui/accessibility/platform/ax_platform_node_cocoa.mm
+++ b/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -12,6 +12,7 @@
#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "base/trace_event/trace_event.h"
+#include "skia/ext/skia_utils_mac.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_range.h"
@@ -807,6 +808,89 @@
range:leafRange];
}
+ ui::AXTextAttributes text_attrs =
+ leafTextRange.anchor()->GetTextAttributes();
+
+ NSMutableDictionary* fontAttributes = [NSMutableDictionary dictionary];
+
+ // TODO(crbug.com/958811): Implement NSAccessibilityFontFamilyKey.
+ // TODO(crbug.com/958811): Implement NSAccessibilityFontNameKey.
+ // TODO(crbug.com/958811): Implement NSAccessibilityVisibleNameKey.
+
+ if (text_attrs.font_size != ui::AXTextAttributes::kUnsetValue) {
+ [fontAttributes setValue:@(text_attrs.font_size)
+ forKey:NSAccessibilityFontSizeKey];
+ }
+
+ if (text_attrs.HasTextStyle(ax::mojom::TextStyle::kBold)) {
+ [fontAttributes setValue:@YES forKey:@"AXFontBold"];
+ }
+
+ if (text_attrs.HasTextStyle(ax::mojom::TextStyle::kItalic)) {
+ [fontAttributes setValue:@YES forKey:@"AXFontItalic"];
+ }
+
+ [attributedString addAttribute:NSAccessibilityFontTextAttribute
+ value:fontAttributes
+ range:leafRange];
+
+ if (text_attrs.color != ui::AXTextAttributes::kUnsetValue) {
+ [attributedString addAttribute:NSAccessibilityForegroundColorTextAttribute
+ value:(__bridge id)skia::SkColorToSRGBNSColor(
+ SkColor(text_attrs.color))
+ .CGColor
+ range:leafRange];
+ } else {
+ [attributedString
+ removeAttribute:NSAccessibilityForegroundColorTextAttribute
+ range:leafRange];
+ }
+
+ if (text_attrs.background_color != ui::AXTextAttributes::kUnsetValue) {
+ [attributedString addAttribute:NSAccessibilityBackgroundColorTextAttribute
+ value:(__bridge id)skia::SkColorToSRGBNSColor(
+ SkColor(text_attrs.background_color))
+ .CGColor
+ range:leafRange];
+ } else {
+ [attributedString
+ removeAttribute:NSAccessibilityBackgroundColorTextAttribute
+ range:leafRange];
+ }
+
+ // TODO(crbug.com/958811): Implement
+ // NSAccessibilitySuperscriptTextAttribute.
+ // TODO(crbug.com/958811): Implement NSAccessibilityShadowTextAttribute.
+
+ if (text_attrs.underline_style != ui::AXTextAttributes::kUnsetValue) {
+ [attributedString addAttribute:NSAccessibilityUnderlineTextAttribute
+ value:@YES
+ range:leafRange];
+ } else {
+ [attributedString removeAttribute:NSAccessibilityUnderlineTextAttribute
+ range:leafRange];
+ }
+
+ // TODO(crbug.com/958811): Implement
+ // NSAccessibilityUnderlineColorTextAttribute.
+
+ if (text_attrs.strikethrough_style != ui::AXTextAttributes::kUnsetValue) {
+ [attributedString addAttribute:NSAccessibilityStrikethroughTextAttribute
+ value:@YES
+ range:leafRange];
+ } else {
+ [attributedString
+ removeAttribute:NSAccessibilityStrikethroughTextAttribute
+ range:leafRange];
+ }
+
+ // TODO(crbug.com/958811): Implement
+ // NSAccessibilityStrikethroughColorTextAttribute.
+
+ // TODO(crbug.com/958811): Implement NSAccessibilityLinkTextAttribute.
+ // TODO(crbug.com/958811): Implement
+ // NSAccessibilityAutocorrectedTextAttribute.
+
anchorStartOffset += leafTextLength;
}
[attributedString endEditing];
diff --git a/ui/accessibility/platform/inspect/ax_transform_mac.h b/ui/accessibility/platform/inspect/ax_transform_mac.h
index c7b961a..ff9b3d93 100644
--- a/ui/accessibility/platform/inspect/ax_transform_mac.h
+++ b/ui/accessibility/platform/inspect/ax_transform_mac.h
@@ -37,6 +37,16 @@
// Returns the base::Value representation of the given AXTextMarkerRange.
base::Value AXTextMarkerRangeToBaseValue(id, const AXTreeIndexerMac*);
+// Returns the base::Value::Dict representation of the given NSAttributedString.
+COMPONENT_EXPORT(AX_PLATFORM)
+base::Value NSAttributedStringToBaseValue(NSAttributedString*,
+ const AXTreeIndexerMac*);
+
+// Returns the base::Value representation of CGColorRef in the form CGColor(r,
+// g, b, a).
+COMPONENT_EXPORT(AX_PLATFORM)
+base::Value CGColorRefToBaseValue(CGColorRef color);
+
// Returns the base::Value representation of nil.
COMPONENT_EXPORT(AX_PLATFORM) base::Value AXNilToBaseValue();
diff --git a/ui/accessibility/platform/inspect/ax_transform_mac.mm b/ui/accessibility/platform/inspect/ax_transform_mac.mm
index 2b4c110a..7bcf2ea 100644
--- a/ui/accessibility/platform/inspect/ax_transform_mac.mm
+++ b/ui/accessibility/platform/inspect/ax_transform_mac.mm
@@ -62,6 +62,16 @@
}
}
+ // 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));
@@ -181,6 +191,40 @@
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);
}