summaryrefslogtreecommitdiff
path: root/chromium/ui/accessibility
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/accessibility')
-rw-r--r--chromium/ui/accessibility/BUILD.gn53
-rw-r--r--chromium/ui/accessibility/OWNERS1
-rw-r--r--chromium/ui/accessibility/accessibility_features.cc19
-rw-r--r--chromium/ui/accessibility/accessibility_features.h15
-rw-r--r--chromium/ui/accessibility/ax_common.h6
-rw-r--r--chromium/ui/accessibility/ax_computed_node_data.cc99
-rw-r--r--chromium/ui/accessibility/ax_computed_node_data.h28
-rw-r--r--chromium/ui/accessibility/ax_computed_node_data_unittest.cc66
-rw-r--r--chromium/ui/accessibility/ax_enum_localization_util.cc40
-rw-r--r--chromium/ui/accessibility/ax_enum_localization_util.h21
-rw-r--r--chromium/ui/accessibility/ax_enum_util.cc34
-rw-r--r--chromium/ui/accessibility/ax_enum_util.h4
-rw-r--r--chromium/ui/accessibility/ax_enums.mojom52
-rw-r--r--chromium/ui/accessibility/ax_event_generator.cc32
-rw-r--r--chromium/ui/accessibility/ax_event_generator.h11
-rw-r--r--chromium/ui/accessibility/ax_event_generator_unittest.cc118
-rw-r--r--chromium/ui/accessibility/ax_language_detection.h8
-rw-r--r--chromium/ui/accessibility/ax_language_detection_unittest.cc9
-rw-r--r--chromium/ui/accessibility/ax_mode.h6
-rw-r--r--chromium/ui/accessibility/ax_node.cc380
-rw-r--r--chromium/ui/accessibility/ax_node.h111
-rw-r--r--chromium/ui/accessibility/ax_node_data.cc33
-rw-r--r--chromium/ui/accessibility/ax_node_data.h3
-rw-r--r--chromium/ui/accessibility/ax_node_data_unittest.cc38
-rw-r--r--chromium/ui/accessibility/ax_node_position.cc4
-rw-r--r--chromium/ui/accessibility/ax_node_position_fuzzer.cc410
-rw-r--r--chromium/ui/accessibility/ax_node_position_unittest.cc42
-rw-r--r--chromium/ui/accessibility/ax_node_unittest.cc110
-rw-r--r--chromium/ui/accessibility/ax_position.h11
-rw-r--r--chromium/ui/accessibility/ax_range_unittest.cc2
-rw-r--r--chromium/ui/accessibility/ax_role_properties.cc3
-rw-r--r--chromium/ui/accessibility/ax_role_properties.h2
-rw-r--r--chromium/ui/accessibility/ax_tree.cc133
-rw-r--r--chromium/ui/accessibility/ax_tree.h25
-rw-r--r--chromium/ui/accessibility/ax_tree_fuzzer_util.cc355
-rw-r--r--chromium/ui/accessibility/ax_tree_fuzzer_util.h90
-rw-r--r--chromium/ui/accessibility/ax_tree_manager.cc107
-rw-r--r--chromium/ui/accessibility/ax_tree_manager.h90
-rw-r--r--chromium/ui/accessibility/ax_tree_manager_base.cc5
-rw-r--r--chromium/ui/accessibility/ax_tree_manager_base.h4
-rw-r--r--chromium/ui/accessibility/ax_tree_manager_map.cc35
-rw-r--r--chromium/ui/accessibility/ax_tree_manager_map.h24
-rw-r--r--chromium/ui/accessibility/ax_tree_serializer.h15
-rw-r--r--chromium/ui/accessibility/ax_tree_unittest.cc45
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/background.js96
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/browser_action.pngbin0 -> 12776 bytes
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/common.js101
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/contrast-128.pngbin0 -> 4211 bytes
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/contrast-16.pngbin0 -> 534 bytes
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/contrast-19.pngbin0 -> 711 bytes
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/contrast-38.pngbin0 -> 1382 bytes
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/contrast-48.pngbin0 -> 1746 bytes
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/help.html151
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/highcontrast.js177
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/manifest.json22
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/ui.html130
-rw-r--r--chromium/ui/accessibility/extensions/color_contrast_companion/ui.js290
-rw-r--r--chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_as.xtb2
-rw-r--r--chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_te.xtb2
-rw-r--r--chromium/ui/accessibility/mojom/BUILD.gn44
-rw-r--r--chromium/ui/accessibility/platform/BUILD.gn3
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win.cc5
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc120
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node.cc2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc78
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.h4
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc55
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.cc131
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.h16
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base_unittest.cc180
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_cocoa.mm25
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate.cc36
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate.h24
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc17
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h5
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_mac.mm6
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc39
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_fuzzer.cc245
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc218
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_unittest.cc11
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_unittest.h5
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.cc72
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc48
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_tree_manager.h5
-rw-r--r--chromium/ui/accessibility/platform/ax_unique_id.cc4
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.h9
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.mm81
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.h158
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.mm362
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_inspect_scenario.h3
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.h75
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm292
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_optional.h30
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_property_node.h2
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_transform_mac.mm7
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.h6
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.mm62
-rw-r--r--chromium/ui/accessibility/platform/inspect/ax_tree_indexer_mac.h14
-rw-r--r--chromium/ui/accessibility/platform/test_ax_tree_update.cc56
-rw-r--r--chromium/ui/accessibility/platform/test_ax_tree_update.h54
-rw-r--r--chromium/ui/accessibility/test_ax_tree_manager.cc81
-rw-r--r--chromium/ui/accessibility/test_ax_tree_manager.h11
-rw-r--r--chromium/ui/accessibility/test_ax_tree_update_json_reader.cc342
-rw-r--r--chromium/ui/accessibility/test_ax_tree_update_json_reader.h27
105 files changed, 5285 insertions, 1457 deletions
diff --git a/chromium/ui/accessibility/BUILD.gn b/chromium/ui/accessibility/BUILD.gn
index a98305b4887..16e5316fdd2 100644
--- a/chromium/ui/accessibility/BUILD.gn
+++ b/chromium/ui/accessibility/BUILD.gn
@@ -20,10 +20,12 @@ if (is_win) {
}
mojom("ax_constants_mojo") {
+ generate_java = true
sources = [ "ax_constants.mojom" ]
}
mojom_component("ax_enums_mojo") {
+ generate_java = true
sources = [ "ax_enums.mojom" ]
macro_prefix = "UI_ACCESSIBILITY_AX_MOJOM"
@@ -77,17 +79,29 @@ component("ax_base") {
"ax_tree_update_forward.h",
]
+ if (!is_chromeos_ash) {
+ sources += [
+ "ax_enum_localization_util.cc",
+ "ax_enum_localization_util.h",
+ ]
+ }
+
public_deps = [
":ax_constants_mojo",
":ax_enums_mojo",
"//base",
- "//base:i18n",
- "//ui/base",
"//ui/gfx",
"//ui/gfx/geometry",
- "//ui/strings",
]
+ if (!is_chromeos_ash) {
+ public_deps += [
+ "//base:i18n",
+ "//ui/base",
+ "//ui/strings",
+ ]
+ }
+
deps = [ "//build:chromeos_buildflags" ]
if (is_chromeos_ash) {
@@ -141,6 +155,7 @@ component("accessibility") {
"ax_tree.h",
"ax_tree_combiner.cc",
"ax_tree_combiner.h",
+ "ax_tree_manager.cc",
"ax_tree_manager.h",
"ax_tree_manager_base.cc",
"ax_tree_manager_base.h",
@@ -218,8 +233,12 @@ source_set("ax_assistant") {
static_library("test_support") {
testonly = true
sources = [
+ "platform/test_ax_tree_update.cc",
+ "platform/test_ax_tree_update.h",
"test_ax_node_helper.cc",
"test_ax_node_helper.h",
+ "test_ax_tree_update_json_reader.cc",
+ "test_ax_tree_update_json_reader.h",
"tree_generator.cc",
"tree_generator.h",
]
@@ -290,8 +309,6 @@ test("accessibility_unittests") {
"//ui/gfx:test_support",
]
- data_deps = [ "//testing/buildbot/filters:accessibility_unittests_filters" ]
-
if (is_fuchsia) {
sources += [
"platform/fuchsia/accessibility_bridge_fuchsia_unittest.cc",
@@ -358,6 +375,32 @@ fuzzer_test("ax_table_fuzzer") {
seed_corpus = "fuzz_corpus"
}
+fuzzer_test("ax_node_position_fuzzer") {
+ sources = [
+ "ax_node_position_fuzzer.cc",
+ "ax_tree_fuzzer_util.cc",
+ "ax_tree_fuzzer_util.h",
+ ]
+
+ deps = [ ":accessibility" ]
+}
+
+if (is_win) {
+ fuzzer_test("ax_platform_node_textrangeprovider_win_fuzzer") {
+ sources = [
+ "ax_tree_fuzzer_util.cc",
+ "ax_tree_fuzzer_util.h",
+ "platform/ax_platform_node_textrangeprovider_win_fuzzer.cc",
+ ]
+
+ deps = [
+ ":accessibility",
+ ":test_support",
+ "//base/test:test_support",
+ ]
+ }
+}
+
test("accessibility_perftests") {
testonly = true
sources = [ "ax_node_position_perftest.cc" ]
diff --git a/chromium/ui/accessibility/OWNERS b/chromium/ui/accessibility/OWNERS
index cfdb21e52d3..23fbf83409b 100644
--- a/chromium/ui/accessibility/OWNERS
+++ b/chromium/ui/accessibility/OWNERS
@@ -1,5 +1,6 @@
abigailbklein@google.com
aleventhal@chromium.org
+benjamin.beaudry@microsoft.com
dlibby@microsoft.com
dtseng@chromium.org
katie@chromium.org
diff --git a/chromium/ui/accessibility/accessibility_features.cc b/chromium/ui/accessibility/accessibility_features.cc
index aeaed89ea0f..fa22a30090a 100644
--- a/chromium/ui/accessibility/accessibility_features.cc
+++ b/chromium/ui/accessibility/accessibility_features.cc
@@ -79,6 +79,13 @@ bool IsAutoDisableAccessibilityEnabled() {
return base::FeatureList::IsEnabled(::features::kAutoDisableAccessibility);
}
+const base::Feature kTextBasedAudioDescription{
+ "TextBasedAudioDescription", base::FEATURE_DISABLED_BY_DEFAULT};
+
+bool IsTextBasedAudioDescriptionEnabled() {
+ return base::FeatureList::IsEnabled(::features::kTextBasedAudioDescription);
+}
+
#if BUILDFLAG(IS_WIN)
const base::Feature kIChromeAccessible{"IChromeAccessible",
base::FEATURE_DISABLED_BY_DEFAULT};
@@ -145,21 +152,13 @@ bool IsEnhancedNetworkVoicesEnabled() {
return base::FeatureList::IsEnabled(::features::kEnhancedNetworkVoices);
}
-const base::Feature kAccessibilityOSSettingsReorganization{
- "AccessibilityOSSettingsReorganization", base::FEATURE_DISABLED_BY_DEFAULT};
-
-bool IsAccessibilityOSSettingsReorganizationEnabled() {
- return base::FeatureList::IsEnabled(
- ::features::kAccessibilityOSSettingsReorganization);
-}
const base::Feature kAccessibilityOSSettingsVisibility{
- "AccessibilityOSSettingsVisibility", base::FEATURE_ENABLED_BY_DEFAULT};
+ "AccessibilityOSSettingsVisibility", base::FEATURE_DISABLED_BY_DEFAULT};
bool IsAccessibilityOSSettingsVisibilityEnabled() {
return base::FeatureList::IsEnabled(
::features::kAccessibilityOSSettingsVisibility);
}
-
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
const base::Feature kAugmentExistingImageLabels{
@@ -215,7 +214,7 @@ bool IsScreenAIVisualAnnotationsEnabled() {
}
bool IsScreenAIServiceNeeded() {
- return IsScreenAIVisualAnnotationsEnabled() ||
+ return IsPdfOcrEnabled() || IsScreenAIVisualAnnotationsEnabled() ||
IsReadAnythingWithScreen2xEnabled();
}
diff --git a/chromium/ui/accessibility/accessibility_features.h b/chromium/ui/accessibility/accessibility_features.h
index cb7f3556d84..942d0e2d739 100644
--- a/chromium/ui/accessibility/accessibility_features.h
+++ b/chromium/ui/accessibility/accessibility_features.h
@@ -62,6 +62,14 @@ AX_BASE_EXPORT extern const base::Feature kAutoDisableAccessibility;
// accessibility API usage in that time.
AX_BASE_EXPORT bool IsAutoDisableAccessibilityEnabled();
+// Enables a setting that can turn on/off browser vocalization of 'descriptions'
+// tracks.
+AX_BASE_EXPORT extern const base::Feature kTextBasedAudioDescription;
+
+// Returns true if the setting to turn on text based audio descriptions is
+// enabled.
+AX_BASE_EXPORT bool IsTextBasedAudioDescriptionEnabled();
+
#if BUILDFLAG(IS_WIN)
// Enables an experimental Chrome-specific accessibility COM API
AX_BASE_EXPORT extern const base::Feature kIChromeAccessible;
@@ -118,13 +126,6 @@ AX_BASE_EXPORT extern const base::Feature kEnhancedNetworkVoices;
// Returns true if network-based voices are enabled in Select-to-speak.
AX_BASE_EXPORT bool IsEnhancedNetworkVoicesEnabled();
-// Enables improved Accessibility OS Settings reorganization.
-AX_BASE_EXPORT extern const base::Feature
- kAccessibilityOSSettingsReorganization;
-
-// Returns true if improved Accessibility OS Settings reorganization is enabled.
-AX_BASE_EXPORT bool IsAccessibilityOSSettingsReorganizationEnabled();
-
// Enables improved Accessibility OS Settings visibility.
AX_BASE_EXPORT extern const base::Feature kAccessibilityOSSettingsVisibility;
diff --git a/chromium/ui/accessibility/ax_common.h b/chromium/ui/accessibility/ax_common.h
index 0153b455d7c..74b9575a938 100644
--- a/chromium/ui/accessibility/ax_common.h
+++ b/chromium/ui/accessibility/ax_common.h
@@ -19,7 +19,9 @@
// SANITIZER_CHECK's use case is severe, but recoverable situations that need
// priority debugging. They trigger on Clusterfuzz, debug and sanitizer builds.
-#if defined(AX_FAIL_FAST_BUILD)
+// Prefer DCHECK() when enabled because it logs messages in the crash tool,
+// unlike CHECK().
+#if defined(AX_FAIL_FAST_BUILD) && !DCHECK_IS_ON()
#define SANITIZER_CHECK(val) CHECK(val)
#define SANITIZER_CHECK_EQ(val1, val2) CHECK_EQ(val1, val2)
#define SANITIZER_CHECK_NE(val1, val2) CHECK_NE(val1, val2)
@@ -38,6 +40,6 @@
#define SANITIZER_CHECK_GE(val1, val2) DCHECK_GE(val1, val2)
#define SANITIZER_CHECK_GT(val1, val2) DCHECK_GT(val1, val2)
#define SANITIZER_NOTREACHED() NOTREACHED()
-#endif // AX_FAIL_FAST_BUIL
+#endif // AX_FAIL_FAST_BUILD && !DCHECK_IS_ON()
#endif // UI_ACCESSIBILITY_AX_COMMON_H_
diff --git a/chromium/ui/accessibility/ax_computed_node_data.cc b/chromium/ui/accessibility/ax_computed_node_data.cc
index cbb3f6f33e0..1a6fe8b8d77 100644
--- a/chromium/ui/accessibility/ax_computed_node_data.cc
+++ b/chromium/ui/accessibility/ax_computed_node_data.cc
@@ -25,22 +25,59 @@ AXComputedNodeData::AXComputedNodeData(const AXNode& node) : owner_(&node) {}
AXComputedNodeData::~AXComputedNodeData() = default;
int AXComputedNodeData::GetOrComputeUnignoredIndexInParent() const {
- DCHECK(!owner_->IsIgnored());
+ DCHECK(!owner_->IsIgnored())
+ << "Ignored nodes cannot have an `unignored index in parent`.\n"
+ << *owner_;
if (unignored_index_in_parent_)
return *unignored_index_in_parent_;
- if (const AXNode* unignored_parent = owner_->GetUnignoredParent()) {
+ if (const AXNode* unignored_parent = SlowGetUnignoredParent()) {
+ DCHECK_NE(unignored_parent->id(), kInvalidAXNodeID)
+ << "All nodes should have a valid ID.\n"
+ << *owner_;
unignored_parent->GetComputedNodeData().ComputeUnignoredValues();
} else {
// This should be the root node and, by convention, we assign it an
// index-in-parent of 0.
unignored_index_in_parent_ = 0;
+ unignored_parent_id_ = kInvalidAXNodeID;
}
return *unignored_index_in_parent_;
}
+AXNodeID AXComputedNodeData::GetOrComputeUnignoredParentID() const {
+ if (unignored_parent_id_)
+ return *unignored_parent_id_;
+
+ if (const AXNode* unignored_parent = SlowGetUnignoredParent()) {
+ DCHECK_NE(unignored_parent->id(), kInvalidAXNodeID)
+ << "All nodes should have a valid ID.\n"
+ << *owner_;
+ unignored_parent_id_ = unignored_parent->id();
+ } else {
+ // This should be the root node and, by convention, we assign it an
+ // index-in-parent of 0.
+ DCHECK(!owner_->GetParent())
+ << "If `unignored_parent` is nullptr, then this should be the "
+ "rootnode, since in all trees the rootnode should be unignored.\n"
+ << *owner_;
+ unignored_index_in_parent_ = 0;
+ unignored_parent_id_ = kInvalidAXNodeID;
+ }
+ return *unignored_parent_id_;
+}
+
+AXNode* AXComputedNodeData::GetOrComputeUnignoredParent() const {
+ DCHECK(owner_->tree())
+ << "All nodes should be owned by an accessibility tree.\n"
+ << *owner_;
+ return owner_->tree()->GetFromId(GetOrComputeUnignoredParentID());
+}
+
int AXComputedNodeData::GetOrComputeUnignoredChildCount() const {
- DCHECK(!owner_->IsIgnored());
+ DCHECK(!owner_->IsIgnored())
+ << "Ignored nodes cannot have an `unignored child count`.\n"
+ << *owner_;
if (!unignored_child_count_)
ComputeUnignoredValues();
return *unignored_child_count_;
@@ -48,12 +85,20 @@ int AXComputedNodeData::GetOrComputeUnignoredChildCount() const {
const std::vector<AXNodeID>& AXComputedNodeData::GetOrComputeUnignoredChildIDs()
const {
- DCHECK(!owner_->IsIgnored());
+ DCHECK(!owner_->IsIgnored())
+ << "Ignored nodes cannot have `unignored child IDs`.\n"
+ << *owner_;
if (!unignored_child_ids_)
ComputeUnignoredValues();
return *unignored_child_ids_;
}
+bool AXComputedNodeData::GetOrComputeIsDescendantOfPlatformLeaf() const {
+ if (!is_descendant_of_leaf_)
+ ComputeIsDescendantOfPlatformLeaf();
+ return *is_descendant_of_leaf_;
+}
+
bool AXComputedNodeData::HasOrCanComputeAttribute(
const ax::mojom::StringAttribute attribute) const {
if (owner_->data().HasStringAttribute(attribute))
@@ -235,12 +280,18 @@ int AXComputedNodeData::GetOrComputeTextContentLengthUTF16() const {
}
void AXComputedNodeData::ComputeUnignoredValues(
+ AXNodeID unignored_parent_id,
int starting_index_in_parent) const {
+ DCHECK_GE(starting_index_in_parent, 0);
// Reset any previously computed values.
unignored_index_in_parent_ = absl::nullopt;
+ unignored_parent_id_ = absl::nullopt;
unignored_child_count_ = absl::nullopt;
unignored_child_ids_ = absl::nullopt;
+ AXNodeID unignored_parent_id_for_child = unignored_parent_id;
+ if (!owner_->IsIgnored())
+ unignored_parent_id_for_child = owner_->id();
int unignored_child_count = 0;
std::vector<AXNodeID> unignored_child_ids;
for (auto iter = owner_->AllChildrenBegin(); iter != owner_->AllChildrenEnd();
@@ -250,7 +301,8 @@ void AXComputedNodeData::ComputeUnignoredValues(
if (iter->IsIgnored()) {
// Skip the ignored node and recursively look at its children.
- computed_data.ComputeUnignoredValues(new_index_in_parent);
+ computed_data.ComputeUnignoredValues(unignored_parent_id_for_child,
+ new_index_in_parent);
DCHECK(computed_data.unignored_child_count_);
unignored_child_count += *computed_data.unignored_child_count_;
DCHECK(computed_data.unignored_child_ids_);
@@ -258,18 +310,50 @@ void AXComputedNodeData::ComputeUnignoredValues(
computed_data.unignored_child_ids_->begin(),
computed_data.unignored_child_ids_->end());
} else {
+ // Setting `unignored_index_in_parent_` and `unignored_parent_id_` is the
+ // responsibility of the parent node, since only the parent node can
+ // calculate these values. This is in contrast to `unignored_child_count_`
+ // and `unignored_child_ids_` that are only set if this method is called
+ // on the node itself.
+ computed_data.unignored_index_in_parent_ = new_index_in_parent;
+ if (unignored_parent_id_for_child != kInvalidAXNodeID)
+ computed_data.unignored_parent_id_ = unignored_parent_id_for_child;
+
++unignored_child_count;
unignored_child_ids.push_back(iter->id());
- computed_data.unignored_index_in_parent_ = new_index_in_parent;
}
}
+ if (unignored_parent_id != kInvalidAXNodeID)
+ unignored_parent_id_ = unignored_parent_id;
// Ignored nodes store unignored child information in order to propagate it to
- // their parents, but do not expose it directly.
+ // their parents, but do not expose it directly. The latter is guarded via a
+ // DCHECK.
unignored_child_count_ = unignored_child_count;
unignored_child_ids_ = unignored_child_ids;
}
+AXNode* AXComputedNodeData::SlowGetUnignoredParent() const {
+ AXNode* unignored_parent = owner_->GetParent();
+ while (unignored_parent && unignored_parent->IsIgnored())
+ unignored_parent = unignored_parent->GetParent();
+ return unignored_parent;
+}
+
+void AXComputedNodeData::ComputeIsDescendantOfPlatformLeaf() const {
+ is_descendant_of_leaf_ = false;
+ for (const AXNode* ancestor = GetOrComputeUnignoredParent(); ancestor;
+ ancestor =
+ ancestor->GetComputedNodeData().GetOrComputeUnignoredParent()) {
+ if (ancestor->GetComputedNodeData().is_descendant_of_leaf_.value_or(
+ false) ||
+ ancestor->IsLeaf()) {
+ is_descendant_of_leaf_ = true;
+ return;
+ }
+ }
+}
+
void AXComputedNodeData::ComputeLineOffsetsIfNeeded() const {
if (line_starts_ || line_ends_) {
DCHECK_EQ(line_starts_->size(), line_ends_->size());
@@ -392,7 +476,6 @@ std::string AXComputedNodeData::ComputeTextContentUTF8() const {
if (owner_->IsLeaf() && !is_atomic_text_field_with_descendants) {
switch (owner_->data().GetNameFrom()) {
case ax::mojom::NameFrom::kNone:
- case ax::mojom::NameFrom::kUninitialized:
// The accessible name is not displayed on screen, e.g. aria-label, or is
// not displayed directly inside the node, e.g. an associated label
// element.
diff --git a/chromium/ui/accessibility/ax_computed_node_data.h b/chromium/ui/accessibility/ax_computed_node_data.h
index fe7d526683b..c7e82426111 100644
--- a/chromium/ui/accessibility/ax_computed_node_data.h
+++ b/chromium/ui/accessibility/ax_computed_node_data.h
@@ -15,6 +15,7 @@
#include "ui/accessibility/ax_enums.mojom-forward.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_id_forward.h"
namespace ui {
@@ -37,6 +38,12 @@ class AX_EXPORT AXComputedNodeData final {
// associated node is ignored.
int GetOrComputeUnignoredIndexInParent() const;
+ // The lowest unignored parent. This value should be computed for all
+ // associated nodes, ignored and unignored. Only the rootnode should not have
+ // an unignored parent.
+ AXNodeID GetOrComputeUnignoredParentID() const;
+ AXNode* GetOrComputeUnignoredParent() const;
+
// If the associated node is unignored, i.e. exposed to the platform's
// assistive software, the number of its children that are also unignored.
// Naturally, this value is not defined when the associated node is ignored.
@@ -46,6 +53,11 @@ class AX_EXPORT AXComputedNodeData final {
// assistive software, the IDs of its children that are also unignored.
const std::vector<AXNodeID>& GetOrComputeUnignoredChildIDs() const;
+ // Whether the associated node is a descendant of a platform leaf. The set of
+ // platform leaves are the lowest nodes that are exposed to the platform's
+ // assistive software.
+ bool GetOrComputeIsDescendantOfPlatformLeaf() const;
+
// Given an accessibility attribute, returns whether the attribute is
// currently present in the node's data, or if it can always be computed on
// demand.
@@ -97,10 +109,20 @@ class AX_EXPORT AXComputedNodeData final {
int GetOrComputeTextContentLengthUTF16() const;
private:
- // Computes and caches the `unignored_index_in_parent_`,
+ // Computes and caches the `unignored_index_in_parent_`, `unignored_parent_`,
// `unignored_child_count_` and `unignored_child_ids_` for the associated
// node.
- void ComputeUnignoredValues(int starting_index_in_parent = 0) const;
+ void ComputeUnignoredValues(AXNodeID unignored_parent_id = kInvalidAXNodeID,
+ int starting_index_in_parent = 0) const;
+
+ // Walks up the accessibility tree from the associated node until it finds the
+ // lowest unignored ancestor.
+ AXNode* SlowGetUnignoredParent() const;
+
+ // Computes and caches (if not already in the cache) whether the associated
+ // node is a descendant of a platform leaf. The set of platform leaves are the
+ // lowest nodes that are exposed to the platform's assistive software.
+ void ComputeIsDescendantOfPlatformLeaf() const;
// Computes and caches (if not already in the cache) the character offsets
// where each line in the associated node's on-screen text starts and ends.
@@ -126,8 +148,10 @@ class AX_EXPORT AXComputedNodeData final {
const raw_ptr<const AXNode> owner_;
mutable absl::optional<int> unignored_index_in_parent_;
+ mutable absl::optional<AXNodeID> unignored_parent_id_;
mutable absl::optional<int> unignored_child_count_;
mutable absl::optional<std::vector<AXNodeID>> unignored_child_ids_;
+ mutable absl::optional<bool> is_descendant_of_leaf_;
mutable absl::optional<std::vector<int32_t>> line_starts_;
mutable absl::optional<std::vector<int32_t>> line_ends_;
mutable absl::optional<std::vector<int32_t>> sentence_starts_;
diff --git a/chromium/ui/accessibility/ax_computed_node_data_unittest.cc b/chromium/ui/accessibility/ax_computed_node_data_unittest.cc
index 5a5a60d15b9..c6e6e16d015 100644
--- a/chromium/ui/accessibility/ax_computed_node_data_unittest.cc
+++ b/chromium/ui/accessibility/ax_computed_node_data_unittest.cc
@@ -13,6 +13,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_id_forward.h"
#include "ui/accessibility/ax_position.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_data.h"
@@ -159,6 +160,8 @@ using ::testing::StrEq;
TEST_F(AXComputedNodeDataTest, UnignoredValues) {
const AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0);
+ const AXNode* static_text_0_0_ignored_node =
+ paragraph_0_node->GetChildAtIndex(0);
const AXNode* paragraph_1_ignored_node = root_node_->GetChildAtIndex(1);
const AXNode* static_text_1_0_node =
paragraph_1_ignored_node->GetChildAtIndex(0);
@@ -185,6 +188,65 @@ TEST_F(AXComputedNodeDataTest, UnignoredValues) {
.GetOrComputeUnignoredIndexInParent());
EXPECT_EQ(
+ kInvalidAXNodeID,
+ root_node_->GetComputedNodeData().GetOrComputeUnignoredParentID());
+ EXPECT_EQ(nullptr,
+ root_node_->GetComputedNodeData().GetOrComputeUnignoredParent());
+ EXPECT_FALSE(root_node_->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), paragraph_0_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(
+ root_node_,
+ paragraph_0_node->GetComputedNodeData().GetOrComputeUnignoredParent());
+ EXPECT_FALSE(paragraph_0_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(paragraph_0_node->id(),
+ static_text_0_0_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(paragraph_0_node,
+ static_text_0_0_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_TRUE(static_text_0_0_ignored_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), paragraph_1_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(root_node_, paragraph_1_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_FALSE(paragraph_1_ignored_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), static_text_1_0_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(root_node_, static_text_1_0_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_FALSE(static_text_1_0_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), paragraph_2_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(root_node_, paragraph_2_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_FALSE(paragraph_2_ignored_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), link_2_0_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(root_node_, link_2_0_ignored_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_FALSE(link_2_0_ignored_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), static_text_2_0_0_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(root_node_, static_text_2_0_0_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_FALSE(static_text_2_0_0_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+ EXPECT_EQ(root_node_->id(), static_text_2_0_1_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParentID());
+ EXPECT_EQ(root_node_, static_text_2_0_1_node->GetComputedNodeData()
+ .GetOrComputeUnignoredParent());
+ EXPECT_FALSE(static_text_2_0_1_node->GetComputedNodeData()
+ .GetOrComputeIsDescendantOfPlatformLeaf());
+
+ EXPECT_EQ(
4, root_node_->GetComputedNodeData().GetOrComputeUnignoredChildCount());
EXPECT_EQ(0, paragraph_0_node->GetComputedNodeData()
.GetOrComputeUnignoredChildCount());
@@ -267,7 +329,7 @@ TEST_F(AXComputedNodeDataTest, HasOrCanComputeAttribute) {
TEST_F(AXComputedNodeDataTest, GetOrComputeAttribute) {
// Embedded object behavior is dependant on platform. We manually set it to a
// specific value so that test results are consistent across platforms.
- testing::ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour(
+ ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour(
AXEmbeddedObjectBehavior::kSuppressCharacter);
// Line breaks should be inserted between each paragraph to mirror how HTML's
@@ -449,7 +511,7 @@ TEST_F(AXComputedNodeDataTest, GetOrComputeAttribute) {
TEST_F(AXComputedNodeDataTest, GetOrComputeTextContent) {
// Embedded object behavior is dependant on platform. We manually set it to a
// specific value so that test results are consistent across platforms.
- testing::ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour(
+ ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour(
AXEmbeddedObjectBehavior::kSuppressCharacter);
EXPECT_THAT(root_node_->GetComputedNodeData()
diff --git a/chromium/ui/accessibility/ax_enum_localization_util.cc b/chromium/ui/accessibility/ax_enum_localization_util.cc
new file mode 100644
index 00000000000..ee31b0f5f7c
--- /dev/null
+++ b/chromium/ui/accessibility/ax_enum_localization_util.cc
@@ -0,0 +1,40 @@
+// Copyright 2022 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_enum_localization_util.h"
+
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/strings/grit/ax_strings.h"
+
+namespace ui {
+
+std::string ToLocalizedString(ax::mojom::DefaultActionVerb action_verb) {
+ switch (action_verb) {
+ case ax::mojom::DefaultActionVerb::kNone:
+ return "";
+ case ax::mojom::DefaultActionVerb::kActivate:
+ return l10n_util::GetStringUTF8(IDS_AX_ACTIVATE_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kCheck:
+ return l10n_util::GetStringUTF8(IDS_AX_CHECK_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kClick:
+ return l10n_util::GetStringUTF8(IDS_AX_CLICK_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kClickAncestor:
+ return l10n_util::GetStringUTF8(IDS_AX_CLICK_ANCESTOR_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kJump:
+ return l10n_util::GetStringUTF8(IDS_AX_JUMP_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kOpen:
+ return l10n_util::GetStringUTF8(IDS_AX_OPEN_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kPress:
+ return l10n_util::GetStringUTF8(IDS_AX_PRESS_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kSelect:
+ return l10n_util::GetStringUTF8(IDS_AX_SELECT_ACTION_VERB);
+ case ax::mojom::DefaultActionVerb::kUncheck:
+ return l10n_util::GetStringUTF8(IDS_AX_UNCHECK_ACTION_VERB);
+ }
+
+ return "";
+}
+
+} // namespace ui
diff --git a/chromium/ui/accessibility/ax_enum_localization_util.h b/chromium/ui/accessibility/ax_enum_localization_util.h
new file mode 100644
index 00000000000..fce548b7f77
--- /dev/null
+++ b/chromium/ui/accessibility/ax_enum_localization_util.h
@@ -0,0 +1,21 @@
+// Copyright 2022 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.
+
+#ifndef UI_ACCESSIBILITY_AX_ENUM_LOCALIZATION_UTIL_H_
+#define UI_ACCESSIBILITY_AX_ENUM_LOCALIZATION_UTIL_H_
+
+#include <string>
+
+#include "ui/accessibility/ax_base_export.h"
+#include "ui/accessibility/ax_enums.mojom-forward.h"
+
+namespace ui {
+
+// Returns a localized string that corresponds to the name of the given action.
+AX_BASE_EXPORT std::string ToLocalizedString(
+ ax::mojom::DefaultActionVerb action_verb);
+
+} // namespace ui
+
+#endif // UI_ACCESSIBILITY_AX_ENUM_LOCALIZATION_UTIL_H_
diff --git a/chromium/ui/accessibility/ax_enum_util.cc b/chromium/ui/accessibility/ax_enum_util.cc
index 3a82bcb7f80..aee128d71f5 100644
--- a/chromium/ui/accessibility/ax_enum_util.cc
+++ b/chromium/ui/accessibility/ax_enum_util.cc
@@ -6,9 +6,6 @@
#include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/strings/grit/ax_strings.h"
-
namespace ui {
const char* ToString(ax::mojom::Event event) {
@@ -750,33 +747,6 @@ const char* ToString(ax::mojom::DefaultActionVerb default_action_verb) {
return "";
}
-std::string ToLocalizedString(ax::mojom::DefaultActionVerb action_verb) {
- switch (action_verb) {
- case ax::mojom::DefaultActionVerb::kNone:
- return "";
- case ax::mojom::DefaultActionVerb::kActivate:
- return l10n_util::GetStringUTF8(IDS_AX_ACTIVATE_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kCheck:
- return l10n_util::GetStringUTF8(IDS_AX_CHECK_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kClick:
- return l10n_util::GetStringUTF8(IDS_AX_CLICK_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kClickAncestor:
- return l10n_util::GetStringUTF8(IDS_AX_CLICK_ANCESTOR_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kJump:
- return l10n_util::GetStringUTF8(IDS_AX_JUMP_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kOpen:
- return l10n_util::GetStringUTF8(IDS_AX_OPEN_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kPress:
- return l10n_util::GetStringUTF8(IDS_AX_PRESS_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kSelect:
- return l10n_util::GetStringUTF8(IDS_AX_SELECT_ACTION_VERB);
- case ax::mojom::DefaultActionVerb::kUncheck:
- return l10n_util::GetStringUTF8(IDS_AX_UNCHECK_ACTION_VERB);
- }
-
- return "";
-}
-
const char* ToString(ax::mojom::Mutation mutation) {
switch (mutation) {
case ax::mojom::Mutation::kNone:
@@ -1552,8 +1522,6 @@ const char* ToString(ax::mojom::NameFrom name_from) {
switch (name_from) {
case ax::mojom::NameFrom::kNone:
return "none";
- case ax::mojom::NameFrom::kUninitialized:
- return "uninitialized";
case ax::mojom::NameFrom::kAttribute:
return "attribute";
case ax::mojom::NameFrom::kAttributeExplicitlyEmpty:
@@ -1581,6 +1549,8 @@ const char* ToString(ax::mojom::DescriptionFrom description_from) {
return "none";
case ax::mojom::DescriptionFrom::kAriaDescription:
return "ariaDescription";
+ case ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty:
+ return "attributeExplicitlyEmpty";
case ax::mojom::DescriptionFrom::kButtonLabel:
return "buttonLabel";
case ax::mojom::DescriptionFrom::kPopupElement:
diff --git a/chromium/ui/accessibility/ax_enum_util.h b/chromium/ui/accessibility/ax_enum_util.h
index f3c4605e9de..0bc02e0ad89 100644
--- a/chromium/ui/accessibility/ax_enum_util.h
+++ b/chromium/ui/accessibility/ax_enum_util.h
@@ -36,10 +36,6 @@ AX_BASE_EXPORT const char* ToString(ax::mojom::ActionFlags action_flags);
AX_BASE_EXPORT const char* ToString(
ax::mojom::DefaultActionVerb default_action_verb);
-// Returns a localized string that corresponds to the name of the given action.
-AX_BASE_EXPORT std::string ToLocalizedString(
- ax::mojom::DefaultActionVerb action_verb);
-
// ax::mojom::Mutation
AX_BASE_EXPORT const char* ToString(ax::mojom::Mutation mutation);
diff --git a/chromium/ui/accessibility/ax_enums.mojom b/chromium/ui/accessibility/ax_enums.mojom
index 671d00eaba7..34fcbe230ee 100644
--- a/chromium/ui/accessibility/ax_enums.mojom
+++ b/chromium/ui/accessibility/ax_enums.mojom
@@ -112,8 +112,7 @@ enum Event {
// Next value: 209
[Extensible, Stable, Uuid="d258eb73-e0cc-490c-b881-80ee11d3fec2"]
enum Role {
- // Used for role="none"/"presentation" -- ignored in platform tree.
- [Default]kNone = 0,
+ [Default]kUnknown = 181, // The role has not been set.
kAbbr = 1,
kAlert = 2,
kAlertDialog = 3,
@@ -281,6 +280,7 @@ enum Role {
kMenuListPopup = 128,
kMeter = 129,
kNavigation = 130,
+ kNone = 0, // Used for role="none"/"presentation"; ignored in platform tree.
kNote = 131,
kPane = 132,
kParagraph = 133,
@@ -333,7 +333,6 @@ enum Role {
kTree = 178,
kTreeGrid = 179,
kTreeItem = 180,
- kUnknown = 181,
kVideo = 182,
kWebView = 183,
kWindow = 184,
@@ -1172,7 +1171,6 @@ enum SortDirection {
enum NameFrom {
kNone,
- kUninitialized,
kAttribute, // E.g. aria-label.
kAttributeExplicitlyEmpty,
kCaption, // E.g. in the case of a table, from a caption element.
@@ -1183,17 +1181,55 @@ enum NameFrom {
kValue, // E.g. <input type="button" value="Button's name">.
};
+// The source of the accessible description. Used by some screen readers
+// to determine if and how the description should be presented to the user.
enum DescriptionFrom {
+ // No description has been provided. (See also kAttributeExplicitlyEmpty)
kNone,
+
+ // The description comes from a flat string, such as aria-description (in the
+ // case of web content) or provided by the View.
kAriaDescription,
- kButtonLabel, // HTML-AAM 5.2.2
+
+ // The description has been removed to improve accessibility. Example: The
+ // description normally provided by this View's tooltip contains text which
+ // is also present in this View's name. This could cause screen readers to
+ // speak the information twice, which is not desired. Therefore the
+ // description has been deliberately set to the empty string to prevent
+ // double presentation.
+ kAttributeExplicitlyEmpty,
+
+ // The description comes from the label/text of a button.
+ // See HTML-AAM's Accessible Name and Description Computation.
+ kButtonLabel,
+
+ // The description comes from some other object such as an element referenced
+ // by aria-describedby (in the case of web content), or another View present
+ // in the UI.
kRelatedElement,
+
+ // The description comes from a Ruby annotation.
kRubyAnnotation,
- kSummary, // HTML-AAM 5.8.2
+
+ // The description comes from the contents of a summary element.
+ // See HTML-AAM's Accessible Name and Description Computation.
+ kSummary,
+
+ // The description comes from the text of an SVG desc element.
+ // See SVG-AAM's Accessible Name and Description Computation.
kSvgDescElement,
- kTableCaption, // HTML-AAM 5.9.2
+
+ // The description comes from a table's caption element.
+ // See HTML-AAM's Accessible Name and Description Computation.
+ kTableCaption,
+
+ // The description comes from the title attribute (HTML), the title element
+ // (SVG), or a View's tooltip.
kTitle,
- kPopupElement, // E.g. |triggerpopup| attr pointing to `popup=hint`.
+
+ // The description comes from a non-tooltip popup, e.g. the |triggerpopup|
+ // attribute pointing to `popup=hint`.
+ kPopupElement,
};
// Next value: 4
diff --git a/chromium/ui/accessibility/ax_event_generator.cc b/chromium/ui/accessibility/ax_event_generator.cc
index e8135a1f6b6..c36911b1552 100644
--- a/chromium/ui/accessibility/ax_event_generator.cc
+++ b/chromium/ui/accessibility/ax_event_generator.cc
@@ -745,11 +745,6 @@ void AXEventGenerator::OnTreeDataChanged(AXTree* tree,
DCHECK_EQ(tree_, tree);
DCHECK(tree->root());
- if (new_tree_data.loaded && !old_tree_data.loaded &&
- ShouldFireLoadEvents(tree->root())) {
- AddEvent(tree->root(), Event::LOAD_COMPLETE);
- }
-
if (new_tree_data.title != old_tree_data.title)
AddEvent(tree->root(), Event::DOCUMENT_TITLE_CHANGED);
@@ -775,7 +770,7 @@ void AXEventGenerator::OnTreeDataChanged(AXTree* tree,
// fields, an event should still fire on the field where the selection
// ends.
if (AXNode* text_field = selection_focus->GetTextFieldAncestor())
- AddEvent(text_field, Event::SELECTION_IN_TEXT_FIELD_CHANGED);
+ AddEvent(text_field, Event::TEXT_SELECTION_CHANGED);
}
}
}
@@ -821,13 +816,6 @@ void AXEventGenerator::OnAtomicUpdateFinished(
DCHECK_EQ(tree_, tree);
DCHECK(tree->root());
- if (root_changed && ShouldFireLoadEvents(tree->root())) {
- if (tree->data().loaded)
- AddEvent(tree->root(), Event::LOAD_COMPLETE);
- else
- AddEvent(tree->root(), Event::LOAD_START);
- }
-
for (const auto& change : changes) {
DCHECK(change.node);
@@ -974,16 +962,6 @@ void AXEventGenerator::FireRelationSourceEvents(AXTree* tree,
});
}
-// Attempts to suppress load-related events that we presume no AT will be
-// interested in under any circumstances, such as pages which have no size.
-bool AXEventGenerator::ShouldFireLoadEvents(AXNode* node) {
- if (always_fire_load_complete_)
- return true;
-
- return node->data().relative_bounds.bounds.width() ||
- node->data().relative_bounds.bounds.height();
-}
-
void AXEventGenerator::TrimEventsDueToAncestorIgnoredChanged(
AXNode* node,
std::map<AXNode*, IgnoredChangedStatesBitset>&
@@ -1310,10 +1288,6 @@ const char* ToString(AXEventGenerator::Event event) {
return "liveRelevantChanged";
case AXEventGenerator::Event::LIVE_STATUS_CHANGED:
return "liveStatusChanged";
- case AXEventGenerator::Event::LOAD_COMPLETE:
- return "loadComplete";
- case AXEventGenerator::Event::LOAD_START:
- return "loadStart";
case AXEventGenerator::Event::MENU_ITEM_SELECTED:
return "menuItemSelected";
case ui::AXEventGenerator::Event::MENU_POPUP_END:
@@ -1366,8 +1340,8 @@ const char* ToString(AXEventGenerator::Event event) {
return "selectedChildrenChanged";
case AXEventGenerator::Event::SELECTED_VALUE_CHANGED:
return "selectedValueChanged";
- case AXEventGenerator::Event::SELECTION_IN_TEXT_FIELD_CHANGED:
- return "selectionInTextFieldChanged";
+ case AXEventGenerator::Event::TEXT_SELECTION_CHANGED:
+ return "textSelectionChanged";
case AXEventGenerator::Event::SET_SIZE_CHANGED:
return "setSizeChanged";
case AXEventGenerator::Event::SORT_CHANGED:
diff --git a/chromium/ui/accessibility/ax_event_generator.h b/chromium/ui/accessibility/ax_event_generator.h
index 3f3f0a45fdf..a7ec36be769 100644
--- a/chromium/ui/accessibility/ax_event_generator.h
+++ b/chromium/ui/accessibility/ax_event_generator.h
@@ -86,8 +86,6 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
LIVE_RELEVANT_CHANGED,
// Fired only on the root of the ARIA live region.
LIVE_STATUS_CHANGED,
- LOAD_COMPLETE,
- LOAD_START,
MENU_ITEM_SELECTED,
MENU_POPUP_END,
MENU_POPUP_START,
@@ -114,12 +112,12 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
SELECTED_CHANGED,
SELECTED_CHILDREN_CHANGED,
SELECTED_VALUE_CHANGED,
- SELECTION_IN_TEXT_FIELD_CHANGED,
SET_SIZE_CHANGED,
SORT_CHANGED,
STATE_CHANGED,
SUBTREE_CREATED,
TEXT_ATTRIBUTE_CHANGED,
+ TEXT_SELECTION_CHANGED,
VALUE_IN_TEXT_FIELD_CHANGED,
// This event is fired for the exact set of attributes that affect the
@@ -246,10 +244,6 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
// same order they were added.
void AddEvent(ui::AXNode* node, Event event);
- void set_always_fire_load_complete(bool val) {
- always_fire_load_complete_ = val;
- }
-
void AddEventsForTesting(const AXNode& node,
const std::set<EventParams>& events);
@@ -329,7 +323,6 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
void FireValueInTextFieldChangedEventIfNecessary(AXTree* tree,
AXNode* target_node);
void FireRelationSourceEvents(AXTree* tree, AXNode* target_node);
- bool ShouldFireLoadEvents(AXNode* node);
// Remove excessive events for a tree update containing node.
// We remove certain events on a node when it flips its IGNORED state to
@@ -364,8 +357,6 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
// previously unknown to ATs.
std::set<AXNodeID> nodes_to_suppress_parent_changed_on_;
- bool always_fire_load_complete_ = false;
-
// Helper that tracks live regions.
std::unique_ptr<AXLiveRegionTracker> live_region_tracker_;
diff --git a/chromium/ui/accessibility/ax_event_generator_unittest.cc b/chromium/ui/accessibility/ax_event_generator_unittest.cc
index bf9038fb3fb..0af42405df1 100644
--- a/chromium/ui/accessibility/ax_event_generator_unittest.cc
+++ b/chromium/ui/accessibility/ax_event_generator_unittest.cc
@@ -176,110 +176,6 @@ TEST(AXEventGeneratorTest, IterateThroughEmptyEventSets) {
EXPECT_TRUE(expected_event_map.empty());
}
-TEST(AXEventGeneratorTest, LoadCompleteSameTree) {
- AXTreeUpdate initial_state;
- initial_state.root_id = 1;
- initial_state.nodes.resize(1);
- initial_state.nodes[0].id = 1;
- initial_state.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
- initial_state.has_tree_data = true;
- AXTree tree(initial_state);
-
- AXEventGenerator event_generator(&tree);
- ASSERT_THAT(event_generator, IsEmpty());
- AXTreeUpdate load_complete_update = initial_state;
- load_complete_update.tree_data.loaded = true;
-
- ASSERT_TRUE(tree.Unserialize(load_complete_update));
- EXPECT_THAT(event_generator, UnorderedElementsAre(HasEventAtNode(
- AXEventGenerator::Event::LOAD_COMPLETE, 1)));
-}
-
-TEST(AXEventGeneratorTest, LoadCompleteNewTree) {
- AXTreeUpdate initial_state;
- initial_state.root_id = 1;
- initial_state.nodes.resize(1);
- initial_state.nodes[0].id = 1;
- initial_state.has_tree_data = true;
- initial_state.tree_data.loaded = true;
- AXTree tree(initial_state);
-
- AXEventGenerator event_generator(&tree);
- ASSERT_THAT(event_generator, IsEmpty());
- AXTreeUpdate load_complete_update;
- load_complete_update.root_id = 2;
- load_complete_update.nodes.resize(1);
- load_complete_update.nodes[0].id = 2;
- load_complete_update.nodes[0].relative_bounds.bounds =
- gfx::RectF(0, 0, 800, 600);
- load_complete_update.has_tree_data = true;
- load_complete_update.tree_data.loaded = true;
-
- ASSERT_TRUE(tree.Unserialize(load_complete_update));
- EXPECT_THAT(event_generator,
- UnorderedElementsAre(
- HasEventAtNode(AXEventGenerator::Event::LOAD_COMPLETE, 2),
- HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2)));
-
- // Load complete should not be emitted for sizeless roots.
- load_complete_update.root_id = 3;
- load_complete_update.nodes.resize(1);
- load_complete_update.nodes[0].id = 3;
- load_complete_update.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 0, 0);
- load_complete_update.has_tree_data = true;
- load_complete_update.tree_data.loaded = true;
-
- ASSERT_TRUE(tree.Unserialize(load_complete_update));
- EXPECT_THAT(event_generator,
- UnorderedElementsAre(
- HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3)));
-
- // TODO(accessibility): http://crbug.com/888758
- // Load complete should not be emitted for chrome-search URLs.
- load_complete_update.root_id = 4;
- load_complete_update.nodes.resize(1);
- load_complete_update.nodes[0].id = 4;
- load_complete_update.nodes[0].relative_bounds.bounds =
- gfx::RectF(0, 0, 800, 600);
- load_complete_update.nodes[0].AddStringAttribute(
- ax::mojom::StringAttribute::kUrl, "chrome-search://foo");
- load_complete_update.has_tree_data = true;
- load_complete_update.tree_data.loaded = true;
-
- ASSERT_TRUE(tree.Unserialize(load_complete_update));
- EXPECT_THAT(event_generator,
- UnorderedElementsAre(
- HasEventAtNode(AXEventGenerator::Event::LOAD_COMPLETE, 4),
- HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4)));
-}
-
-TEST(AXEventGeneratorTest, LoadStart) {
- AXTreeUpdate initial_state;
- initial_state.root_id = 1;
- initial_state.nodes.resize(1);
- initial_state.nodes[0].id = 1;
- initial_state.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
- initial_state.has_tree_data = true;
- AXTree tree(initial_state);
-
- AXEventGenerator event_generator(&tree);
- ASSERT_THAT(event_generator, IsEmpty());
- AXTreeUpdate load_start_update;
- load_start_update.root_id = 2;
- load_start_update.nodes.resize(1);
- load_start_update.nodes[0].id = 2;
- load_start_update.nodes[0].relative_bounds.bounds =
- gfx::RectF(0, 0, 800, 600);
- load_start_update.has_tree_data = true;
- load_start_update.tree_data.loaded = false;
-
- ASSERT_TRUE(tree.Unserialize(load_start_update));
- EXPECT_THAT(event_generator,
- UnorderedElementsAre(
- HasEventAtNode(AXEventGenerator::Event::LOAD_START, 2),
- HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2)));
-}
-
TEST(AXEventGeneratorTest, DocumentSelectionChanged) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
@@ -560,15 +456,14 @@ TEST(AXEventGeneratorTest, SelectionInTextFieldChanged) {
UnorderedElementsAre(
HasEventAtNode(AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED,
root.id),
- HasEventAtNode(
- AXEventGenerator::Event::SELECTION_IN_TEXT_FIELD_CHANGED,
- text_field.id)));
+ HasEventAtNode(AXEventGenerator::Event::TEXT_SELECTION_CHANGED,
+ text_field.id)));
}
event_generator.ClearEvents();
{
// A selection that does not include a text field in it should not raise the
- // "SELECTION_IN_TEXT_FIELD_CHANGED" event.
+ // "TEXT_SELECTION_CHANGED" event.
tree_data.sel_anchor_object_id = root.id;
tree_data.sel_anchor_offset = 0;
tree_data.sel_focus_object_id = root.id;
@@ -587,7 +482,7 @@ TEST(AXEventGeneratorTest, SelectionInTextFieldChanged) {
event_generator.ClearEvents();
{
// A selection that spans more than one node but which nevertheless ends on
- // a text field should still raise the "SELECTION_IN_TEXT_FIELD_CHANGED"
+ // a text field should still raise the "TEXT_SELECTION_CHANGED"
// event.
tree_data.sel_anchor_object_id = root.id;
tree_data.sel_anchor_offset = 0;
@@ -603,9 +498,8 @@ TEST(AXEventGeneratorTest, SelectionInTextFieldChanged) {
UnorderedElementsAre(
HasEventAtNode(AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED,
root.id),
- HasEventAtNode(
- AXEventGenerator::Event::SELECTION_IN_TEXT_FIELD_CHANGED,
- text_field.id)));
+ HasEventAtNode(AXEventGenerator::Event::TEXT_SELECTION_CHANGED,
+ text_field.id)));
}
}
diff --git a/chromium/ui/accessibility/ax_language_detection.h b/chromium/ui/accessibility/ax_language_detection.h
index eded0ff39f0..61ec4a9c627 100644
--- a/chromium/ui/accessibility/ax_language_detection.h
+++ b/chromium/ui/accessibility/ax_language_detection.h
@@ -7,11 +7,11 @@
#include <memory>
#include <string>
-#include <unordered_map>
-#include <unordered_set>
#include <utility>
#include <vector>
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "third_party/cld_3/src/src/nnet_language_identifier.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
@@ -150,7 +150,7 @@ class AX_EXPORT AXLanguageInfoStats {
friend class AXLanguageDetectionTestFixture;
// Store a count of the occurrences of a given language.
- std::unordered_map<std::string, int> lang_counts_;
+ base::flat_map<std::string, int> lang_counts_;
// Cache of last calculated top language results.
// A vector of pairs of (score, language) sorted by descending score.
@@ -201,7 +201,7 @@ class AX_EXPORT AXLanguageInfoStats {
// Set of top language detected for every node, used to generate the unique
// number of detected languages metric (LangsPerPage).
- std::unordered_set<std::string> unique_top_lang_detected_;
+ base::flat_set<std::string> unique_top_lang_detected_;
};
// AXLanguageDetectionObserver is registered as a change observer on an AXTree
diff --git a/chromium/ui/accessibility/ax_language_detection_unittest.cc b/chromium/ui/accessibility/ax_language_detection_unittest.cc
index 6b4bc538128..f7091ae2994 100644
--- a/chromium/ui/accessibility/ax_language_detection_unittest.cc
+++ b/chromium/ui/accessibility/ax_language_detection_unittest.cc
@@ -10,6 +10,7 @@
#include <memory>
#include "base/command_line.h"
+#include "base/containers/flat_set.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -103,7 +104,7 @@ class AXLanguageDetectionTestFixture : public testing::Test {
return tree.language_detection_manager->lang_info_stats_.count_overridden_;
}
- const std::unordered_set<std::string>& unique_top_lang_detected(
+ const base::flat_set<std::string>& unique_top_lang_detected(
AXTree& tree) const {
return tree.language_detection_manager->lang_info_stats_
.unique_top_lang_detected_;
@@ -672,8 +673,8 @@ TEST_F(AXLanguageDetectionTestStaticContent, MetricCollection) {
// There should be 4 unique languages (de, en, fr, es).
{
const auto& top_lang = unique_top_lang_detected(tree);
- const std::unordered_set<std::string> expected_top_lang = {"de", "en", "es",
- "fr"};
+ const base::flat_set<std::string> expected_top_lang = {"de", "en", "es",
+ "fr"};
EXPECT_EQ(top_lang, expected_top_lang);
}
histograms.ExpectUniqueSample("Accessibility.LanguageDetection.LangsPerPage",
@@ -1182,7 +1183,7 @@ TEST_F(AXLanguageDetectionTestDynamicContent, MetricCollection) {
// There should be 2 unique languages (fr, es).
{
auto top_lang = unique_top_lang_detected(tree);
- const std::unordered_set<std::string> expected_top_lang = {"es", "fr"};
+ const base::flat_set<std::string> expected_top_lang = {"es", "fr"};
EXPECT_EQ(top_lang, expected_top_lang);
}
// There should be a single (unique, 1) value for '2' unique languages.
diff --git a/chromium/ui/accessibility/ax_mode.h b/chromium/ui/accessibility/ax_mode.h
index 56306c72067..895fa7b9c96 100644
--- a/chromium/ui/accessibility/ax_mode.h
+++ b/chromium/ui/accessibility/ax_mode.h
@@ -71,7 +71,8 @@ class AX_BASE_EXPORT AXMode {
// Update this to include the last supported mode flag. If you add
// another, be sure to update the stream insertion operator for
- // logging and debugging.
+ // logging and debugging, as well as AccessibilityModeFlagEnum (and
+ // related metrics callsites, see: |ModeFlagHistogramValue|).
static constexpr uint32_t kLastModeFlag = 1 << 7;
constexpr AXMode() : flags_(0) {}
@@ -107,6 +108,9 @@ class AX_BASE_EXPORT AXMode {
UMA_AX_MODE_INLINE_TEXT_BOXES = 2,
UMA_AX_MODE_SCREEN_READER = 3,
UMA_AX_MODE_HTML = 4,
+ UMA_AX_MODE_HTML_METADATA = 5,
+ UMA_AX_MODE_LABEL_IMAGES = 6,
+ UMA_AX_MODE_PDF = 7,
// This must always be the last enum. It's okay for its value to
// increase, but none of the other enum values may change.
diff --git a/chromium/ui/accessibility/ax_node.cc b/chromium/ui/accessibility/ax_node.cc
index 75687a6e482..452b3907555 100644
--- a/chromium/ui/accessibility/ax_node.cc
+++ b/chromium/ui/accessibility/ax_node.cc
@@ -4,13 +4,10 @@
#include "ui/accessibility/ax_node.h"
-#include <string.h>
-
#include <algorithm>
-#include "base/debug/crash_logging.h"
-#include "base/debug/dump_without_crashing.h"
#include "base/no_destructor.h"
+#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
@@ -23,7 +20,6 @@
#include "ui/accessibility/ax_table_info.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_manager.h"
-#include "ui/accessibility/ax_tree_manager_map.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/transform.h"
@@ -50,7 +46,6 @@ AXNode::AXNode(AXNode::OwnerTree* tree,
AXNode::~AXNode() = default;
AXNodeData&& AXNode::TakeData() {
- has_data_been_taken_ = true;
return std::move(data_);
}
@@ -67,8 +62,7 @@ size_t AXNode::GetChildCount() const {
size_t AXNode::GetChildCountCrossingTreeBoundary() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
- const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
if (child_tree_manager)
return 1u;
@@ -85,8 +79,7 @@ size_t AXNode::GetUnignoredChildCountCrossingTreeBoundary() const {
// TODO(nektar): Should DCHECK that this node is not ignored.
DCHECK(!tree_->GetTreeUpdateInProgressState());
- const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
if (child_tree_manager) {
DCHECK_EQ(unignored_child_count_, 0u)
<< "A node cannot be hosting both a child tree and other nodes as "
@@ -107,8 +100,7 @@ AXNode* AXNode::GetChildAtIndex(size_t index) const {
AXNode* AXNode::GetChildAtIndexCrossingTreeBoundary(size_t index) const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
- const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
if (child_tree_manager) {
DCHECK_EQ(index, 0u)
<< "A node cannot be hosting both a child tree and other nodes as "
@@ -137,8 +129,7 @@ AXNode* AXNode::GetUnignoredChildAtIndexCrossingTreeBoundary(
// TODO(nektar): Should DCHECK that this node is not ignored.
DCHECK(!tree_->GetTreeUpdateInProgressState());
- const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
if (child_tree_manager) {
DCHECK_EQ(index, 0u)
<< "A node cannot be hosting both a child tree and other nodes as "
@@ -159,36 +150,17 @@ AXNode* AXNode::GetParentCrossingTreeBoundary() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
if (parent_)
return parent_;
- const AXTreeManager* manager =
- AXTreeManagerMap::GetInstance().GetManager(tree_->GetAXTreeID());
+ const AXTreeManager* manager = GetManager();
if (manager)
return manager->GetParentNodeFromParentTreeAsAXNode();
return nullptr;
}
AXNode* AXNode::GetUnignoredParent() const {
- // TODO(crbug.com/1237353): The following bailout is to test a hypothesis that
- // this function is sometimes called while a tree update is in progress or
- // when data_ isn't valid, which may be the cause of the crash detailed in
- // crbug.com/1237353. Once this hypothesis has been verified, replace the
- // bailout with a fix, which ideally should not call this function under
- // the circumstances hypothesized. Also, add back in the following line:
- // DCHECK(!tree_->GetTreeUpdateInProgressState());
- if (tree_->GetTreeUpdateInProgressState() || !IsDataValid()) {
- static auto* const crash_key = base::debug::AllocateCrashKeyString(
- "ax_node_err", base::debug::CrashKeySize::Size64);
- std::ostringstream error;
- error << "dataUninitialized=" << is_data_still_uninitialized_
- << " dataTaken=" << has_data_been_taken_
- << " treeUpdating=" << tree_->GetTreeUpdateInProgressState();
- base::debug::SetCrashKeyString(crash_key, error.str());
- base::debug::DumpWithoutCrashing();
- return nullptr;
- }
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
AXNode* unignored_parent = GetParent();
while (unignored_parent && unignored_parent->IsIgnored())
unignored_parent = unignored_parent->GetParent();
-
return unignored_parent;
}
@@ -196,8 +168,7 @@ AXNode* AXNode::GetUnignoredParentCrossingTreeBoundary() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
AXNode* unignored_parent = GetUnignoredParent();
if (!unignored_parent) {
- const AXTreeManager* manager =
- AXTreeManagerMap::GetInstance().GetManager(tree_->GetAXTreeID());
+ const AXTreeManager* manager = GetManager();
if (manager)
unignored_parent = manager->GetParentNodeFromParentTreeAsAXNode();
}
@@ -242,8 +213,7 @@ AXNode* AXNode::GetFirstUnignoredChild() const {
AXNode* AXNode::GetFirstUnignoredChildCrossingTreeBoundary() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
- const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
if (child_tree_manager)
return child_tree_manager->GetRootAsAXNode();
@@ -274,8 +244,7 @@ AXNode* AXNode::GetLastUnignoredChild() const {
AXNode* AXNode::GetLastUnignoredChildCrossingTreeBoundary() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
- const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ const AXTreeManager* child_tree_manager = AXTreeManager::ForChildTree(*this);
if (child_tree_manager)
return child_tree_manager->GetRootAsAXNode();
@@ -288,48 +257,104 @@ AXNode* AXNode::GetDeepestFirstChild() const {
return nullptr;
AXNode* deepest_child = GetFirstChild();
+ DCHECK(deepest_child);
while (deepest_child->GetChildCount())
deepest_child = deepest_child->GetFirstChild();
return deepest_child;
}
+AXNode* AXNode::GetDeepestFirstChildCrossingTreeBoundary() const {
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
+ if (!GetChildCountCrossingTreeBoundary())
+ return nullptr;
+
+ AXNode* deepest_child = GetFirstChildCrossingTreeBoundary();
+ DCHECK(deepest_child);
+ while (deepest_child->GetChildCountCrossingTreeBoundary())
+ deepest_child = deepest_child->GetFirstChildCrossingTreeBoundary();
+
+ return deepest_child;
+}
+
AXNode* AXNode::GetDeepestFirstUnignoredChild() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
if (!GetUnignoredChildCount())
return nullptr;
AXNode* deepest_child = GetFirstUnignoredChild();
+ DCHECK(deepest_child);
while (deepest_child->GetUnignoredChildCount())
deepest_child = deepest_child->GetFirstUnignoredChild();
return deepest_child;
}
+AXNode* AXNode::GetDeepestFirstUnignoredChildCrossingTreeBoundary() const {
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
+ if (!GetUnignoredChildCountCrossingTreeBoundary())
+ return nullptr;
+
+ AXNode* deepest_child = GetFirstUnignoredChildCrossingTreeBoundary();
+ DCHECK(deepest_child);
+ while (deepest_child->GetUnignoredChildCountCrossingTreeBoundary())
+ deepest_child = deepest_child->GetFirstUnignoredChildCrossingTreeBoundary();
+
+ return deepest_child;
+}
+
AXNode* AXNode::GetDeepestLastChild() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
if (!GetChildCount())
return nullptr;
AXNode* deepest_child = GetLastChild();
+ DCHECK(deepest_child);
while (deepest_child->GetChildCount())
deepest_child = deepest_child->GetLastChild();
return deepest_child;
}
+AXNode* AXNode::GetDeepestLastChildCrossingTreeBoundary() const {
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
+ if (!GetChildCountCrossingTreeBoundary())
+ return nullptr;
+
+ AXNode* deepest_child = GetLastChildCrossingTreeBoundary();
+ DCHECK(deepest_child);
+ while (deepest_child->GetChildCountCrossingTreeBoundary())
+ deepest_child = deepest_child->GetLastChildCrossingTreeBoundary();
+
+ return deepest_child;
+}
+
AXNode* AXNode::GetDeepestLastUnignoredChild() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
if (!GetUnignoredChildCount())
return nullptr;
AXNode* deepest_child = GetLastUnignoredChild();
+ DCHECK(deepest_child);
while (deepest_child->GetUnignoredChildCount())
deepest_child = deepest_child->GetLastUnignoredChild();
return deepest_child;
}
+AXNode* AXNode::GetDeepestLastUnignoredChildCrossingTreeBoundary() const {
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
+ if (!GetUnignoredChildCountCrossingTreeBoundary())
+ return nullptr;
+
+ AXNode* deepest_child = GetLastUnignoredChildCrossingTreeBoundary();
+ DCHECK(deepest_child);
+ while (deepest_child->GetUnignoredChildCountCrossingTreeBoundary())
+ deepest_child = deepest_child->GetLastUnignoredChildCrossingTreeBoundary();
+
+ return deepest_child;
+}
+
AXNode* AXNode::GetNextSibling() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
AXNode* parent = GetParent();
@@ -602,6 +627,11 @@ AXNode::UnignoredChildrenCrossingTreeBoundaryEnd() const {
return UnignoredChildCrossingTreeBoundaryIterator(this, nullptr);
}
+bool AXNode::CanFireEvents() const {
+ // TODO(nektar): Cache the `IsChildOfLeaf` state in `AXComputedNodeData`.
+ return !IsChildOfLeaf();
+}
+
absl::optional<int> AXNode::CompareTo(const AXNode& other) const {
if (this == &other)
return 0;
@@ -663,8 +693,6 @@ bool AXNode::IsLineBreak() const {
void AXNode::SetData(const AXNodeData& src) {
data_ = src;
- is_data_still_uninitialized_ = false;
- has_data_been_taken_ = false;
}
void AXNode::SetLocation(AXNodeID offset_container_id,
@@ -737,6 +765,78 @@ SkColor AXNode::ComputeColorAttribute(ax::mojom::IntAttribute attr) const {
return color;
}
+AXTreeManager* AXNode::GetManager() const {
+ return AXTreeManager::FromID(tree_->GetAXTreeID());
+}
+
+bool AXNode::HasVisibleCaretOrSelection() const {
+ const OwnerTree::Selection selection = GetSelection();
+ const AXNode* focus = tree()->GetFromId(selection.focus_object_id);
+ if (!focus || !focus->IsDescendantOf(this))
+ return false;
+
+ // A selection or the caret will be visible in a focused text field (including
+ // a content editable).
+ const AXNode* text_field = GetTextFieldAncestor();
+ if (text_field)
+ return true;
+
+ // The selection will be visible in non-editable content only if it is not
+ // collapsed.
+ return !selection.IsCollapsed();
+}
+
+AXNode::OwnerTree::Selection AXNode::GetSelection() const {
+ DCHECK(tree()) << "Cannot retrieve the current selection if the node is not "
+ "attached to an accessibility tree.\n"
+ << *this;
+ return tree()->GetSelection();
+}
+
+AXNode::OwnerTree::Selection AXNode::GetUnignoredSelection() const {
+ DCHECK(tree()) << "Cannot retrieve the current selection if the node is not "
+ "attached to an accessibility tree.\n"
+ << *this;
+ OwnerTree::Selection selection = tree()->GetUnignoredSelection();
+
+ // "selection.anchor_offset" and "selection.focus_ofset" might need to be
+ // adjusted if the anchor or the focus nodes include ignored children.
+ //
+ // TODO(nektar): Move this logic into its own "AXSelection" class and cache
+ // the result for faster reuse.
+ const AXNode* anchor = tree()->GetFromId(selection.anchor_object_id);
+ if (anchor && !anchor->IsLeaf()) {
+ DCHECK_GE(selection.anchor_offset, 0);
+ if (static_cast<size_t>(selection.anchor_offset) <
+ anchor->GetChildCount()) {
+ const AXNode* anchor_child =
+ anchor->GetChildAtIndex(selection.anchor_offset);
+ DCHECK(anchor_child);
+ selection.anchor_offset =
+ static_cast<int>(anchor_child->GetUnignoredIndexInParent());
+ } else {
+ selection.anchor_offset =
+ static_cast<int>(anchor->GetUnignoredChildCount());
+ }
+ }
+
+ const AXNode* focus = tree()->GetFromId(selection.focus_object_id);
+ if (focus && !focus->IsLeaf()) {
+ DCHECK_GE(selection.focus_offset, 0);
+ if (static_cast<size_t>(selection.focus_offset) < focus->GetChildCount()) {
+ const AXNode* focus_child =
+ focus->GetChildAtIndex(selection.focus_offset);
+ DCHECK(focus_child);
+ selection.focus_offset =
+ static_cast<int>(focus_child->GetUnignoredIndexInParent());
+ } else {
+ selection.focus_offset =
+ static_cast<int>(focus->GetUnignoredChildCount());
+ }
+ }
+ return selection;
+}
+
bool AXNode::HasStringAttribute(ax::mojom::StringAttribute attribute) const {
return GetComputedNodeData().HasOrCanComputeAttribute(attribute);
}
@@ -840,7 +940,7 @@ const std::string& AXNode::GetNameUTF8() const {
if (GetRole() == ax::mojom::Role::kPortal &&
GetNameFrom() == ax::mojom::NameFrom::kNone) {
const AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManagerForChildTree(*this);
+ AXTreeManager::ForChildTree(*this);
if (child_tree_manager)
node = child_tree_manager->GetRootAsAXNode();
}
@@ -955,6 +1055,110 @@ int AXNode::GetTextContentLengthUTF16() const {
return GetComputedNodeData().GetOrComputeTextContentLengthUTF16();
}
+gfx::RectF AXNode::GetTextContentRangeBoundsUTF8(int start_offset,
+ int end_offset) const {
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
+ DCHECK_LE(start_offset, end_offset)
+ << "Invalid `start_offset` and `end_offset`.\n"
+ << start_offset << ' ' << end_offset << "\nin\n"
+ << *this;
+ // Since we DCHECK that `start_offset` <= `end_offset`, there is no need to
+ // check whether `start_offset` is also in range.
+ if (end_offset > GetTextContentLengthUTF8())
+ return gfx::RectF();
+
+ // TODO(nektar): Update this to use
+ // "base/strings/utf_offset_string_conversions.h" which provides caching of
+ // offsets.
+ std::u16string out_trancated_string_utf16;
+ if (!base::UTF8ToUTF16(GetTextContentUTF8().data(),
+ base::checked_cast<size_t>(start_offset),
+ &out_trancated_string_utf16)) {
+ return gfx::RectF();
+ }
+ start_offset = base::checked_cast<int>(out_trancated_string_utf16.length());
+ if (!base::UTF8ToUTF16(GetTextContentUTF8().data(),
+ base::checked_cast<size_t>(end_offset),
+ &out_trancated_string_utf16)) {
+ return gfx::RectF();
+ }
+ end_offset = base::checked_cast<int>(out_trancated_string_utf16.length());
+ return GetTextContentRangeBoundsUTF16(start_offset, end_offset);
+}
+
+gfx::RectF AXNode::GetTextContentRangeBoundsUTF16(int start_offset,
+ int end_offset) const {
+ DCHECK(!tree_->GetTreeUpdateInProgressState());
+ DCHECK_LE(start_offset, end_offset)
+ << "Invalid `start_offset` and `end_offset`.\n"
+ << start_offset << ' ' << end_offset << "\nin\n"
+ << *this;
+ // Since we DCHECK that `start_offset` <= `end_offset`, there is no need to
+ // check whether `start_offset` is also in range.
+ if (end_offset > GetTextContentLengthUTF16())
+ return gfx::RectF();
+
+ const std::vector<int32_t>& character_offsets =
+ GetIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
+ int character_offsets_length =
+ base::checked_cast<int>(character_offsets.size());
+ // Charactger offsets are always based on the UTF-16 representation of the
+ // text.
+ if (character_offsets_length < GetTextContentLengthUTF16()) {
+ // Blink might not return pixel offsets for all characters. Clamp the
+ // character range to be within the number of provided pixels. Note that the
+ // first character always starts at pixel 0, so an offset for that character
+ // is not provided.
+ //
+ // TODO(accessibility): We need to fix this bug in Blink.
+ start_offset = std::min(start_offset, character_offsets_length);
+ end_offset = std::min(end_offset, character_offsets_length);
+ }
+
+ // TODO(nektar): Remove all this code and fix up the character offsets vector
+ // itself.
+ int start_pixel_offset =
+ start_offset > 0
+ ? character_offsets[base::checked_cast<size_t>(start_offset - 1)]
+ : 0;
+ int end_pixel_offset =
+ end_offset > 0
+ ? character_offsets[base::checked_cast<size_t>(end_offset - 1)]
+ : 0;
+ int max_pixel_offset = character_offsets_length > 0
+ ? character_offsets[character_offsets_length - 1]
+ : 0;
+ const gfx::RectF& node_bounds = data().relative_bounds.bounds;
+
+ gfx::RectF out_bounds;
+ switch (static_cast<ax::mojom::WritingDirection>(
+ GetIntAttribute(ax::mojom::IntAttribute::kTextDirection))) {
+ case ax::mojom::WritingDirection::kNone:
+ case ax::mojom::WritingDirection::kLtr:
+ out_bounds = gfx::RectF(start_pixel_offset, 0,
+ end_pixel_offset - start_pixel_offset,
+ node_bounds.height());
+ break;
+ case ax::mojom::WritingDirection::kRtl: {
+ int left = max_pixel_offset - end_pixel_offset;
+ int right = max_pixel_offset - start_pixel_offset;
+ out_bounds = gfx::RectF(left, 0, right - left, node_bounds.height());
+ break;
+ }
+ case ax::mojom::WritingDirection::kTtb:
+ out_bounds = gfx::RectF(0, start_pixel_offset, node_bounds.width(),
+ end_pixel_offset - start_pixel_offset);
+ break;
+ case ax::mojom::WritingDirection::kBtt: {
+ int top = max_pixel_offset - end_pixel_offset;
+ int bottom = max_pixel_offset - start_pixel_offset;
+ out_bounds = gfx::RectF(0, top, node_bounds.width(), bottom - top);
+ break;
+ }
+ }
+ return out_bounds;
+}
+
std::string AXNode::GetLanguage() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
// Walk up tree considering both detected and author declared languages.
@@ -1496,6 +1700,7 @@ bool AXNode::IsIgnoredContainerForOrderedSet() const {
GetRole() == ax::mojom::Role::kLabelText ||
GetRole() == ax::mojom::Role::kListItem ||
GetRole() == ax::mojom::Role::kGenericContainer ||
+ GetRole() == ax::mojom::Role::kScrollView ||
GetRole() == ax::mojom::Role::kUnknown;
}
@@ -1555,12 +1760,21 @@ AXNode* AXNode::GetOrderedSet() const {
return result;
}
-bool AXNode::IsDataValid() const {
- return !is_data_still_uninitialized_ && !has_data_been_taken_;
-}
-
bool AXNode::IsReadOnlySupported() const {
- return IsCellOrHeaderOfAriaGrid() || ui::IsReadOnlySupported(GetRole());
+ // Grid cells and headers can't be derived solely from the role (need to check
+ // the ancestor chain) so check this first.
+ if (IsCellOrHeaderOfAriaGrid())
+ return true;
+
+ // kPopUpButton is special in that it is the role Blink assigns for both
+ // role=button with aria-haspopup set, along with <select> elements.
+ // HTML AAM (https://w3c.github.io/html-aam/) maps <select> to the combobox
+ // role, which supports readonly, but readonly is not supported for button
+ // roles.
+ if (GetRole() == ax::mojom::Role::kPopUpButton && !IsMenuListPopUpButton())
+ return false;
+
+ return ui::IsReadOnlySupported(GetRole());
}
bool AXNode::IsReadOnlyOrDisabled() const {
@@ -1675,6 +1889,7 @@ bool AXNode::IsInvisibleOrIgnored() const {
}
bool AXNode::IsChildOfLeaf() const {
+ // TODO(nektar): Cache this state in `AXComputedNodeData`.
for (const AXNode* ancestor = GetUnignoredParent(); ancestor;
ancestor = ancestor->GetUnignoredParent()) {
if (ancestor->IsLeaf())
@@ -1829,11 +2044,9 @@ bool AXNode::IsInListMarker() const {
grandparent_node->GetRole() == ax::mojom::Role::kListMarker;
}
-bool AXNode::IsCollapsedMenuListPopUpButton() const {
- if (GetRole() != ax::mojom::Role::kPopUpButton ||
- !HasState(ax::mojom::State::kCollapsed)) {
+bool AXNode::IsMenuListPopUpButton() const {
+ if (GetRole() != ax::mojom::Role::kPopUpButton)
return false;
- }
// When a popup button contains a menu list popup, its only child is unignored
// and is a menu list popup.
@@ -1844,6 +2057,22 @@ bool AXNode::IsCollapsedMenuListPopUpButton() const {
return node->GetRole() == ax::mojom::Role::kMenuListPopup;
}
+bool AXNode::IsCollapsedMenuListPopUpButton() const {
+ if (!HasState(ax::mojom::State::kCollapsed))
+ return false;
+
+ return IsMenuListPopUpButton();
+}
+
+bool AXNode::IsRootWebAreaForPresentationalIframe() const {
+ if (!ui::IsPlatformDocument(GetRole()))
+ return false;
+ const AXNode* parent = GetUnignoredParentCrossingTreeBoundary();
+ if (!parent)
+ return false;
+ return parent->GetRole() == ax::mojom::Role::kIframePresentational;
+}
+
AXNode* AXNode::GetCollapsedMenuListPopUpButtonAncestor() const {
AXNode* node = GetOrderedSet();
@@ -1911,6 +2140,51 @@ AXNode* AXNode::GetTextFieldAncestor() const {
return nullptr;
}
+AXNode* AXNode::GetTextFieldInnerEditorElement() const {
+ if (!data().IsAtomicTextField() || !GetUnignoredChildCount())
+ return nullptr;
+
+ // Text fields wrap their static text and inline text boxes in generic
+ // containers, and some, like <input type="search">, wrap the wrapper as well.
+ // There are several incarnations of this structure.
+ // 1. An empty atomic text field:
+ // -- Generic container <-- there can be any number of these in a chain.
+ // However, some empty text fields have the below structure, with empty
+ // text boxes.
+ // 2. A single line, an atomic text field with some text in it:
+ // -- Generic container <-- there can be any number of these in a chain.
+ // ---- Static text
+ // ------ Inline text box children (zero or more)
+ // ---- Line Break (optional, a placeholder break element if the text data
+ // ends with '\n' or '\r')
+ // 3. A multiline textarea with some text in it:
+ // Similar to #2, but can repeat the static text, line break children
+ // multiple times.
+
+ AXNode* text_container = GetDeepestFirstUnignoredChild();
+ DCHECK(text_container) << "Unable to retrieve deepest unignored child on\n"
+ << *this;
+ // Non-empty text fields expose a set of static text objects with one or more
+ // inline text boxes each. On some platforms, such as Android, we don't enable
+ // inline text boxes, and only the static text objects are exposed.
+ if (text_container->GetRole() == ax::mojom::Role::kInlineTextBox)
+ text_container = text_container->GetUnignoredParent();
+
+ // Get the parent of the static text or the line break, if any; a line break
+ // is possible when the field contains a line break as its first character.
+ if (text_container->GetRole() == ax::mojom::Role::kStaticText ||
+ text_container->GetRole() == ax::mojom::Role::kLineBreak) {
+ text_container = text_container->GetUnignoredParent();
+ }
+
+ DCHECK(text_container) << "Unexpected unignored parent while computing text "
+ "field inner editor element on\n"
+ << *this;
+ if (text_container->GetRole() == ax::mojom::Role::kGenericContainer)
+ return text_container;
+ return nullptr;
+}
+
AXNode* AXNode::GetSelectionContainer() const {
for (AXNode* ancestor = const_cast<AXNode*>(this); ancestor;
ancestor = ancestor->GetUnignoredParent()) {
diff --git a/chromium/ui/accessibility/ax_node.h b/chromium/ui/accessibility/ax_node.h
index 2a5efdc2176..2a98042c335 100644
--- a/chromium/ui/accessibility/ax_node.h
+++ b/chromium/ui/accessibility/ax_node.h
@@ -24,11 +24,14 @@
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_text_attributes.h"
#include "ui/accessibility/ax_tree_id.h"
+#include "ui/gfx/geometry/rect_f.h"
namespace ui {
class AXComputedNodeData;
class AXTableInfo;
+class AXTreeManager;
+
struct AXLanguageInfo;
struct AXTreeData;
@@ -58,13 +61,27 @@ class AX_EXPORT AXNode final {
// be necessary.
class OwnerTree {
public:
- struct Selection {
- bool is_backward;
- AXNodeID anchor_object_id;
- int anchor_offset;
+ // A data structure that can store either the selected range of nodes in the
+ // accessibility tree, or the location of the caret in the case of a
+ // "collapsed" selection.
+ //
+ // TODO(nektar): Move this struct into its own file called "AXSelection",
+ // turn it into a class and make it compute the unignored selection given
+ // the `AXTreeData`.
+ struct Selection final {
+ // Returns true if this instance represents the position of the caret.
+ constexpr bool IsCollapsed() const {
+ return focus_object_id != kInvalidAXNodeID &&
+ anchor_object_id == focus_object_id &&
+ anchor_offset == focus_offset;
+ }
+
+ bool is_backward = false;
+ AXNodeID anchor_object_id = kInvalidAXNodeID;
+ int anchor_offset = -1;
ax::mojom::TextAffinity anchor_affinity;
- AXNodeID focus_object_id;
- int focus_offset;
+ AXNodeID focus_object_id = kInvalidAXNodeID;
+ int focus_offset = -1;
ax::mojom::TextAffinity focus_affinity;
};
@@ -80,8 +97,13 @@ class AX_EXPORT AXNode final {
virtual absl::optional<int> GetPosInSet(const AXNode& node) = 0;
virtual absl::optional<int> GetSetSize(const AXNode& node) = 0;
+ // See `AXTree::GetSelection`.
+ virtual Selection GetSelection() const = 0;
+ // See `AXTree::GetUnignoredSelection`.
virtual Selection GetUnignoredSelection() const = 0;
+ // See `AXTree::GetTreeUpdateInProgressState`.
virtual bool GetTreeUpdateInProgressState() const = 0;
+ // See `AXTree::HasPaginationSupport`.
virtual bool HasPaginationSupport() const = 0;
};
@@ -111,7 +133,7 @@ class AX_EXPORT AXNode final {
protected:
raw_ptr<const NodeType> parent_;
- raw_ptr<NodeType> child_;
+ raw_ptr<NodeType, DanglingUntriaged> child_;
};
// The constructor requires a parent, id, and index in parent, but
@@ -122,7 +144,7 @@ class AX_EXPORT AXNode final {
AXNode* parent,
AXNodeID id,
size_t index_in_parent,
- size_t unignored_index_in_parent = 0);
+ size_t unignored_index_in_parent = 0u);
virtual ~AXNode();
// Accessors.
@@ -167,10 +189,18 @@ class AX_EXPORT AXNode final {
AXNode* GetLastChildCrossingTreeBoundary() const;
AXNode* GetLastUnignoredChild() const;
AXNode* GetLastUnignoredChildCrossingTreeBoundary() const;
+
+ // TODO(accessibility): Consider renaming all "GetDeepest...Child" methods to
+ // "GetDeepest...Descendant".
AXNode* GetDeepestFirstChild() const;
+ AXNode* GetDeepestFirstChildCrossingTreeBoundary() const;
AXNode* GetDeepestFirstUnignoredChild() const;
+ AXNode* GetDeepestFirstUnignoredChildCrossingTreeBoundary() const;
AXNode* GetDeepestLastChild() const;
+ AXNode* GetDeepestLastChildCrossingTreeBoundary() const;
AXNode* GetDeepestLastUnignoredChild() const;
+ AXNode* GetDeepestLastUnignoredChildCrossingTreeBoundary() const;
+
AXNode* GetNextSibling() const;
AXNode* GetNextUnignoredSibling() const;
AXNode* GetPreviousSibling() const;
@@ -231,6 +261,12 @@ class AX_EXPORT AXNode final {
UnignoredChildCrossingTreeBoundaryIterator
UnignoredChildrenCrossingTreeBoundaryEnd() const;
+ // Returns true if this is a node on which accessibility events make sense to
+ // be fired. Events are not needed on nodes that will, for example, never
+ // appear in a tree that is visible to assistive software, as there will be no
+ // software to handle the event on the other end.
+ bool CanFireEvents() const;
+
// Returns an optional integer indicating the logical order of this node
// compared to another node, or returns an empty optional if the nodes are not
// comparable. Nodes are not comparable if they do not share a common
@@ -246,6 +282,8 @@ class AX_EXPORT AXNode final {
// be before (logically less) the node we visit later.
absl::optional<int> CompareTo(const AXNode& other) const;
+ bool IsDataValid() const { return data_.id != kInvalidAXNodeID; }
+
// Returns true if the node has any of the text related roles, including
// kStaticText, kInlineTextBox and kListMarker (for Legacy Layout). Does not
// include any text field roles.
@@ -292,6 +330,24 @@ class AX_EXPORT AXNode final {
SkColor ComputeColor() const;
SkColor ComputeBackgroundColor() const;
+ AXTreeManager* GetManager() const;
+
+ //
+ // Methods for accessing caret and selection information.
+ //
+
+ // Returns true if the caret is visible or there is an active selection inside
+ // this node.
+ bool HasVisibleCaretOrSelection() const;
+
+ // Gets the current selection from the accessibility tree.
+ OwnerTree::Selection GetSelection() const;
+
+ // Gets the unignored selection from the accessibility tree, meaning the
+ // selection whose endpoints are on unignored nodes. (An "ignored" node is a
+ // node that is not exposed to platform APIs: See `IsIgnored`.)
+ OwnerTree::Selection GetUnignoredSelection() const;
+
//
// Methods for accessing accessibility attributes including attributes that
// are computed on the browser side. (See `AXNodeData` and
@@ -388,6 +444,9 @@ class AX_EXPORT AXNode final {
bool HasHtmlAttribute(const char* attribute) const {
return data().HasHtmlAttribute(attribute);
}
+ std::u16string GetHtmlAttribute(const char* attribute) const {
+ return data().GetHtmlAttribute(attribute);
+ }
bool GetHtmlAttribute(const char* attribute, std::string* value) const {
return data().GetHtmlAttribute(attribute, value);
}
@@ -492,6 +551,18 @@ class AX_EXPORT AXNode final {
int GetTextContentLengthUTF8() const;
int GetTextContentLengthUTF16() const;
+ // Returns the smallest bounding box that can enclose the given range of
+ // characters in the node's text contents. The bounding box is relative to
+ // this node's coordinate system as specified in
+ // `AXNodeData::relative_bounds`.
+ //
+ // Note that `start_offset` and `end_offset` are either in UTF8 or UTF16 code
+ // units, not in grapheme clusters.
+ gfx::RectF GetTextContentRangeBoundsUTF8(int start_offset,
+ int end_offset) const;
+ gfx::RectF GetTextContentRangeBoundsUTF16(int start_offset,
+ int end_offset) const;
+
// Returns a string representing the language code.
//
// This will consider the language declared in the DOM, and may eventually
@@ -659,10 +730,18 @@ class AX_EXPORT AXNode final {
// of a list marker node. Returns false otherwise.
bool IsInListMarker() const;
+ // Returns true if this node is a popup button that is a parent to a menu list
+ // popup.
+ bool IsMenuListPopUpButton() const;
+
// Returns true if this node is a collapsed popup button that is parent to a
// menu list popup.
bool IsCollapsedMenuListPopUpButton() const;
+ // Returns true if this node is at the root of an accessibility tree that is
+ // hosted by a presentational iframe.
+ bool IsRootWebAreaForPresentationalIframe() const;
+
// Returns the popup button ancestor of this current node if any. The popup
// button needs to be the parent of a menu list popup and needs to be
// collapsed.
@@ -680,6 +759,11 @@ class AX_EXPORT AXNode final {
// contenteditable without the role, (see `AXNodeData::IsTextField()`).
AXNode* GetTextFieldAncestor() const;
+ // Get the native text field's deepest container; the lowest descendant that
+ // contains all its text. Returns nullptr if the text field is empty, or if it
+ // is not an atomic text field, (e.g., <input> or <textarea>).
+ AXNode* GetTextFieldInnerEditorElement() const;
+
// If this node is within a container (or widget) that supports either single
// or multiple selection, returns the node that represents the container.
AXNode* GetSelectionContainer() const;
@@ -698,10 +782,6 @@ class AX_EXPORT AXNode final {
// Finds and returns a pointer to ordered set containing node.
AXNode* GetOrderedSet() const;
- // Returns false if the |data_| is uninitialized or has been taken. Returns
- // true otherwise.
- bool IsDataValid() const;
-
// Returns true if the node supports the read-only attribute.
bool IsReadOnlySupported() const;
@@ -741,13 +821,6 @@ class AX_EXPORT AXNode final {
// computed by the tree's source, such as `content::BlinkAXTreeSource`.
AXNodeData data_;
- // Used to track when this object's data_ is valid. If either of these are
- // true, and data is accessed, there will be a crash.
- // TODO(crbug.com/1237353): Wrap this inside of an `#if DCHECK_IS_ON()` after
- // removing `DumpWithoutCrashing`.
- bool is_data_still_uninitialized_ = true;
- bool has_data_been_taken_ = false;
-
// See the class comment in "ax_hypertext.h" for an explanation of this
// member.
mutable AXHypertext hypertext_;
diff --git a/chromium/ui/accessibility/ax_node_data.cc b/chromium/ui/accessibility/ax_node_data.cc
index 639485b34ec..6179db7ebcf 100644
--- a/chromium/ui/accessibility/ax_node_data.cc
+++ b/chromium/ui/accessibility/ax_node_data.cc
@@ -257,9 +257,14 @@ AXNodeData::AXNodeData(AXNodeData&& other) {
html_attributes.swap(other.html_attributes);
child_ids.swap(other.child_ids);
relative_bounds = other.relative_bounds;
+
+ other.id = kInvalidAXNodeID;
+ other.role = ax::mojom::Role::kUnknown;
+ other.state = 0U;
+ other.actions = 0ULL;
}
-AXNodeData& AXNodeData::operator=(AXNodeData other) {
+AXNodeData& AXNodeData::operator=(const AXNodeData& other) {
id = other.id;
role = other.role;
state = other.state;
@@ -458,6 +463,12 @@ bool AXNodeData::GetHtmlAttribute(const char* attribute,
return false;
}
+std::u16string AXNodeData::GetHtmlAttribute(const char* attribute) const {
+ std::u16string value_utf16;
+ GetHtmlAttribute(attribute, &value_utf16);
+ return value_utf16;
+}
+
bool AXNodeData::GetHtmlAttribute(const char* attribute,
std::u16string* value) const {
std::string value_utf8;
@@ -607,9 +618,15 @@ AXTextAttributes AXNodeData::GetTextAttributes() const {
}
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.";
+ // Elements with role='presentation' have Role::kNone. They should not be
+ // named. This check is only relevant if the name is not empty.
+ // TODO(accessibility): It would be nice to have a means to set the name
+ // and role at the same time to avoid this ordering requirement.
+ DCHECK(role != ax::mojom::Role::kNone || name.empty())
+ << "Cannot set name to '" << name << "' on class: '"
+ << GetStringAttribute(ax::mojom::StringAttribute::kClassName)
+ << "' because a valid role is needed to set the default NameFrom "
+ "attribute. Set the role first.";
auto iter = std::find_if(string_attributes.begin(), string_attributes.end(),
[](const auto& string_attribute) {
@@ -623,6 +640,13 @@ void AXNodeData::SetName(const std::string& name) {
iter->second = name;
}
+ // It is possible for SetName to be called after
+ // SetNameExplicitlyEmpty.
+ if (!name.empty() &&
+ GetNameFrom() == ax::mojom::NameFrom::kAttributeExplicitlyEmpty) {
+ RemoveIntAttribute(ax::mojom::IntAttribute::kNameFrom);
+ }
+
if (HasIntAttribute(ax::mojom::IntAttribute::kNameFrom))
return;
// Since this method is mostly used by tests which don't always set the
@@ -650,6 +674,7 @@ void AXNodeData::SetName(const std::u16string& name) {
void AXNodeData::SetNameExplicitlyEmpty() {
SetNameFrom(ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
+ SetName(std::string());
}
void AXNodeData::SetDescription(const std::string& description) {
diff --git a/chromium/ui/accessibility/ax_node_data.h b/chromium/ui/accessibility/ax_node_data.h
index 9d2c3e605d9..6504ead5672 100644
--- a/chromium/ui/accessibility/ax_node_data.h
+++ b/chromium/ui/accessibility/ax_node_data.h
@@ -49,7 +49,7 @@ struct AX_BASE_EXPORT AXNodeData {
AXNodeData(const AXNodeData& other);
AXNodeData(AXNodeData&& other);
- AXNodeData& operator=(AXNodeData other);
+ AXNodeData& operator=(const AXNodeData& other);
// Accessing accessibility attributes:
//
@@ -104,6 +104,7 @@ struct AX_BASE_EXPORT AXNodeData {
bool HasHtmlAttribute(const char* attribute) const;
bool GetHtmlAttribute(const char* attribute, std::string* value) const;
+ std::u16string GetHtmlAttribute(const char* attribute) const;
bool GetHtmlAttribute(const char* attribute, std::u16string* value) const;
//
diff --git a/chromium/ui/accessibility/ax_node_data_unittest.cc b/chromium/ui/accessibility/ax_node_data_unittest.cc
index a9bfa61272e..2092adb21cf 100644
--- a/chromium/ui/accessibility/ax_node_data_unittest.cc
+++ b/chromium/ui/accessibility/ax_node_data_unittest.cc
@@ -10,6 +10,7 @@
#include <utility>
#include "base/containers/contains.h"
+#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
@@ -363,6 +364,43 @@ TEST(AXNodeDataTest, SupportsExpandCollapse) {
}
}
+TEST(AXNodeDataTest, SetName) {
+ AXNodeData data;
+ // SetName should not be called on a role of kNone. That role is used for
+ // presentational objects which should not be included in the accessibility
+ // tree. This is enforced by a DCHECK.
+ data.role = ax::mojom::Role::kNone;
+ EXPECT_DCHECK_DEATH(data.SetName("role is presentational"));
+
+ // For roles other than text, setting the name should result in the NameFrom
+ // source being kAttribute.
+ data.role = ax::mojom::Role::kButton;
+ data.SetName("foo");
+ EXPECT_EQ("foo", data.GetStringAttribute(ax::mojom::StringAttribute::kName));
+ EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kAttribute);
+
+ // TODO(accessibility): The static text role should have a NameFrom source of
+ // kContents. But nothing clears the NameFrom if the role of an existing
+ // object changes because currently there is no AXNodeData::SetRole method.
+ data.role = ax::mojom::Role::kStaticText;
+ data.SetName("bar");
+ EXPECT_EQ("bar", data.GetStringAttribute(ax::mojom::StringAttribute::kName));
+ EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kAttribute);
+
+ data.RemoveIntAttribute(ax::mojom::IntAttribute::kNameFrom);
+ data.SetName("baz");
+ EXPECT_EQ("baz", data.GetStringAttribute(ax::mojom::StringAttribute::kName));
+ EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kContents);
+
+ data.SetNameExplicitlyEmpty();
+ EXPECT_EQ("", data.GetStringAttribute(ax::mojom::StringAttribute::kName));
+ EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
+
+ data.SetName("foo");
+ EXPECT_EQ("foo", data.GetStringAttribute(ax::mojom::StringAttribute::kName));
+ EXPECT_EQ(data.GetNameFrom(), ax::mojom::NameFrom::kContents);
+}
+
TEST(AXNodeDataTest, BitFieldsSanityCheck) {
EXPECT_LT(static_cast<size_t>(ax::mojom::State::kMaxValue),
sizeof(AXNodeData::state) * 8);
diff --git a/chromium/ui/accessibility/ax_node_position.cc b/chromium/ui/accessibility/ax_node_position.cc
index 6af347c4e02..6f41e0724de 100644
--- a/chromium/ui/accessibility/ax_node_position.cc
+++ b/chromium/ui/accessibility/ax_node_position.cc
@@ -22,8 +22,6 @@ AXEmbeddedObjectBehavior g_ax_embedded_object_behavior =
AXEmbeddedObjectBehavior::kSuppressCharacter;
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK)
-namespace testing {
-
ScopedAXEmbeddedObjectBehaviorSetter::ScopedAXEmbeddedObjectBehaviorSetter(
AXEmbeddedObjectBehavior behavior) {
prev_behavior_ = g_ax_embedded_object_behavior;
@@ -34,8 +32,6 @@ ScopedAXEmbeddedObjectBehaviorSetter::~ScopedAXEmbeddedObjectBehaviorSetter() {
g_ax_embedded_object_behavior = prev_behavior_;
}
-} // namespace testing
-
std::string ToString(const AXPositionKind kind) {
static constexpr auto kKindToString =
base::MakeFixedFlatMap<AXPositionKind, const char*>(
diff --git a/chromium/ui/accessibility/ax_node_position_fuzzer.cc b/chromium/ui/accessibility/ax_node_position_fuzzer.cc
new file mode 100644
index 00000000000..6c58f8254a6
--- /dev/null
+++ b/chromium/ui/accessibility/ax_node_position_fuzzer.cc
@@ -0,0 +1,410 @@
+// Copyright 2022 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_enums.mojom.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/ax_position.h"
+#include "ui/accessibility/ax_range.h"
+#include "ui/accessibility/ax_role_properties.h"
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_data.h"
+#include "ui/accessibility/ax_tree_fuzzer_util.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_update.h"
+#include "ui/accessibility/test_ax_tree_manager.h"
+
+// Max amount of fuzz data needed to create the next position
+const size_t kNextNodePositionMaxDataSize = 4;
+
+// Min/Max node size for generated tree.
+const size_t kMinNodeCount = 10;
+const size_t kMaxNodeCount = kMinNodeCount + 50;
+
+// Min fuzz data needed for fuzzer to function.
+// Tree of minimum size with text for each node + 2 positions.
+const size_t kMinFuzzDataSize =
+ kMinNodeCount * AXTreeFuzzerGenerator::kMinimumNewNodeFuzzDataSize +
+ kMinNodeCount * AXTreeFuzzerGenerator::kMinTextFuzzDataSize +
+ 2 * kNextNodePositionMaxDataSize;
+// Cap fuzz data to avoid slowness.
+const size_t kMaxFuzzDataSize = 20000;
+
+using TestPositionType =
+ std::unique_ptr<ui::AXPosition<ui::AXNodePosition, ui::AXNode>>;
+using TestPositionRange =
+ ui::AXRange<ui::AXPosition<ui::AXNodePosition, ui::AXNode>>;
+
+// Helper to create positions in the given tree.
+class AXNodePositionFuzzerGenerator {
+ public:
+ AXNodePositionFuzzerGenerator(ui::AXTree* tree,
+ ui::AXNodeID max_id,
+ FuzzerData& fuzzer_data);
+
+ TestPositionType CreateNewPosition();
+ TestPositionType GenerateNextPosition(TestPositionType& current_position,
+ TestPositionType& previous_position);
+
+ static void CallPositionAPIs(TestPositionType& position,
+ TestPositionType& other_position);
+
+ private:
+ static ax::mojom::MoveDirection GenerateMoveDirection(unsigned char byte);
+ static ax::mojom::TextAffinity GenerateTextAffinity(unsigned char byte);
+ static ui::AXPositionKind GeneratePositionKind(unsigned char byte);
+ static ui::AXPositionAdjustmentBehavior GenerateAdjustmentBehavior(
+ unsigned char byte);
+ static ui::AXMovementOptions GenerateMovementOptions(
+ unsigned char behavior_byte,
+ unsigned char detection_byte);
+
+ TestPositionType CreateNewPosition(ui::AXNodeID anchor_id,
+ int child_index_or_text_offset,
+ ui::AXPositionKind position_kind,
+ ax::mojom::TextAffinity affinity);
+
+ ui::AXTree* tree_;
+ const ui::AXNodeID max_id_;
+ FuzzerData& fuzzer_data_;
+};
+
+AXNodePositionFuzzerGenerator::AXNodePositionFuzzerGenerator(
+ ui::AXTree* tree,
+ ui::AXNodeID max_id,
+ FuzzerData& fuzzer_data)
+ : tree_(tree), max_id_(max_id), fuzzer_data_(fuzzer_data) {}
+
+TestPositionType AXNodePositionFuzzerGenerator::CreateNewPosition() {
+ return CreateNewPosition(fuzzer_data_.NextByte(), fuzzer_data_.NextByte(),
+ GeneratePositionKind(fuzzer_data_.NextByte()),
+ GenerateTextAffinity(fuzzer_data_.NextByte()));
+}
+
+TestPositionType AXNodePositionFuzzerGenerator::CreateNewPosition(
+ ui::AXNodeID anchor_id,
+ int child_index_or_text_offset,
+ ui::AXPositionKind position_kind,
+ ax::mojom::TextAffinity affinity) {
+ // To ensure that anchor_id is between |ui::kInvalidAXNodeID| and the max ID
+ // of the tree (non-inclusive), get a number [0, max_id - 1) and then shift by
+ // 1 to get [1, max_id)
+ anchor_id = (anchor_id % (max_id_ - 1)) + 1;
+ ui::AXNode* anchor = tree_->GetFromId(anchor_id);
+ DCHECK(anchor);
+
+ switch (position_kind) {
+ case ui::AXPositionKind::TREE_POSITION:
+ // Avoid division by zero in the case where the node has no children.
+ child_index_or_text_offset =
+ anchor->GetChildCount()
+ ? child_index_or_text_offset % anchor->GetChildCount()
+ : 0;
+ return ui::AXNodePosition::CreateTreePosition(
+ tree_->GetAXTreeID(), anchor_id, child_index_or_text_offset);
+ case ui::AXPositionKind::TEXT_POSITION: {
+ // Avoid division by zero in the case where the node has no text.
+ child_index_or_text_offset =
+ anchor->GetTextContentLengthUTF16()
+ ? child_index_or_text_offset % anchor->GetTextContentLengthUTF16()
+ : 0;
+ return ui::AXNodePosition::CreateTextPosition(
+ tree_->GetAXTreeID(), anchor_id, child_index_or_text_offset,
+ affinity);
+ case ui::AXPositionKind::NULL_POSITION:
+ NOTREACHED();
+ return ui::AXNodePosition::CreateNullPosition();
+ }
+ }
+}
+
+ax::mojom::MoveDirection AXNodePositionFuzzerGenerator::GenerateMoveDirection(
+ unsigned char byte) {
+ constexpr unsigned char max_value =
+ static_cast<unsigned char>(ax::mojom::MoveDirection::kMaxValue);
+ return static_cast<ax::mojom::MoveDirection>(byte % max_value);
+}
+
+ax::mojom::TextAffinity AXNodePositionFuzzerGenerator::GenerateTextAffinity(
+ unsigned char byte) {
+ constexpr unsigned char max_value =
+ static_cast<unsigned char>(ax::mojom::TextAffinity::kMaxValue);
+ return static_cast<ax::mojom::TextAffinity>(byte % max_value);
+}
+
+ui::AXPositionKind AXNodePositionFuzzerGenerator::GeneratePositionKind(
+ unsigned char byte) {
+ return byte % 2 ? ui::AXPositionKind::TREE_POSITION
+ : ui::AXPositionKind::TEXT_POSITION;
+}
+
+ui::AXPositionAdjustmentBehavior
+AXNodePositionFuzzerGenerator::GenerateAdjustmentBehavior(unsigned char byte) {
+ return byte % 2 ? ui::AXPositionAdjustmentBehavior::kMoveBackward
+ : ui::AXPositionAdjustmentBehavior::kMoveForward;
+}
+
+ui::AXMovementOptions AXNodePositionFuzzerGenerator::GenerateMovementOptions(
+ unsigned char behavior_byte,
+ unsigned char detection_byte) {
+ return ui::AXMovementOptions(
+ static_cast<ui::AXBoundaryBehavior>(behavior_byte % 3),
+ static_cast<ui::AXBoundaryDetection>(detection_byte % 3));
+}
+
+TestPositionType AXNodePositionFuzzerGenerator::GenerateNextPosition(
+ TestPositionType& current_position,
+ TestPositionType& previous_position) {
+ switch (fuzzer_data_.NextByte() % 55) {
+ case 0:
+ default:
+ return CreateNewPosition();
+ case 1:
+ return current_position->AsValidPosition();
+ case 2:
+ return current_position->AsTreePosition();
+ case 3:
+ return current_position->AsLeafTreePosition();
+ case 4:
+ return current_position->AsTextPosition();
+ case 5:
+ return current_position->AsLeafTextPosition();
+ case 6:
+ return current_position->AsDomSelectionPosition();
+ case 7:
+ return current_position->AsUnignoredPosition(
+ GenerateAdjustmentBehavior(fuzzer_data_.NextByte()));
+ case 8:
+ return current_position->CreateAncestorPosition(
+ previous_position->GetAnchor(),
+ GenerateMoveDirection(fuzzer_data_.NextByte()));
+ case 9:
+ return current_position->CreatePositionAtStartOfAnchor();
+ case 10:
+ return current_position->CreatePositionAtEndOfAnchor();
+ case 11:
+ return current_position->CreatePositionAtStartOfAXTree();
+ case 12:
+ return current_position->CreatePositionAtEndOfAXTree();
+ case 13:
+ return current_position->CreatePositionAtStartOfContent();
+ case 14:
+ return current_position->CreatePositionAtEndOfContent();
+ case 15:
+ return current_position->CreateChildPositionAt(fuzzer_data_.NextByte() %
+ 10);
+ case 16:
+ return current_position->CreateParentPosition(
+ GenerateMoveDirection(fuzzer_data_.NextByte()));
+ case 17:
+ return current_position->CreateNextLeafTreePosition();
+ case 18:
+ return current_position->CreatePreviousLeafTreePosition();
+ case 19:
+ return current_position->CreateNextLeafTextPosition();
+ case 20:
+ return current_position->CreatePreviousLeafTextPosition();
+ case 21:
+ return current_position->AsLeafTextPositionBeforeCharacter();
+ case 22:
+ return current_position->AsLeafTextPositionAfterCharacter();
+ case 23:
+ return current_position->CreatePreviousCharacterPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 24:
+ return current_position->CreateNextWordStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 25:
+ return current_position->CreatePreviousWordStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 26:
+ return current_position->CreateNextWordEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 27:
+ return current_position->CreatePreviousWordEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 28:
+ return current_position->CreateNextLineStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 29:
+ return current_position->CreatePreviousLineStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 30:
+ return current_position->CreateNextLineEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 31:
+ return current_position->CreatePreviousLineEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 32:
+ return current_position->CreateNextFormatStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 33:
+ return current_position->CreatePreviousFormatStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 34:
+ return current_position->CreateNextFormatEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 35:
+ return current_position->CreatePreviousFormatEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 36:
+ return current_position->CreateNextSentenceStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 37:
+ return current_position->CreatePreviousSentenceStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 38:
+ return current_position->CreateNextSentenceEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 39:
+ return current_position->CreatePreviousSentenceEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 40:
+ return current_position->CreateNextParagraphStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 41:
+ return current_position
+ ->CreateNextParagraphStartPositionSkippingEmptyParagraphs(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 42:
+ return current_position->CreatePreviousParagraphStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 43:
+ return current_position
+ ->CreatePreviousParagraphStartPositionSkippingEmptyParagraphs(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 44:
+ return current_position->CreateNextParagraphEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 45:
+ return current_position->CreatePreviousParagraphEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 46:
+ return current_position->CreateNextPageStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 47:
+ return current_position->CreatePreviousPageStartPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 48:
+ return current_position->CreateNextPageEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 49:
+ return current_position->CreatePreviousPageEndPosition(
+ GenerateMovementOptions(fuzzer_data_.NextByte(),
+ fuzzer_data_.NextByte()));
+ case 52:
+ return current_position->CreateNextAnchorPosition();
+ case 53:
+ return current_position->CreatePreviousAnchorPosition();
+ case 54:
+ return current_position->LowestCommonAncestorPosition(
+ *previous_position, GenerateMoveDirection(fuzzer_data_.NextByte()));
+ }
+}
+
+void AXNodePositionFuzzerGenerator::CallPositionAPIs(
+ TestPositionType& position,
+ TestPositionType& other_position) {
+ // Call APIs on the created position. We don't care about any of the results,
+ // we just want to make sure none of these crash or hang.
+ std::ignore = position->GetAnchor();
+ std::ignore = position->GetAnchorSiblingCount();
+ std::ignore = position->IsIgnored();
+ std::ignore = position->IsLeaf();
+ std::ignore = position->IsValid();
+ std::ignore = position->AtStartOfWord();
+ std::ignore = position->AtEndOfWord();
+ std::ignore = position->AtStartOfLine();
+ std::ignore = position->AtEndOfLine();
+ std::ignore = position->GetFormatStartBoundaryType();
+ std::ignore = position->GetFormatEndBoundaryType();
+ std::ignore = position->AtStartOfSentence();
+ std::ignore = position->AtEndOfSentence();
+ std::ignore = position->AtStartOfParagraph();
+ std::ignore = position->AtEndOfParagraph();
+ std::ignore = position->AtStartOfInlineBlock();
+ std::ignore = position->AtStartOfPage();
+ std::ignore = position->AtEndOfPage();
+ std::ignore = position->AtStartOfAXTree();
+ std::ignore = position->AtEndOfAXTree();
+ std::ignore = position->AtStartOfContent();
+ std::ignore = position->AtEndOfContent();
+ std::ignore = position->LowestCommonAnchor(*other_position);
+ std::ignore = position->CompareTo(*other_position);
+ std::ignore = position->GetText();
+ std::ignore = position->IsPointingToLineBreak();
+ std::ignore = position->IsInTextObject();
+ std::ignore = position->IsInWhiteSpace();
+ std::ignore = position->MaxTextOffset();
+ std::ignore = position->GetRole();
+}
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
+ if (size < kMinFuzzDataSize || size > kMaxFuzzDataSize)
+ return 0;
+ AXTreeFuzzerGenerator generator;
+ FuzzerData fuzz_data(data, size);
+ const size_t node_count =
+ kMinNodeCount + fuzz_data.NextByte() % kMaxNodeCount;
+ generator.GenerateInitialUpdate(fuzz_data, node_count);
+ ui::AXNodeID max_id = generator.GetMaxAssignedID();
+
+ ui::AXTree* tree = generator.GetTree();
+
+ // Run with --v=1 to aid in debugging a specific crash.
+ VLOG(1) << tree->ToString();
+
+ // Check to ensure there is enough fuzz data to create two positions.
+ if (fuzz_data.RemainingBytes() < kNextNodePositionMaxDataSize * 2)
+ return 0;
+ AXNodePositionFuzzerGenerator position_fuzzer(tree, max_id, fuzz_data);
+
+ // Having two positions allows us to test "more interesting" APIs that do work
+ // on multiple positions.
+ TestPositionType previous_position = position_fuzzer.CreateNewPosition();
+ TestPositionType position = position_fuzzer.CreateNewPosition();
+
+ while (fuzz_data.RemainingBytes() > kNextNodePositionMaxDataSize) {
+ // Run with --v=1 to aid in debugging a specific crash.
+ VLOG(1) << position->ToString() << fuzz_data.RemainingBytes();
+
+ position_fuzzer.CallPositionAPIs(position, previous_position);
+
+ // Determine next position to test:
+ TestPositionType next_position =
+ position_fuzzer.GenerateNextPosition(position, previous_position);
+ previous_position = std::move(position);
+ position = std::move(next_position);
+ }
+
+ return 0;
+}
diff --git a/chromium/ui/accessibility/ax_node_position_unittest.cc b/chromium/ui/accessibility/ax_node_position_unittest.cc
index 32c2f162137..c63083f407d 100644
--- a/chromium/ui/accessibility/ax_node_position_unittest.cc
+++ b/chromium/ui/accessibility/ax_node_position_unittest.cc
@@ -125,7 +125,7 @@ class AXPositionTest : public ::testing::Test, public TestAXTreeManager {
AXNodeData inline_box2_;
private:
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behaviour_;
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behaviour_;
// Manages a minimalistic Views tree that is hosting the test webpage.
TestAXTreeManager views_tree_manager_;
};
@@ -3032,7 +3032,7 @@ TEST_F(AXPositionTest, AtStartOrEndOfParagraphWithIgnoredNodes) {
}
TEST_F(AXPositionTest, AtStartOrEndOfParagraphWithEmbeddedObjectCharacter) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// This test ensures that "At{Start|End}OfParagraph" work correctly when there
@@ -4088,7 +4088,7 @@ TEST_F(AXPositionTest, AsLeafTextPositionWithTextPositionAndEmptyTextSandwich) {
AXNodeData button_data;
button_data.id = 3;
button_data.role = ax::mojom::Role::kButton;
- button_data.SetName("");
+ button_data.SetNameExplicitlyEmpty();
button_data.SetNameFrom(ax::mojom::NameFrom::kContents);
AXNodeData more_text_data;
@@ -4131,7 +4131,7 @@ TEST_F(AXPositionTest, AsLeafTextPositionWithTextPositionAndEmptyTextSandwich) {
}
TEST_F(AXPositionTest, AsLeafTextPositionWithTextPositionAndEmbeddedObject) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea "<embedded_object><embedded_object>"
@@ -4883,7 +4883,7 @@ TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithNullPosition) {
}
TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithTreePosition) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), static_text1_.id, 1 /* child_index */);
@@ -4943,7 +4943,7 @@ TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithTreePosition) {
}
TEST_F(AXPositionTest, CreatePositionAtPreviousFormatStartWithTextPosition) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), inline_box1_.id, 2 /* text_offset */,
@@ -5020,7 +5020,7 @@ TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithNullPosition) {
}
TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithTreePosition) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
TestPositionType tree_position = AXNodePosition::CreateTreePosition(
GetTreeID(), button_.id, 0 /* child_index */);
@@ -5087,7 +5087,7 @@ TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithTreePosition) {
}
TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithTextPosition) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
TestPositionType text_position = AXNodePosition::CreateTextPosition(
GetTreeID(), button_.id, 0 /* text_offset */,
@@ -5163,7 +5163,7 @@ TEST_F(AXPositionTest, CreatePositionAtNextFormatEndWithTextPosition) {
}
TEST_F(AXPositionTest, CreatePositionAtNextFormatEndOnEmbeddedObject) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++root_1
// ++++heading_2
@@ -7772,7 +7772,7 @@ TEST_F(AXPositionTest, CreateParentPositionWithMoveDirection) {
// This test only applies when "object replacement characters" are used in the
// accessibility tree, e.g., in IAccessible2, UI Automation and Linux ATK
// APIs.
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// This test ensures that "CreateParentPosition" (and by extension
@@ -8632,7 +8632,7 @@ TEST_F(AXPositionTest, CreateParentAndLeafPositionWithEmptyNodes) {
}
TEST_F(AXPositionTest, CreateParentAndLeafPositionWithEmbeddedObjects) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++kRootWebArea "<embedded>Hello<embedded>"
@@ -9653,7 +9653,7 @@ TEST_F(AXPositionTest, AsValidPosition) {
}
TEST_F(AXPositionTest, AsValidPositionInDescendantOfEmptyObject) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
@@ -10538,7 +10538,7 @@ TEST_F(AXPositionTest, OperatorEqualsSameTextOffsetDifferentAnchorIdLeaf) {
}
TEST_F(AXPositionTest, OperatorEqualsTextPositionsInTextField) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
@@ -10603,7 +10603,7 @@ TEST_F(AXPositionTest, OperatorEqualsTextPositionsInTextField) {
}
TEST_F(AXPositionTest, OperatorEqualsTextPositionsInSearchBox) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
@@ -10705,7 +10705,7 @@ TEST_F(AXPositionTest, OperatorEqualsTextPositionsInSearchBox) {
}
TEST_F(AXPositionTest, OperatorsTreePositionsAroundEmbeddedCharacter) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea "<embedded_object><embedded_object>"
@@ -10839,7 +10839,7 @@ TEST_F(AXPositionTest, OperatorsTreePositionsAroundEmbeddedCharacter) {
}
TEST_F(AXPositionTest, OperatorsTextPositionsAroundEmbeddedCharacter) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea "<embedded_object><embedded_object>"
@@ -11275,7 +11275,7 @@ TEST_F(AXPositionTest, CreateNextAnchorPosition) {
AXNodeData empty_text_data;
empty_text_data.id = 4;
empty_text_data.role = ax::mojom::Role::kStaticText;
- empty_text_data.SetName("");
+ empty_text_data.SetNameExplicitlyEmpty();
AXNodeData more_text_data;
more_text_data.id = 5;
@@ -11765,7 +11765,7 @@ TEST_F(AXPositionTest, CreatePreviousWordPositionInList) {
}
TEST_F(AXPositionTest, EmptyObjectReplacedByCharacterTextNavigation) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
@@ -12100,7 +12100,7 @@ TEST_F(AXPositionTest, EmptyObjectReplacedByCharacterTextNavigation) {
}
TEST_F(AXPositionTest, EmptyObjectReplacedByCharacterEmbedObject) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// Parent Tree
@@ -12152,7 +12152,7 @@ TEST_F(AXPositionTest, TextNavigationWithCollapsedCombobox) {
// collapsed, the subtree of that combobox needs to be hidden and, when
// expanded, it must be accessible in the tree. This test ensures we can't
// navigate into the options of a collapsed menu list popup.
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
// ++1 kRootWebArea
@@ -12314,7 +12314,7 @@ TEST_F(AXPositionTest, TextNavigationWithCollapsedCombobox) {
}
TEST_F(AXPositionTest, GetUnignoredSelectionWithLeafNodes) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kExposeCharacter);
AXNodeData root_data;
diff --git a/chromium/ui/accessibility/ax_node_unittest.cc b/chromium/ui/accessibility/ax_node_unittest.cc
index bd6674a2ac9..73553698b8f 100644
--- a/chromium/ui/accessibility/ax_node_unittest.cc
+++ b/chromium/ui/accessibility/ax_node_unittest.cc
@@ -11,6 +11,7 @@
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/ax_enums.mojom-shared.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_position.h"
@@ -18,6 +19,7 @@
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/test_ax_tree_manager.h"
+#include "ui/gfx/geometry/rect_f.h"
namespace ui {
@@ -396,7 +398,7 @@ TEST(AXNodeTest, TreeWalkingCrossingTreeBoundary) {
}
TEST(AXNodeTest, GetValueForControlTextField) {
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kSuppressCharacter);
// kRootWebArea
@@ -631,6 +633,112 @@ TEST(AXNodeTest, GetLowestPlatformAncestor) {
EXPECT_EQ(text_field_node, inline_box_2_node->GetLowestPlatformAncestor());
}
+TEST(AXNodeTest, GetTextContentRangeBounds) {
+ constexpr char16_t kEnglishText[] = u"Hey";
+ const std::vector<int32_t> kEnglishCharacterOffsets = {12, 19, 27};
+ // A Hindi word (which means "Hindi") consisting of two letters.
+ constexpr char16_t kHindiText[] = u"\x0939\x093F\x0928\x094D\x0926\x0940";
+ const std::vector<int32_t> kHindiCharacterOffsets = {40, 40, 59, 59, 59, 59};
+ // A Thai word (which means "feel") consisting of 3 letters.
+ constexpr char16_t kThaiText[] = u"\x0E23\x0E39\x0E49\x0E2A\x0E36\x0E01";
+ const std::vector<int32_t> kThaiCharacterOffsets = {66, 66, 66, 76, 76, 85};
+
+ AXNodeData root_data;
+ root_data.id = 1;
+ root_data.role = ax::mojom::Role::kRootWebArea;
+
+ AXNodeData text_data1;
+ text_data1.id = 2;
+ text_data1.role = ax::mojom::Role::kStaticText;
+ text_data1.SetName(kEnglishText);
+ text_data1.AddIntAttribute(
+ ax::mojom::IntAttribute::kTextDirection,
+ static_cast<int32_t>(ax::mojom::WritingDirection::kLtr));
+ text_data1.AddIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets,
+ kEnglishCharacterOffsets);
+
+ AXNodeData text_data2;
+ text_data2.id = 3;
+ text_data2.role = ax::mojom::Role::kStaticText;
+ text_data2.SetName(kHindiText);
+ text_data2.AddIntAttribute(
+ ax::mojom::IntAttribute::kTextDirection,
+ static_cast<int32_t>(ax::mojom::WritingDirection::kRtl));
+ text_data2.AddIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets,
+ kHindiCharacterOffsets);
+
+ AXNodeData text_data3;
+ text_data3.id = 4;
+ text_data3.role = ax::mojom::Role::kStaticText;
+ text_data3.SetName(kThaiText);
+ text_data3.AddIntAttribute(
+ ax::mojom::IntAttribute::kTextDirection,
+ static_cast<int32_t>(ax::mojom::WritingDirection::kTtb));
+ text_data3.AddIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets,
+ kThaiCharacterOffsets);
+
+ root_data.child_ids = {text_data1.id, text_data2.id, text_data3.id};
+
+ AXTreeUpdate update;
+ update.root_id = root_data.id;
+ update.nodes = {root_data, text_data1, text_data2, text_data3};
+ update.has_tree_data = true;
+
+ AXTreeData tree_data;
+ tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
+ tree_data.title = "Application";
+ update.tree_data = tree_data;
+
+ AXTree tree;
+ ASSERT_TRUE(tree.Unserialize(update)) << tree.error();
+
+ const AXNode* root_node = tree.root();
+ ASSERT_EQ(root_data.id, root_node->id());
+
+ const AXNode* text1_node = root_node->GetUnignoredChildAtIndex(0);
+ ASSERT_EQ(text_data1.id, text1_node->id());
+ const AXNode* text2_node = root_node->GetUnignoredChildAtIndex(1);
+ ASSERT_EQ(text_data2.id, text2_node->id());
+ const AXNode* text3_node = root_node->GetUnignoredChildAtIndex(2);
+ ASSERT_EQ(text_data3.id, text3_node->id());
+
+ // Bounds should be the same between UTF-8 and UTF-16 for `kEnglishText`.
+ EXPECT_EQ(gfx::RectF(0, 0, 27, 0),
+ text1_node->GetTextContentRangeBoundsUTF8(0, 3));
+ EXPECT_EQ(gfx::RectF(12, 0, 7, 0),
+ text1_node->GetTextContentRangeBoundsUTF8(1, 2));
+ EXPECT_EQ(gfx::RectF(), text1_node->GetTextContentRangeBoundsUTF8(2, 4));
+ EXPECT_EQ(gfx::RectF(0, 0, 27, 0),
+ text1_node->GetTextContentRangeBoundsUTF16(0, 3));
+ EXPECT_EQ(gfx::RectF(12, 0, 7, 0),
+ text1_node->GetTextContentRangeBoundsUTF16(1, 2));
+ EXPECT_EQ(gfx::RectF(), text1_node->GetTextContentRangeBoundsUTF16(2, 4));
+
+ // Offsets are manually converted between UTF-8 and UTF-16.
+ //
+ // `kHindiText` is 6 code units in UTF-16 and 18 in UTF-8.
+ EXPECT_EQ(gfx::RectF(0, 0, 59, 0),
+ text2_node->GetTextContentRangeBoundsUTF8(0, 18));
+ EXPECT_EQ(gfx::RectF(0, 0, 19, 0),
+ text2_node->GetTextContentRangeBoundsUTF8(6, 12));
+ EXPECT_EQ(gfx::RectF(0, 0, 59, 0),
+ text2_node->GetTextContentRangeBoundsUTF16(0, 6));
+ EXPECT_EQ(gfx::RectF(0, 0, 19, 0),
+ text2_node->GetTextContentRangeBoundsUTF16(2, 4));
+
+ // Offsets are manually converted between UTF-8 and UTF-16.
+ //
+ // `kThaiText` is 6 code units in UTF-16 and 18 in UTF-8.
+ EXPECT_EQ(gfx::RectF(0, 0, 0, 85),
+ text3_node->GetTextContentRangeBoundsUTF8(0, 18));
+ EXPECT_EQ(gfx::RectF(0, 66, 0, 10),
+ text3_node->GetTextContentRangeBoundsUTF8(6, 12));
+ EXPECT_EQ(gfx::RectF(0, 0, 0, 85),
+ text3_node->GetTextContentRangeBoundsUTF16(0, 6));
+ EXPECT_EQ(gfx::RectF(0, 66, 0, 10),
+ text3_node->GetTextContentRangeBoundsUTF16(2, 4));
+}
+
TEST(AXNodeTest, IsGridCellReadOnlyOrDisabled) {
// ++kRootWebArea
// ++++kGrid
diff --git a/chromium/ui/accessibility/ax_position.h b/chromium/ui/accessibility/ax_position.h
index 13f38045eaf..3ada31c07f1 100644
--- a/chromium/ui/accessibility/ax_position.h
+++ b/chromium/ui/accessibility/ax_position.h
@@ -35,7 +35,6 @@
#include "ui/accessibility/ax_text_attributes.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_manager.h"
-#include "ui/accessibility/ax_tree_manager_map.h"
#include "ui/gfx/utf16_indexing.h"
namespace ui {
@@ -165,8 +164,6 @@ enum class AXEmbeddedObjectBehavior {
// TODO(crbug.com/1204592) Don't export this so tests can't change it.
extern AX_EXPORT AXEmbeddedObjectBehavior g_ax_embedded_object_behavior;
-namespace testing {
-
class AX_EXPORT ScopedAXEmbeddedObjectBehaviorSetter {
public:
explicit ScopedAXEmbeddedObjectBehaviorSetter(
@@ -177,8 +174,6 @@ class AX_EXPORT ScopedAXEmbeddedObjectBehaviorSetter {
AXEmbeddedObjectBehavior prev_behavior_;
};
-} // namespace testing
-
// Forward declarations.
template <class AXPositionType, class AXNodeType>
class AXPosition;
@@ -406,7 +401,7 @@ class AXPosition {
DCHECK(GetManager());
std::ostringstream str;
str << "* Position: " << ToString()
- << "\n* Manager: " << GetManager()->ToString()
+ << "\n* Manager: " << GetManager()->ax_tree()->data().ToString()
<< "\n* Anchor node: " << *GetAnchor();
return str.str();
}
@@ -415,9 +410,7 @@ class AXPosition {
AXTreeID tree_id() const { return tree_id_; }
AXNodeID anchor_id() const { return anchor_id_; }
- AXTreeManager* GetManager() const {
- return AXTreeManagerMap::GetInstance().GetManager(tree_id());
- }
+ AXTreeManager* GetManager() const { return AXTreeManager::FromID(tree_id()); }
AXNode* GetAnchor() const {
if (tree_id_ == AXTreeIDUnknown() || anchor_id_ == kInvalidAXNodeID)
diff --git a/chromium/ui/accessibility/ax_range_unittest.cc b/chromium/ui/accessibility/ax_range_unittest.cc
index 4f0182e4e09..918b615e390 100644
--- a/chromium/ui/accessibility/ax_range_unittest.cc
+++ b/chromium/ui/accessibility/ax_range_unittest.cc
@@ -145,7 +145,7 @@ class AXRangeTest : public ::testing::Test, public TestAXTreeManager {
AXNodeData empty_paragraph_;
private:
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior_;
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior_;
};
// These tests use kSuppressCharacter behavior.
diff --git a/chromium/ui/accessibility/ax_role_properties.cc b/chromium/ui/accessibility/ax_role_properties.cc
index 1141967da8b..ae176cfbece 100644
--- a/chromium/ui/accessibility/ax_role_properties.cc
+++ b/chromium/ui/accessibility/ax_role_properties.cc
@@ -105,8 +105,10 @@ bool IsCellOrTableHeader(const ax::mojom::Role role) {
bool IsChildTreeOwner(const ax::mojom::Role role) {
switch (role) {
+ case ax::mojom::Role::kEmbeddedObject:
case ax::mojom::Role::kIframe:
case ax::mojom::Role::kIframePresentational:
+ case ax::mojom::Role::kPluginObject:
case ax::mojom::Role::kPortal:
return true;
default:
@@ -547,7 +549,6 @@ bool IsReadOnlySupported(const ax::mojom::Role role) {
case ax::mojom::Role::kSwitch:
case ax::mojom::Role::kTextField:
case ax::mojom::Role::kTextFieldWithComboBox:
- case ax::mojom::Role::kToggleButton:
case ax::mojom::Role::kTreeGrid:
return true;
diff --git a/chromium/ui/accessibility/ax_role_properties.h b/chromium/ui/accessibility/ax_role_properties.h
index 0ab00426668..2165f8d65f9 100644
--- a/chromium/ui/accessibility/ax_role_properties.h
+++ b/chromium/ui/accessibility/ax_role_properties.h
@@ -41,6 +41,8 @@ AX_BASE_EXPORT bool IsButton(const ax::mojom::Role role);
AX_BASE_EXPORT bool IsCellOrTableHeader(const ax::mojom::Role role);
// Returns true if the role is expected to be the parent of a child tree.
+// Can return false for a child tree owner if an ARIA role was used, e.g.
+// <iframe role="region">.
AX_BASE_EXPORT bool IsChildTreeOwner(const ax::mojom::Role role);
// Returns true if the provided role belongs to an object on which a click
diff --git a/chromium/ui/accessibility/ax_tree.cc b/chromium/ui/accessibility/ax_tree.cc
index 5fc55901e6c..9e716fc9c10 100644
--- a/chromium/ui/accessibility/ax_tree.cc
+++ b/chromium/ui/accessibility/ax_tree.cc
@@ -802,9 +802,9 @@ void AXTree::Destroy() {
{
ScopedTreeUpdateInProgressStateSetter tree_update_in_progress(*this);
-
- DestroyNodeAndSubtree(root_, nullptr);
- root_ = nullptr;
+ // ExtractAsDangling clears the underlying pointer and returns another
+ // raw_ptr instance that is allowed to dangle.
+ DestroyNodeAndSubtree(root_.ExtractAsDangling(), nullptr);
} // tree_update_in_progress.
}
@@ -822,6 +822,7 @@ gfx::RectF AXTree::RelativeToTreeBoundsInternal(const AXNode* node,
gfx::RectF bounds,
bool* offscreen,
bool clip_bounds,
+ bool skip_container_offset,
bool allow_recursion) const {
// If |bounds| is uninitialized, which is not the same as empty,
// start with the node bounds.
@@ -838,16 +839,17 @@ gfx::RectF AXTree::RelativeToTreeBoundsInternal(const AXNode* node,
ui::AXNode* child = node->children()[i];
bool ignore_offscreen;
- gfx::RectF child_bounds = RelativeToTreeBoundsInternal(
- child, gfx::RectF(), &ignore_offscreen, clip_bounds,
- /* allow_recursion = */ false);
+ gfx::RectF child_bounds =
+ RelativeToTreeBoundsInternal(child, gfx::RectF(), &ignore_offscreen,
+ clip_bounds, skip_container_offset,
+ /* allow_recursion = */ false);
bounds.Union(child_bounds);
}
if (bounds.width() > 0 && bounds.height() > 0) {
return bounds;
}
}
- } else {
+ } else if (!skip_container_offset) {
bounds.Offset(node->data().relative_bounds.bounds.x(),
node->data().relative_bounds.bounds.y());
}
@@ -863,7 +865,7 @@ gfx::RectF AXTree::RelativeToTreeBoundsInternal(const AXNode* node,
GetFromId(node->data().relative_bounds.offset_container_id);
if (!container && container != root())
container = root();
- if (!container || container == node)
+ if (!container || container == node || skip_container_offset)
break;
gfx::RectF container_bounds = container->data().relative_bounds.bounds;
@@ -950,6 +952,7 @@ gfx::RectF AXTree::RelativeToTreeBoundsInternal(const AXNode* node,
bool ignore_offscreen;
ancestor_bounds = RelativeToTreeBoundsInternal(
ancestor, gfx::RectF(), &ignore_offscreen, clip_bounds,
+ skip_container_offset,
/* allow_recursion = */ false);
gfx::RectF original_bounds = original_node->data().relative_bounds.bounds;
@@ -971,10 +974,11 @@ gfx::RectF AXTree::RelativeToTreeBoundsInternal(const AXNode* node,
gfx::RectF AXTree::RelativeToTreeBounds(const AXNode* node,
gfx::RectF bounds,
bool* offscreen,
- bool clip_bounds) const {
+ bool clip_bounds,
+ bool skip_container_offset) const {
bool allow_recursion = true;
return RelativeToTreeBoundsInternal(node, bounds, offscreen, clip_bounds,
- allow_recursion);
+ skip_container_offset, allow_recursion);
}
gfx::RectF AXTree::GetTreeBounds(const AXNode* node,
@@ -1167,7 +1171,7 @@ bool AXTree::Unserialize(const AXTreeUpdate& update) {
if (!root_) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kNoRoot);
- RecordError("Tree has no root.");
+ RecordError(update_state, "Tree has no root.");
return false;
}
@@ -1526,8 +1530,10 @@ bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
if (!is_new_root) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kNotInTree);
- RecordError(base::StringPrintf(
- "%d will not be in the tree and is not the new root", new_data.id));
+ RecordError(*update_state,
+ base::StringPrintf(
+ "%d will not be in the tree and is not the new root",
+ new_data.id));
return false;
}
@@ -1537,9 +1543,11 @@ bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
absl::nullopt)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kCreationPending);
- RecordError(base::StringPrintf(
- "Node %d is already pending for creation, cannot be the new root",
- new_data.id));
+ RecordError(
+ *update_state,
+ base::StringPrintf(
+ "Node %d is already pending for creation, cannot be the new root",
+ new_data.id));
return false;
}
if (update_state->pending_root_id) {
@@ -1555,7 +1563,8 @@ bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
if (base::Contains(new_child_id_set, new_child_id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kDuplicateChild);
- RecordError(base::StringPrintf("Node %d has duplicate child id %d",
+ RecordError(*update_state,
+ base::StringPrintf("Node %d has duplicate child id %d",
new_data.id, new_child_id));
return false;
}
@@ -1580,9 +1589,10 @@ bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
new_data.id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kCreationPendingForChild);
- RecordError(base::StringPrintf(
- "Node %d is already pending for creation, cannot be a new child",
- child_id));
+ RecordError(*update_state,
+ base::StringPrintf("Node %d is already pending for "
+ "creation, cannot be a new child",
+ child_id));
return false;
}
}
@@ -1624,9 +1634,10 @@ bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
if (update_state->ShouldPendingNodeExistInTree(child_id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kReparent);
- RecordError(base::StringPrintf(
- "Node %d is not marked for destruction, would be reparented to %d",
- child_id, new_data.id));
+ RecordError(*update_state,
+ base::StringPrintf("Node %d is not marked for destruction, "
+ "would be reparented to %d",
+ child_id, new_data.id));
return false;
}
@@ -1637,9 +1648,10 @@ bool AXTree::ComputePendingChangesToNode(const AXNodeData& new_data,
new_data.id)) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kCreationPendingForChild);
- RecordError(base::StringPrintf(
- "Node %d is already pending for creation, cannot be a new child",
- child_id));
+ RecordError(*update_state,
+ base::StringPrintf("Node %d is already pending for "
+ "creation, cannot be a new child",
+ child_id));
return false;
}
} else {
@@ -1678,8 +1690,9 @@ bool AXTree::UpdateNode(const AXNodeData& src,
if (!is_new_root) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kNotInTree);
- RecordError(base::StringPrintf(
- "%d is not in the tree and not the new root", src.id));
+ RecordError(*update_state,
+ base::StringPrintf(
+ "%d is not in the tree and not the new root", src.id));
return false;
}
@@ -2003,7 +2016,7 @@ bool AXTree::ValidatePendingChangesComplete(
std::string error = "Nodes left pending by the update:";
for (const AXNodeID pending_id : update_state.pending_node_ids)
error += base::StringPrintf(" %d", pending_id);
- RecordError(error);
+ RecordError(update_state, error);
return false;
}
@@ -2029,11 +2042,13 @@ bool AXTree::ValidatePendingChangesComplete(
if (has_pending_changes) {
ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
AXTreeUnserializeError::kPendingChanges);
- RecordError(base::StringPrintf(
- "Changes left pending by the update; "
- "destroy subtrees: %s, destroy nodes: %s, create nodes: %s",
- destroy_subtree_ids.c_str(), destroy_node_ids.c_str(),
- create_node_ids.c_str()));
+ RecordError(
+ update_state,
+ base::StringPrintf(
+ "Changes left pending by the update; "
+ "destroy subtrees: %s, destroy nodes: %s, create nodes: %s",
+ destroy_subtree_ids.c_str(), destroy_node_ids.c_str(),
+ create_node_ids.c_str()));
}
return !has_pending_changes;
}
@@ -2142,7 +2157,8 @@ bool AXTree::CreateNewChildVector(AXNode* node,
// If this case occurs, continue so this node isn't left in an
// inconsistent state, but return failure at the end.
if (child->parent()) {
- RecordError(base::StringPrintf("Node %d reparented from %d to %d",
+ RecordError(*update_state,
+ base::StringPrintf("Node %d reparented from %d to %d",
child->id(), child->parent()->id(),
node->id()));
} else {
@@ -2668,12 +2684,15 @@ bool ComputeUnignoredSelectionEndpoint(
} // namespace
+AXTree::Selection AXTree::GetSelection() const {
+ return {data().sel_is_backward, data().sel_anchor_object_id,
+ data().sel_anchor_offset, data().sel_anchor_affinity,
+ data().sel_focus_object_id, data().sel_focus_offset,
+ data().sel_focus_affinity};
+}
+
AXTree::Selection AXTree::GetUnignoredSelection() const {
- Selection unignored_selection = {
- data().sel_is_backward, data().sel_anchor_object_id,
- data().sel_anchor_offset, data().sel_anchor_affinity,
- data().sel_focus_object_id, data().sel_focus_offset,
- data().sel_focus_affinity};
+ Selection unignored_selection = GetSelection();
// If one of the selection endpoints is invalid, then the other endpoint
// should also be unset.
@@ -2724,18 +2743,36 @@ void AXTree::NotifyTreeManagerWillBeRemoved(AXTreeID previous_tree_id) {
observer.OnTreeManagerWillBeRemoved(previous_tree_id);
}
-void AXTree::RecordError(std::string new_error) {
+void AXTree::RecordError(const AXTreeUpdateState& update_state,
+ std::string new_error) {
if (!error_.empty())
error_ = error_ + "\n"; // Add visual separation between errors.
error_ = error_ + new_error;
- if (!error_.empty()) {
- // Add a crash key so we can figure out why this is happening.
- static crash_reporter::CrashKeyString<256> ax_tree_error(
- "ax_tree_unserialize_error");
- ax_tree_error.Set(error_);
- LOG(ERROR) << error_;
- }
+ LOG(ERROR) << new_error;
+
+ static auto* const ax_tree_error_key = base::debug::AllocateCrashKeyString(
+ "ax_tree_error", base::debug::CrashKeySize::Size256);
+ static auto* const ax_tree_update_key = base::debug::AllocateCrashKeyString(
+ "ax_tree_update", base::debug::CrashKeySize::Size256);
+ static auto* const ax_tree_key = base::debug::AllocateCrashKeyString(
+ "ax_tree", base::debug::CrashKeySize::Size256);
+ static auto* const ax_tree_data_key = base::debug::AllocateCrashKeyString(
+ "ax_tree_data", base::debug::CrashKeySize::Size256);
+
+ // Log additional crash keys so we can debug bad tree updates.
+ base::debug::SetCrashKeyString(ax_tree_error_key, new_error);
+ base::debug::SetCrashKeyString(ax_tree_update_key,
+ update_state.pending_tree_update.ToString());
+ base::debug::SetCrashKeyString(ax_tree_key, TreeToStringHelper(root_, 1));
+ base::debug::SetCrashKeyString(ax_tree_data_key, data().ToString());
+
+ // In fast-failing-builds, crash immediately with a message, otherwise
+ // rely on AccessibilityFatalError(), which will not crash until multiple
+ // errors occur.
+ SANITIZER_NOTREACHED() << new_error << "\n"
+ << update_state.pending_tree_update.ToString() << "\n"
+ << ToString();
}
} // namespace ui
diff --git a/chromium/ui/accessibility/ax_tree.h b/chromium/ui/accessibility/ax_tree.h
index 5bf1e1b818d..554c202e5ec 100644
--- a/chromium/ui/accessibility/ax_tree.h
+++ b/chromium/ui/accessibility/ax_tree.h
@@ -11,9 +11,10 @@
#include <memory>
#include <set>
#include <string>
-#include <unordered_map>
#include <vector>
+#include "base/containers/flat_map.h"
+#include "base/debug/crash_logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
@@ -146,7 +147,8 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
gfx::RectF RelativeToTreeBounds(const AXNode* node,
gfx::RectF node_bounds,
bool* offscreen = nullptr,
- bool clip_bounds = true) const;
+ bool clip_bounds = true,
+ bool skip_container_offset = false) const;
// Get the bounds of a node in the coordinate space of the tree.
// If set, updates |offscreen| boolean to be true if the node is offscreen
@@ -213,6 +215,14 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// present in the cache.
absl::optional<int> GetSetSize(const AXNode& node) override;
+ // Returns the part of the current selection that falls within this
+ // accessibility tree, if any.
+ Selection GetSelection() const override;
+
+ // Returns the part of the current selection that falls within this
+ // accessibility tree, if any, adjusting its endpoints to be within unignored
+ // nodes. (An "ignored" node is a node that is not exposed to platform APIs:
+ // See `AXNode::IsIgnored`.)
Selection GetUnignoredSelection() const override;
bool GetTreeUpdateInProgressState() const override;
@@ -246,7 +256,9 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// Accumulate errors as there can be more than one before Chrome is crashed
// via AccessibilityFatalError();
- void RecordError(std::string new_error);
+ // In an AX_FAIL_FAST_BUILD, will assert/crash immediately.
+ void RecordError(const AXTreeUpdateState& update_state,
+ std::string new_error);
// AXNode::OwnerTree override.
//
@@ -384,13 +396,14 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
gfx::RectF node_bounds,
bool* offscreen,
bool clip_bounds,
+ bool skip_container_offset,
bool allow_recursion) const;
base::ObserverList<AXTreeObserver> observers_;
raw_ptr<AXNode> root_ = nullptr;
- std::unordered_map<AXNodeID, std::unique_ptr<AXNode>> id_map_;
std::string error_;
AXTreeData data_;
+ base::flat_map<AXNodeID, std::unique_ptr<AXNode>> id_map_;
// Map from an int attribute (if IsNodeIdIntAttribute is true) to
// a reverse mapping from target nodes to source nodes.
@@ -403,7 +416,7 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// Map from node ID to cached table info, if the given node is a table.
// Invalidated every time the tree is updated.
- mutable std::unordered_map<AXNodeID, std::unique_ptr<AXTableInfo>>
+ mutable base::flat_map<AXNodeID, std::unique_ptr<AXTableInfo>>
table_info_map_;
// The next negative node ID to use for internal nodes.
@@ -462,7 +475,7 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// objects.
// All other objects will map to default-constructed OrderedSetInfo objects.
// Invalidated every time the tree is updated.
- mutable std::unordered_map<AXNodeID, NodeSetSizePosInSetInfo>
+ mutable base::flat_map<AXNodeID, NodeSetSizePosInSetInfo>
node_set_size_pos_in_set_info_map_;
// Indicates if the tree is updating.
diff --git a/chromium/ui/accessibility/ax_tree_fuzzer_util.cc b/chromium/ui/accessibility/ax_tree_fuzzer_util.cc
new file mode 100644
index 00000000000..ed27dc808ec
--- /dev/null
+++ b/chromium/ui/accessibility/ax_tree_fuzzer_util.cc
@@ -0,0 +1,355 @@
+// Copyright 2022 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_tree_fuzzer_util.h"
+
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/ax_range.h"
+#include "ui/accessibility/ax_role_properties.h"
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_data.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_update.h"
+
+FuzzerData::FuzzerData(const unsigned char* data, size_t size)
+ : data_(data), data_size_(size), data_index_(0) {}
+
+size_t FuzzerData::RemainingBytes() {
+ return data_size_ - data_index_;
+}
+
+unsigned char FuzzerData::NextByte() {
+ CHECK(RemainingBytes());
+ return data_[data_index_++];
+}
+
+const unsigned char* FuzzerData::NextBytes(size_t amount) {
+ CHECK(RemainingBytes() >= amount);
+ const unsigned char* current_position = &data_[data_index_];
+ data_index_ += amount;
+ return current_position;
+}
+
+ui::AXTree* AXTreeFuzzerGenerator::GetTree() {
+ return tree_manager_.GetTree();
+}
+
+void AXTreeFuzzerGenerator::GenerateInitialUpdate(FuzzerData& fuzz_data,
+ int node_count) {
+ max_assigned_node_id_ = 1;
+ ui::AXTreeUpdate initial_state;
+ initial_state.root_id = max_assigned_node_id_++;
+
+ initial_state.has_tree_data = true;
+ initial_state.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+
+ ui::AXNodeData root;
+ root.id = initial_state.root_id;
+ root.role = ax::mojom::Role::kRootWebArea;
+
+ std::stack<size_t> parent_index_stack;
+ parent_index_stack.push(initial_state.nodes.size());
+ initial_state.nodes.push_back(root);
+
+ // As we give out ids sequentially, starting at 1, the
+ // ...max_assigned_node_id_... is equivalent to the node count.
+ while (fuzz_data.RemainingBytes() >= kMinimumNewNodeFuzzDataSize &&
+ max_assigned_node_id_ < node_count) {
+ size_t extra_data_size =
+ fuzz_data.RemainingBytes() - kMinimumNewNodeFuzzDataSize;
+
+ ui::AXNodeData& parent = initial_state.nodes[parent_index_stack.top()];
+
+ // Create a node.
+ ui::AXNodeData node = CreateChildNodeData(parent, max_assigned_node_id_++);
+
+ // Determine role.
+ node.role = GetInterestingRole(fuzz_data.NextByte(), parent.role);
+
+ // Add role-specific properties.
+ AddRoleSpecificProperties(
+ fuzz_data, node,
+ parent.GetStringAttribute(ax::mojom::StringAttribute::kName),
+ extra_data_size);
+
+ // Determine the relationship of the next node from fuzz data. See
+ // implementation of `DetermineNextNodeRelationship` for details.
+ size_t ancestor_pop_count;
+ switch (DetermineNextNodeRelationship(node.role, fuzz_data.NextByte())) {
+ case kChild:
+ CHECK(CanHaveChildren(node.role));
+ parent_index_stack.push(initial_state.nodes.size());
+ break;
+ case kSibling:
+ initial_state.nodes.push_back(node);
+ break;
+ case kSiblingToAncestor:
+ ancestor_pop_count = 1 + fuzz_data.NextByte() % kMaxAncestorPopCount;
+ for (size_t i = 0;
+ i < ancestor_pop_count && parent_index_stack.size() > 1; ++i) {
+ parent_index_stack.pop();
+ }
+ break;
+ }
+
+ initial_state.nodes.push_back(node);
+ }
+ // Run with --v=1 to aid in debugging a specific crash.
+ VLOG(1) << "Input accessibility tree:\n" << initial_state.ToString();
+ tree_manager_.SetTree(std::make_unique<ui::AXTree>(initial_state));
+}
+
+// Pre-order depth first walk of tree. Skip over deleted subtrees.
+void AXTreeFuzzerGenerator::RecursiveGenerateUpdate(
+ const ui::AXNode* node,
+ ui::AXTreeUpdate& tree_update,
+ FuzzerData& fuzz_data,
+ std::set<ui::AXNodeID>& updated_nodes) {
+ // Stop traversing if we run out of fuzz data.
+ if (fuzz_data.RemainingBytes() <= kMinimumNewNodeFuzzDataSize)
+ return;
+ size_t extra_data_size =
+ fuzz_data.RemainingBytes() - kMinimumNewNodeFuzzDataSize;
+
+ AXTreeFuzzerGenerator::TreeUpdateOperation operation = kNoOperation;
+ if (!updated_nodes.count(node->id()))
+ operation = DetermineTreeUpdateOperation(node, fuzz_data.NextByte());
+
+ switch (operation) {
+ case kAddChild: {
+ // Determine where to insert the node.
+ // Create node and attach to parent.
+ ui::AXNodeData parent = node->data();
+ ui::AXNodeData child =
+ CreateChildNodeData(parent, max_assigned_node_id_++);
+
+ // Determine role.
+ child.role = GetInterestingRole(fuzz_data.NextByte(), node->GetRole());
+
+ // Add role-specific properties.
+ AddRoleSpecificProperties(
+ fuzz_data, child,
+ node->GetStringAttribute(ax::mojom::StringAttribute::kName),
+ extra_data_size);
+ // Also add inline text child if we can.
+ ui::AXNodeData inline_text_data;
+ if (ui::CanHaveInlineTextBoxChildren(child.role)) {
+ inline_text_data = CreateChildNodeData(child, max_assigned_node_id_++);
+ inline_text_data.role = ax::mojom::Role::kInlineTextBox;
+ inline_text_data.SetName(
+ child.GetStringAttribute(ax::mojom::StringAttribute::kName));
+ }
+ // Add both the current node (parent) and the child to the tree update.
+ tree_update.nodes.push_back(parent);
+ tree_update.nodes.push_back(child);
+ updated_nodes.emplace(parent.id);
+ updated_nodes.emplace(child.id);
+ if (inline_text_data.id != ui::kInvalidAXNodeID) {
+ tree_update.nodes.push_back(inline_text_data);
+ updated_nodes.emplace(inline_text_data.id);
+ }
+ break;
+ }
+ case kRemoveNode: {
+ const ui::AXNode* parent = node->GetParent();
+ if (updated_nodes.count(parent->id()))
+ break;
+ // Determine what node to delete.
+ // To delete a node, just find the parent and update the child list to
+ // no longer include this node.
+ ui::AXNodeData parent_update = parent->data();
+ parent_update.child_ids.erase(
+ std::remove(parent_update.child_ids.begin(),
+ parent_update.child_ids.end(), node->id()),
+ parent_update.child_ids.end());
+ tree_update.nodes.push_back(parent_update);
+ updated_nodes.emplace(parent_update.id);
+
+ // This node was deleted, don't traverse to the subtree.
+ return;
+ }
+ case kTextChange: {
+ // Modify the text.
+ const ui::AXNode* child_inline_text = node->GetFirstChild();
+ if (!child_inline_text ||
+ child_inline_text->GetRole() != ax::mojom::Role::kInlineTextBox) {
+ break;
+ }
+ ui::AXNodeData static_text_data = node->data();
+ ui::AXNodeData inline_text_data = child_inline_text->data();
+ size_t text_size =
+ kMinTextFuzzDataSize + fuzz_data.NextByte() % kMaxTextFuzzDataSize;
+ if (text_size > extra_data_size)
+ text_size = extra_data_size;
+ extra_data_size -= text_size;
+ inline_text_data.SetName(
+ GenerateInterestingText(fuzz_data.NextBytes(text_size), text_size));
+ static_text_data.SetName(inline_text_data.GetStringAttribute(
+ ax::mojom::StringAttribute::kName));
+ tree_update.nodes.push_back(static_text_data);
+ tree_update.nodes.push_back(inline_text_data);
+ updated_nodes.emplace(static_text_data.id);
+ updated_nodes.emplace(inline_text_data.id);
+ break;
+ }
+ case kNoOperation:
+ break;
+ }
+
+ // Visit subtree.
+ for (auto iter = node->AllChildrenBegin(); iter != node->AllChildrenEnd();
+ ++iter) {
+ RecursiveGenerateUpdate(iter.get(), tree_update, fuzz_data, updated_nodes);
+ }
+}
+
+// When building a tree update, we must take care to not create an
+// unserializable tree. If the tree does not serialize, things like
+// TestAXTreeObserver will not be able to handle the incorrectly serialized
+// tree. This will require us to abort the fuzz run.
+bool AXTreeFuzzerGenerator::GenerateTreeUpdate(FuzzerData& fuzz_data,
+ size_t node_count) {
+ ui::AXTreeUpdate tree_update;
+ std::set<ui::AXNodeID> updated_nodes;
+ RecursiveGenerateUpdate(tree_manager_.GetRootAsAXNode(), tree_update,
+ fuzz_data, updated_nodes);
+ return GetTree()->Unserialize(tree_update);
+}
+
+ui::AXNodeID AXTreeFuzzerGenerator::GetMaxAssignedID() const {
+ return max_assigned_node_id_;
+}
+
+ui::AXNodeData AXTreeFuzzerGenerator::CreateChildNodeData(
+ ui::AXNodeData& parent,
+ ui::AXNodeID new_node_id) {
+ ui::AXNodeData node;
+ node.id = new_node_id;
+ // Connect parent to this node.
+ parent.child_ids.push_back(node.id);
+ return node;
+}
+
+// Determine the relationship of the next node from fuzz data.
+AXTreeFuzzerGenerator::NextNodeRelationship
+AXTreeFuzzerGenerator::DetermineNextNodeRelationship(ax::mojom::Role role,
+ unsigned char byte) {
+ // Force this to have a inline text child if it can.
+ if (ui::CanHaveInlineTextBoxChildren(role))
+ return NextNodeRelationship::kChild;
+
+ // Don't allow inline text boxes to have children or siblings.
+ if (role == ax::mojom::Role::kInlineTextBox)
+ return NextNodeRelationship::kSiblingToAncestor;
+
+ // Determine next node using fuzz data.
+ NextNodeRelationship relationship =
+ static_cast<NextNodeRelationship>(byte % 3);
+
+ // Check to ensure we can have children.
+ if (relationship == NextNodeRelationship::kChild && !CanHaveChildren(role)) {
+ return NextNodeRelationship::kSibling;
+ }
+ return relationship;
+}
+
+AXTreeFuzzerGenerator::TreeUpdateOperation
+AXTreeFuzzerGenerator::DetermineTreeUpdateOperation(const ui::AXNode* node,
+ unsigned char byte) {
+ switch (byte % 4) {
+ case 0:
+ // Don't delete the following nodes:
+ // 1) The root. TODO(janewman): implement root changes in an update.
+ // 2) Inline text. We don't want to leave Static text nodes without inline
+ // text children.
+ if (ax::mojom::Role::kRootWebArea != node->GetRole())
+ return kRemoveNode;
+ ABSL_FALLTHROUGH_INTENDED;
+ case 1:
+ // Check to ensure this node can have children. Also consider that we
+ // shouldn't add children to static text, as these nodes only expect to
+ // have a inline text single child.
+ if (CanHaveChildren(node->GetRole()) && !ui::IsText(node->GetRole()))
+ return kAddChild;
+ ABSL_FALLTHROUGH_INTENDED;
+ case 2:
+ if (ax::mojom::Role::kStaticText == node->GetRole())
+ return kTextChange;
+ ABSL_FALLTHROUGH_INTENDED;
+ default:
+ return kNoOperation;
+ }
+}
+
+void AXTreeFuzzerGenerator::AddRoleSpecificProperties(
+ FuzzerData& fuzz_data,
+ ui::AXNodeData& node,
+ const std::string& parentName,
+ size_t extra_data_size) {
+ // TODO(janewman): Add ignored state.
+ // Add role-specific properties.
+ if (node.role == ax::mojom::Role::kInlineTextBox) {
+ node.SetName(parentName);
+ } else if (node.role == ax::mojom::Role::kLineBreak) {
+ node.SetName("\n");
+ } else if (ui::IsText(node.role)) {
+ size_t text_size =
+ kMinTextFuzzDataSize + fuzz_data.NextByte() % kMaxTextFuzzDataSize;
+ if (text_size > extra_data_size)
+ text_size = extra_data_size;
+ extra_data_size -= text_size;
+ node.SetName(
+ GenerateInterestingText(fuzz_data.NextBytes(text_size), text_size));
+ }
+}
+
+ax::mojom::Role AXTreeFuzzerGenerator::GetInterestingRole(
+ unsigned char byte,
+ ax::mojom::Role parent_role) {
+ if (ui::CanHaveInlineTextBoxChildren(parent_role))
+ return ax::mojom::Role::kInlineTextBox;
+
+ // Bias towards creating text nodes so we end up with more text in the tree.
+ switch (byte % 7) {
+ default:
+ case 0:
+ case 1:
+ case 2:
+ return ax::mojom::Role::kStaticText;
+ case 3:
+ return ax::mojom::Role::kLineBreak;
+ case 4:
+ return ax::mojom::Role::kParagraph;
+ case 5:
+ return ax::mojom::Role::kGenericContainer;
+ case 6:
+ return ax::mojom::Role::kGroup;
+ }
+}
+
+bool AXTreeFuzzerGenerator::CanHaveChildren(ax::mojom::Role role) {
+ switch (role) {
+ case ax::mojom::Role::kInlineTextBox:
+ return false;
+ default:
+ return true;
+ }
+}
+
+std::u16string AXTreeFuzzerGenerator::GenerateInterestingText(
+ const unsigned char* data,
+ size_t size) {
+ std::u16string wide_str;
+ for (size_t i = 0; i + 1 < size; i += 2) {
+ char16_t char_16 = data[i] << 8;
+ char_16 |= data[i + 1];
+ // Don't insert a null character.
+ if (char_16)
+ wide_str.push_back(char_16);
+ }
+ return wide_str;
+}
diff --git a/chromium/ui/accessibility/ax_tree_fuzzer_util.h b/chromium/ui/accessibility/ax_tree_fuzzer_util.h
new file mode 100644
index 00000000000..b71af77e426
--- /dev/null
+++ b/chromium/ui/accessibility/ax_tree_fuzzer_util.h
@@ -0,0 +1,90 @@
+// Copyright 2022 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.
+#ifndef UI_ACCESSIBILITY_AX_TREE_FUZZER_UTIL_H_
+#define UI_ACCESSIBILITY_AX_TREE_FUZZER_UTIL_H_
+
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_data.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_update.h"
+#include "ui/accessibility/test_ax_tree_manager.h"
+
+// TODO(janewman): Replace usage with ...FuzzedDataProvider...
+class FuzzerData {
+ public:
+ FuzzerData(const unsigned char* data, size_t size);
+ size_t RemainingBytes();
+ unsigned char NextByte();
+ const unsigned char* NextBytes(size_t amount);
+
+ private:
+ const unsigned char* data_;
+ const size_t data_size_;
+ size_t data_index_;
+};
+
+class AXTreeFuzzerGenerator {
+ public:
+ AXTreeFuzzerGenerator() = default;
+ ~AXTreeFuzzerGenerator() = default;
+
+ ui::AXTree* GetTree();
+
+ void GenerateInitialUpdate(FuzzerData& fuzz_data, int node_count);
+ bool GenerateTreeUpdate(FuzzerData& fuzz_data, size_t node_count);
+
+ ui::AXNodeID GetMaxAssignedID() const;
+
+ // This must be kept in sync with the minimum amount of data needed to create
+ // any node. Any optional node data should check to ensure there is space.
+ static constexpr size_t kMinimumNewNodeFuzzDataSize = 5;
+ static constexpr size_t kMinTextFuzzDataSize = 10;
+ static constexpr size_t kMaxTextFuzzDataSize = 200;
+
+ // When creating a node, we allow for the next node to be a sibling of an
+ // ancestor, this constant determines the maximum nodes we will pop when
+ // building the tree.
+ static constexpr size_t kMaxAncestorPopCount = 3;
+
+ private:
+ enum NextNodeRelationship {
+ // Next node is a child of this node. (This node is a parent.)
+ kChild,
+ // Next node is sibling to this node. (This node is a leaf.)
+ kSibling,
+ // Next node is sibling to an ancestor. (This node is a leaf.)
+ kSiblingToAncestor,
+ };
+ enum TreeUpdateOperation {
+ kAddChild,
+ kRemoveNode,
+ kTextChange,
+ kNoOperation
+ };
+
+ void RecursiveGenerateUpdate(const ui::AXNode* node,
+ ui::AXTreeUpdate& tree_update,
+ FuzzerData& fuzz_data,
+ std::set<ui::AXNodeID>& updated_nodes);
+ // TODO(janewman): Many of these can be made static.
+ ui::AXNodeData CreateChildNodeData(ui::AXNodeData& parent,
+ ui::AXNodeID new_node_id);
+ NextNodeRelationship DetermineNextNodeRelationship(ax::mojom::Role role,
+ unsigned char byte);
+ TreeUpdateOperation DetermineTreeUpdateOperation(const ui::AXNode* node,
+ unsigned char byte);
+ void AddRoleSpecificProperties(FuzzerData& fuzz_data,
+ ui::AXNodeData& node,
+ const std::string& parentName,
+ size_t extra_data_size);
+ ax::mojom::Role GetInterestingRole(unsigned char byte,
+ ax::mojom::Role parent_role);
+ bool CanHaveChildren(ax::mojom::Role role);
+ bool CanHaveText(ax::mojom::Role role);
+ std::u16string GenerateInterestingText(const unsigned char* data,
+ size_t size);
+ ui::AXNodeID max_assigned_node_id_;
+ ui::TestAXTreeManager tree_manager_;
+};
+#endif // UI_ACCESSIBILITY_AX_TREE_FUZZER_UTIL_H_
diff --git a/chromium/ui/accessibility/ax_tree_manager.cc b/chromium/ui/accessibility/ax_tree_manager.cc
new file mode 100644
index 00000000000..311c23991c5
--- /dev/null
+++ b/chromium/ui/accessibility/ax_tree_manager.cc
@@ -0,0 +1,107 @@
+// Copyright 2022 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_tree_manager.h"
+
+#include "base/no_destructor.h"
+#include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_manager_map.h"
+#include "ui/accessibility/ax_tree_observer.h"
+
+namespace ui {
+
+// static
+AXTreeManagerMap& AXTreeManager::GetMap() {
+ static base::NoDestructor<AXTreeManagerMap> map;
+ return *map;
+}
+
+// static
+AXTreeManager* AXTreeManager::FromID(AXTreeID ax_tree_id) {
+ return ax_tree_id != AXTreeIDUnknown() ? GetMap().GetManager(ax_tree_id)
+ : nullptr;
+}
+
+// static
+AXTreeManager* AXTreeManager::ForChildTree(const AXNode& parent_node) {
+ if (!parent_node.HasStringAttribute(
+ ax::mojom::StringAttribute::kChildTreeId)) {
+ return nullptr;
+ }
+
+ AXTreeID child_tree_id = AXTreeID::FromString(
+ parent_node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
+ AXTreeManager* child_tree_manager = GetMap().GetManager(child_tree_id);
+
+ // Some platforms do not use AXTreeManagers, so child trees don't exist in
+ // the browser process.
+ DCHECK(!child_tree_manager ||
+ !child_tree_manager->GetParentNodeFromParentTreeAsAXNode() ||
+ child_tree_manager->GetParentNodeFromParentTreeAsAXNode()->id() ==
+ parent_node.id());
+ return child_tree_manager;
+}
+
+AXTreeManager::AXTreeManager()
+ : ax_tree_id_(AXTreeIDUnknown()),
+ ax_tree_(nullptr),
+ event_generator_(ax_tree()) {}
+
+AXTreeManager::AXTreeManager(std::unique_ptr<AXTree> tree)
+ : ax_tree_id_(tree ? tree->data().tree_id : AXTreeIDUnknown()),
+ ax_tree_(std::move(tree)),
+ event_generator_(ax_tree()) {
+ GetMap().AddTreeManager(ax_tree_id_, this);
+}
+
+AXTreeManager::AXTreeManager(const AXTreeID& tree_id,
+ std::unique_ptr<AXTree> tree)
+ : ax_tree_id_(tree_id),
+ ax_tree_(std::move(tree)),
+ event_generator_(ax_tree()) {
+ GetMap().AddTreeManager(ax_tree_id_, this);
+ if (ax_tree())
+ tree_observation_.Observe(ax_tree());
+}
+
+AXTreeID AXTreeManager::GetTreeID() const {
+ return ax_tree_ ? ax_tree_->data().tree_id : AXTreeIDUnknown();
+}
+
+AXTreeID AXTreeManager::GetParentTreeID() const {
+ return ax_tree_ ? ax_tree_->data().parent_tree_id : AXTreeIDUnknown();
+}
+
+AXNode* AXTreeManager::GetRootAsAXNode() const {
+ return ax_tree_ ? ax_tree_->root() : nullptr;
+}
+
+void AXTreeManager::WillBeRemovedFromMap() {
+ if (!ax_tree_)
+ return;
+ ax_tree_->NotifyTreeManagerWillBeRemoved(ax_tree_id_);
+}
+
+AXTreeManager::~AXTreeManager() {
+ // Stop observing so we don't get a callback for every node being deleted.
+ event_generator_.ReleaseTree();
+ if (ax_tree_)
+ GetMap().RemoveTreeManager(ax_tree_id_);
+}
+
+void AXTreeManager::OnTreeDataChanged(AXTree* tree,
+ const AXTreeData& old_data,
+ const AXTreeData& new_data) {
+ GetMap().RemoveTreeManager(ax_tree_id_);
+ ax_tree_id_ = new_data.tree_id;
+ GetMap().AddTreeManager(ax_tree_id_, this);
+}
+
+void AXTreeManager::RemoveFromMap() {
+ GetMap().RemoveTreeManager(ax_tree_id_);
+}
+
+} // namespace ui
diff --git a/chromium/ui/accessibility/ax_tree_manager.h b/chromium/ui/accessibility/ax_tree_manager.h
index 52f892c1acf..df43f35a0c2 100644
--- a/chromium/ui/accessibility/ax_tree_manager.h
+++ b/chromium/ui/accessibility/ax_tree_manager.h
@@ -5,18 +5,32 @@
#ifndef UI_ACCESSIBILITY_AX_TREE_MANAGER_H_
#define UI_ACCESSIBILITY_AX_TREE_MANAGER_H_
+#include "base/scoped_observation.h"
+#include "ui/accessibility/ax_event_generator.h"
#include "ui/accessibility/ax_export.h"
-#include "ui/accessibility/ax_node.h"
-#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_observer.h"
namespace ui {
+class AXNode;
+class AXTreeManagerMap;
+
// Abstract interface for a class that owns an AXTree and manages its
// connections to other AXTrees in the same page or desktop (parent and child
// trees).
-class AX_EXPORT AXTreeManager {
+class AX_EXPORT AXTreeManager : public AXTreeObserver {
public:
+ static AXTreeManager* FromID(AXTreeID ax_tree_id);
+ // If the child of `parent_node` exists in a separate child tree, return the
+ // tree manager for that child tree. Otherwise, return nullptr.
+ static AXTreeManager* ForChildTree(const AXNode& parent_node);
+
+ AXTreeManager(const AXTreeManager&) = delete;
+ AXTreeManager& operator=(const AXTreeManager&) = delete;
+
+ ~AXTreeManager() override;
+
// Returns the AXNode with the given |node_id| from the tree that has the
// given |tree_id|. This allows for callers to access nodes outside of their
// own tree. Returns nullptr if |tree_id| or |node_id| is not found.
@@ -28,20 +42,15 @@ class AX_EXPORT AXTreeManager {
// Returns nullptr if |node_id| is not found.
virtual AXNode* GetNodeFromTree(const AXNodeID node_id) const = 0;
- // Use `AddObserver` and `RemoveObserver` when you want to be notified when
- // changes happen to an `XTree`
- virtual void AddObserver(AXTreeObserver* observer) {}
- virtual void RemoveObserver(AXTreeObserver* observer) {}
-
// Returns the tree id of the tree managed by this AXTreeManager.
- virtual AXTreeID GetTreeID() const = 0;
+ AXTreeID GetTreeID() const;
// Returns the tree id of the parent tree.
// Returns AXTreeIDUnknown if this tree doesn't have a parent tree.
- virtual AXTreeID GetParentTreeID() const = 0;
+ virtual AXTreeID GetParentTreeID() const;
// Returns the AXNode that is at the root of the current tree.
- virtual AXNode* GetRootAsAXNode() const = 0;
+ AXNode* GetRootAsAXNode() const;
// If this tree has a parent tree, returns the node in the parent tree that
// hosts the current tree. Returns nullptr if this tree doesn't have a parent
@@ -50,12 +59,61 @@ class AX_EXPORT AXTreeManager {
// Called when the tree manager is about to be removed from the tree map,
// `AXTreeManagerMap`.
- virtual void WillBeRemovedFromMap() {}
+ void WillBeRemovedFromMap();
+
+ const AXTreeID& ax_tree_id() const { return ax_tree_id_; }
+ AXTree* ax_tree() const { return ax_tree_.get(); }
+
+ const AXEventGenerator& event_generator() const { return event_generator_; }
+ AXEventGenerator& event_generator() { return event_generator_; }
+
+ // AXTreeObserver implementation.
+ void OnTreeDataChanged(ui::AXTree* tree,
+ const ui::AXTreeData& old_data,
+ const ui::AXTreeData& new_data) override;
+ void OnNodeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override {}
+ void OnSubtreeWillBeDeleted(ui::AXTree* tree, ui::AXNode* node) override {}
+ void OnNodeCreated(ui::AXTree* tree, ui::AXNode* node) override {}
+ void OnNodeDeleted(ui::AXTree* tree, int32_t node_id) override {}
+ void OnNodeReparented(ui::AXTree* tree, ui::AXNode* node) override {}
+ void OnRoleChanged(ui::AXTree* tree,
+ ui::AXNode* node,
+ ax::mojom::Role old_role,
+ ax::mojom::Role new_role) override {}
+ void OnAtomicUpdateFinished(
+ ui::AXTree* tree,
+ bool root_changed,
+ const std::vector<ui::AXTreeObserver::Change>& changes) override {}
+
+ protected:
+ AXTreeManager();
+ explicit AXTreeManager(std::unique_ptr<AXTree> tree);
+ explicit AXTreeManager(const AXTreeID& tree_id, std::unique_ptr<AXTree> tree);
+
+ // TODO(benjamin.beaudry): Remove this helper once we move the logic related
+ // to the parent connection from `BrowserAccessibilityManager` to this class.
+ // `BrowserAccessibilityManager` needs to remove the manager from the map
+ // before calling `BrowserAccessibilityManager::ParentConnectionChanged`, so
+ // the default removal of the manager in `~AXTreeManager` occurs too late.
+ void RemoveFromMap();
+
+ AXTreeID ax_tree_id_;
+ std::unique_ptr<AXTree> ax_tree_;
+
+ AXEventGenerator event_generator_;
+
+ private:
+ friend class TestAXTreeManager;
+
+ static AXTreeManagerMap& GetMap();
- // For debugging.
- // TODO(benjamin.beaudry) Instead of this, implement GetTreeData() on all
- // AXTreeManager subclasses, and have callers use GetTreeData().ToString();
- virtual std::string ToString() const = 0;
+ // Automatically stops observing notifications from the AXTree when this class
+ // is destructed.
+ //
+ // This member needs to be destructed before any observed AXTrees. Since
+ // destructors for non-static member fields are called in the reverse order of
+ // declaration, do not move this member above other members.
+ base::ScopedObservation<AXTree, AXTreeObserver> tree_observation_{this};
};
} // namespace ui
diff --git a/chromium/ui/accessibility/ax_tree_manager_base.cc b/chromium/ui/accessibility/ax_tree_manager_base.cc
index 5b452d85e5f..461ad3aaffd 100644
--- a/chromium/ui/accessibility/ax_tree_manager_base.cc
+++ b/chromium/ui/accessibility/ax_tree_manager_base.cc
@@ -25,10 +25,9 @@ AXTreeManagerBase* AXTreeManagerBase::GetManager(const AXTreeID& tree_id) {
}
// static
-std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>&
+base::flat_map<AXTreeID, AXTreeManagerBase*>&
AXTreeManagerBase::GetTreeManagerMapInstance() {
- static base::NoDestructor<
- std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>>
+ static base::NoDestructor<base::flat_map<AXTreeID, AXTreeManagerBase*>>
map_instance;
return *map_instance;
}
diff --git a/chromium/ui/accessibility/ax_tree_manager_base.h b/chromium/ui/accessibility/ax_tree_manager_base.h
index b42429d79e0..ac733dbaabb 100644
--- a/chromium/ui/accessibility/ax_tree_manager_base.h
+++ b/chromium/ui/accessibility/ax_tree_manager_base.h
@@ -6,8 +6,8 @@
#define UI_ACCESSIBILITY_AX_TREE_MANAGER_BASE_H_
#include <memory>
-#include <unordered_map>
+#include "base/containers/flat_map.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/ax_node_data.h"
@@ -122,7 +122,7 @@ class AX_EXPORT AXTreeManagerBase final {
AXTreeManagerBase* DetachChildTree(AXNode& host_node);
private:
- static std::unordered_map<AXTreeID, AXTreeManagerBase*, AXTreeIDHash>&
+ static base::flat_map<AXTreeID, AXTreeManagerBase*>&
GetTreeManagerMapInstance();
std::unique_ptr<AXTree> tree_;
diff --git a/chromium/ui/accessibility/ax_tree_manager_map.cc b/chromium/ui/accessibility/ax_tree_manager_map.cc
index 8e12b1936b2..574f12cf3e8 100644
--- a/chromium/ui/accessibility/ax_tree_manager_map.cc
+++ b/chromium/ui/accessibility/ax_tree_manager_map.cc
@@ -5,8 +5,6 @@
#include "ui/accessibility/ax_tree_manager_map.h"
#include "base/containers/contains.h"
-#include "base/no_destructor.h"
-#include "ui/accessibility/ax_enums.mojom.h"
namespace ui {
@@ -14,26 +12,20 @@ AXTreeManagerMap::AXTreeManagerMap() = default;
AXTreeManagerMap::~AXTreeManagerMap() = default;
-// static
-AXTreeManagerMap& AXTreeManagerMap::GetInstance() {
- static base::NoDestructor<AXTreeManagerMap> instance;
- return *instance;
-}
-
-void AXTreeManagerMap::AddTreeManager(AXTreeID tree_id,
+void AXTreeManagerMap::AddTreeManager(const AXTreeID& tree_id,
AXTreeManager* manager) {
if (tree_id != AXTreeIDUnknown())
map_[tree_id] = manager;
}
-void AXTreeManagerMap::RemoveTreeManager(AXTreeID tree_id) {
+void AXTreeManagerMap::RemoveTreeManager(const AXTreeID& tree_id) {
if (auto* manager = GetManager(tree_id)) {
manager->WillBeRemovedFromMap();
map_.erase(tree_id);
}
}
-AXTreeManager* AXTreeManagerMap::GetManager(AXTreeID tree_id) {
+AXTreeManager* AXTreeManagerMap::GetManager(const AXTreeID& tree_id) {
if (tree_id == AXTreeIDUnknown())
return nullptr;
auto iter = map_.find(tree_id);
@@ -43,25 +35,4 @@ AXTreeManager* AXTreeManagerMap::GetManager(AXTreeID tree_id) {
return iter->second;
}
-AXTreeManager* AXTreeManagerMap::GetManagerForChildTree(
- const AXNode& parent_node) {
- if (!parent_node.HasStringAttribute(
- ax::mojom::StringAttribute::kChildTreeId)) {
- return nullptr;
- }
-
- AXTreeID child_tree_id = AXTreeID::FromString(
- parent_node.GetStringAttribute(ax::mojom::StringAttribute::kChildTreeId));
- AXTreeManager* child_tree_manager =
- AXTreeManagerMap::GetInstance().GetManager(child_tree_id);
-
- // Some platforms do not use AXTreeManagers, so child trees don't exist in
- // the browser process.
- DCHECK(!child_tree_manager ||
- !child_tree_manager->GetParentNodeFromParentTreeAsAXNode() ||
- child_tree_manager->GetParentNodeFromParentTreeAsAXNode()->id() ==
- parent_node.id());
- return child_tree_manager;
-}
-
} // namespace ui
diff --git a/chromium/ui/accessibility/ax_tree_manager_map.h b/chromium/ui/accessibility/ax_tree_manager_map.h
index aff21f0be99..4a444e920b3 100644
--- a/chromium/ui/accessibility/ax_tree_manager_map.h
+++ b/chromium/ui/accessibility/ax_tree_manager_map.h
@@ -5,17 +5,16 @@
#ifndef UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
#define UI_ACCESSIBILITY_AX_TREE_MANAGER_MAP_H_
-#include <unordered_map>
-
+#include "base/containers/flat_map.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_manager.h"
namespace ui {
-// This class manages AXTreeManager instances. It is a singleton wrapper
-// around a std::unordered_map. AXTreeID's are used as the key for the map.
-// Since AXTreeID's might refer to AXTreeIDUnknown, callers should not expect
-// AXTreeIDUnknown to map to a particular AXTreeManager.
+// This class manages AXTreeManager instances. It is a wrapper around a
+// base::flat_map. AXTreeID's are used as the key for the map. Since AXTreeID's
+// might refer to AXTreeIDUnknown, callers should not expect AXTreeIDUnknown to
+// map to a particular AXTreeManager.
class AX_EXPORT AXTreeManagerMap {
public:
AXTreeManagerMap();
@@ -23,17 +22,12 @@ class AX_EXPORT AXTreeManagerMap {
AXTreeManagerMap(const AXTreeManagerMap& map) = delete;
AXTreeManagerMap& operator=(const AXTreeManagerMap& map) = delete;
- static AXTreeManagerMap& GetInstance();
- void AddTreeManager(AXTreeID tree_id, AXTreeManager* manager);
- void RemoveTreeManager(AXTreeID tree_id);
- AXTreeManager* GetManager(AXTreeID tree_id);
-
- // If the child of `parent_node` exists in a separate child tree, return the
- // tree manager for that child tree. Otherwise, return nullptr.
- AXTreeManager* GetManagerForChildTree(const AXNode& parent_node);
+ void AddTreeManager(const AXTreeID& tree_id, AXTreeManager* manager);
+ void RemoveTreeManager(const AXTreeID& tree_id);
+ AXTreeManager* GetManager(const AXTreeID& tree_id);
private:
- std::unordered_map<AXTreeID, AXTreeManager*, AXTreeIDHash> map_;
+ base::flat_map<AXTreeID, AXTreeManager*> map_;
};
} // namespace ui
diff --git a/chromium/ui/accessibility/ax_tree_serializer.h b/chromium/ui/accessibility/ax_tree_serializer.h
index 4d97ae45f98..9d927ec695d 100644
--- a/chromium/ui/accessibility/ax_tree_serializer.h
+++ b/chromium/ui/accessibility/ax_tree_serializer.h
@@ -13,7 +13,6 @@
#include <memory>
#include <ostream>
#include <set>
-#include <unordered_set>
#include <vector>
#include "base/debug/crash_logging.h"
@@ -322,16 +321,14 @@ AXSourceNode AXTreeSerializer<AXSourceNode>::LeastCommonAncestor(
// client ancestor chain disagree. The last node before they disagree
// is the LCA.
AXSourceNode lca = tree_->GetNull();
- int source_index = static_cast<int>(ancestors.size() - 1);
- int client_index = static_cast<int>(client_ancestors.size() - 1);
- while (source_index >= 0 && client_index >= 0) {
- if (tree_->GetId(ancestors[source_index]) !=
- client_ancestors[client_index]->id) {
+ for (size_t source_index = ancestors.size(),
+ client_index = client_ancestors.size();
+ source_index > 0 && client_index > 0; --source_index, --client_index) {
+ if (tree_->GetId(ancestors[source_index - 1]) !=
+ client_ancestors[client_index - 1]->id) {
return lca;
}
- lca = ancestors[source_index];
- source_index--;
- client_index--;
+ lca = ancestors[source_index - 1];
}
return lca;
}
diff --git a/chromium/ui/accessibility/ax_tree_unittest.cc b/chromium/ui/accessibility/ax_tree_unittest.cc
index 9388778e097..97dbe0a005b 100644
--- a/chromium/ui/accessibility/ax_tree_unittest.cc
+++ b/chromium/ui/accessibility/ax_tree_unittest.cc
@@ -449,6 +449,10 @@ TEST(AXTreeTest, LeaveOrphanedDeletedSubtreeFails) {
update.node_id_to_clear = 2;
update.nodes.resize(1);
update.nodes[0].id = 3;
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(update),
+ "Nodes left pending by the update: 2");
+#else
EXPECT_FALSE(tree.Unserialize(update));
ASSERT_EQ("Nodes left pending by the update: 2", tree.error());
histogram_tester.ExpectUniqueSample(
@@ -456,6 +460,7 @@ TEST(AXTreeTest, LeaveOrphanedDeletedSubtreeFails) {
AXTreeUnserializeError::kPendingNodes, 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.Tree.Unserialize", 2);
+#endif
}
TEST(AXTreeTest, LeaveOrphanedNewChildFails) {
@@ -475,6 +480,10 @@ TEST(AXTreeTest, LeaveOrphanedNewChildFails) {
update.nodes.resize(1);
update.nodes[0].id = 1;
update.nodes[0].child_ids.push_back(2);
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(update),
+ "Nodes left pending by the update: 2");
+#else
EXPECT_FALSE(tree.Unserialize(update));
ASSERT_EQ("Nodes left pending by the update: 2", tree.error());
histogram_tester.ExpectUniqueSample(
@@ -482,6 +491,7 @@ TEST(AXTreeTest, LeaveOrphanedNewChildFails) {
AXTreeUnserializeError::kPendingNodes, 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.Tree.Unserialize", 2);
+#endif
}
TEST(AXTreeTest, DuplicateChildIdFails) {
@@ -502,6 +512,10 @@ TEST(AXTreeTest, DuplicateChildIdFails) {
update.nodes[0].child_ids.push_back(2);
update.nodes[0].child_ids.push_back(2);
update.nodes[1].id = 2;
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(update),
+ "Node 1 has duplicate child id 2");
+#else
EXPECT_FALSE(tree.Unserialize(update));
ASSERT_EQ("Node 1 has duplicate child id 2", tree.error());
histogram_tester.ExpectUniqueSample(
@@ -509,6 +523,7 @@ TEST(AXTreeTest, DuplicateChildIdFails) {
AXTreeUnserializeError::kDuplicateChild, 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.Tree.Unserialize", 1);
+#endif
}
TEST(AXTreeTest, InvalidReparentingFails) {
@@ -536,6 +551,11 @@ TEST(AXTreeTest, InvalidReparentingFails) {
update.nodes[0].child_ids.push_back(2);
update.nodes[1].id = 2;
update.nodes[2].id = 3;
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(
+ tree.Unserialize(update),
+ "Node 3 is not marked for destruction, would be reparented to 1");
+#else
EXPECT_FALSE(tree.Unserialize(update));
ASSERT_EQ("Node 3 is not marked for destruction, would be reparented to 1",
tree.error());
@@ -544,6 +564,7 @@ TEST(AXTreeTest, InvalidReparentingFails) {
AXTreeUnserializeError::kReparent, 1);
histogram_tester.ExpectTotalCount(
"Accessibility.Performance.Tree.Unserialize", 1);
+#endif
}
TEST(AXTreeTest, NoReparentingOfRootIfNoNewRoot) {
@@ -954,6 +975,7 @@ TEST(AXTreeTest, ImplicitAttributeDelete) {
initial_state.root_id = 1;
initial_state.nodes.resize(1);
initial_state.nodes[0].id = 1;
+ initial_state.nodes[0].role = ax::mojom::Role::kGroup;
initial_state.nodes[0].SetName("Node 1 name");
AXTree tree(initial_state);
@@ -1205,7 +1227,13 @@ TEST(AXTreeTest, BogusAXTree2) {
node2.child_ids.push_back(0);
initial_state.nodes.push_back(node2);
ui::AXTree tree;
- tree.Unserialize(initial_state);
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(initial_state),
+ "Node 0 has duplicate child id 0");
+#else
+ EXPECT_FALSE(tree.Unserialize(initial_state));
+ EXPECT_EQ("Node 0 has duplicate child id 0", tree.error());
+#endif
}
// UAF caught by ax_tree_fuzzer
@@ -1223,7 +1251,13 @@ TEST(AXTreeTest, BogusAXTree3) {
initial_state.nodes.push_back(node2);
ui::AXTree tree;
- tree.Unserialize(initial_state);
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(initial_state),
+ "Node 1 has duplicate child id 1");
+#else
+ EXPECT_FALSE(tree.Unserialize(initial_state));
+ EXPECT_EQ("Node 1 has duplicate child id 1", tree.error());
+#endif
}
TEST(AXTreeTest, RoleAndStateChangeCallbacks) {
@@ -5381,10 +5415,17 @@ TEST(AXTreeTest, UnserializeErrors) {
ui::AXNodeData disconnected_node;
disconnected_node.id = 2;
tree_update_3.nodes.push_back(disconnected_node);
+#if defined(AX_FAIL_FAST_BUILD)
+ EXPECT_DEATH_IF_SUPPORTED(
+ tree.Unserialize(tree_update_3),
+ "2 will not be in the tree and is not the new root");
+#else
EXPECT_FALSE(tree.Unserialize(tree_update_3));
+ EXPECT_EQ("2 will not be in the tree and is not the new root", tree.error());
histogram_tester.ExpectUniqueSample(
"Accessibility.Reliability.Tree.UnserializeError",
AXTreeUnserializeError::kNotInTree, 1);
+#endif
}
} // namespace ui
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/background.js b/chromium/ui/accessibility/extensions/color_contrast_companion/background.js
new file mode 100644
index 00000000000..68e985c2c6c
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/background.js
@@ -0,0 +1,96 @@
+// Copyright 2022 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.
+
+var stream = null;
+var ui = null;
+
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ console.log('Got message');
+ console.log(request);
+ if (request.close && ui) {
+ chrome.windows.remove(ui.id);
+ }
+});
+
+function capture() {
+ let video = document.createElement('video');
+ video.autoplay = true;
+ document.body.appendChild(video);
+ video.addEventListener('canplay', () => {
+ if (video.videoWidth < 100) {
+ // We probably need the permission again.
+ getStream();
+ return;
+ }
+
+ window.setTimeout(() => {
+ let canvas = document.createElement('canvas');
+ canvas.height = video.videoHeight;
+ canvas.width = video.videoWidth;
+ var context = canvas.getContext('2d');
+ context.drawImage(video, 0, 0, canvas.width, canvas.height);
+ var imageDataUrl = canvas.toDataURL();
+ document.body.removeChild(video);
+
+ // Close the stream so it stops using resources.
+ let tracks = stream.getTracks();
+ tracks.forEach(function(track) {
+ track.stop();
+ });
+ stream = null;
+
+ chrome.windows.create(
+ {
+ 'url': chrome.runtime.getURL('ui.html'),
+ 'focused': true,
+ 'type': 'popup',
+ 'state': 'fullscreen'
+ },
+ (win) => {
+ ui = win;
+ var tab = win.tabs[0];
+ window.setTimeout(() => {
+ console.log('Sending message');
+ chrome.tabs.sendMessage(tab.id, {'imageDataUrl': imageDataUrl});
+ }, 250);
+ });
+ }, 250);
+ });
+ video.srcObject = stream;
+}
+
+function getStream() {
+ chrome.desktopCapture.chooseDesktopMedia(['screen'], (streamId) => {
+ let video = document.createElement('video');
+ navigator.mediaDevices
+ .getUserMedia({
+ video: {
+ mandatory: {
+ chromeMediaSource: 'desktop',
+ chromeMediaSourceId: streamId,
+ }
+ }
+ })
+ .then(returnedStream => {
+ stream = returnedStream;
+ capture();
+ });
+ });
+}
+
+chrome.browserAction.onClicked.addListener(() => {
+ if (!stream) {
+ getStream();
+ return;
+ }
+
+ capture();
+});
+
+var alreadyShowedHelp = localStorage.getItem('help');
+if (!alreadyShowedHelp) {
+ localStorage.setItem('help', 'true');
+ chrome.windows.create(
+ {'url': chrome.runtime.getURL('help.html'), 'focused': true});
+}
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/browser_action.png b/chromium/ui/accessibility/extensions/color_contrast_companion/browser_action.png
new file mode 100644
index 00000000000..a72899c2832
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/browser_action.png
Binary files differ
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/common.js b/chromium/ui/accessibility/extensions/color_contrast_companion/common.js
new file mode 100644
index 00000000000..b4cf068274b
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/common.js
@@ -0,0 +1,101 @@
+// Copyright (c) 2014 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.
+
+var DEFAULT_SCHEME = 3;
+var MAX_SCHEME = 5;
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+function getEnabled() {
+ var result = localStorage['enabled'];
+ if (result === 'true' || result === 'false') {
+ return (result === 'true');
+ }
+ localStorage['enabled'] = 'true';
+ return true;
+}
+
+function setEnabled(enabled) {
+ localStorage['enabled'] = enabled;
+}
+
+function getKeyAction() {
+ var keyAction = localStorage['keyaction'];
+ if (keyAction == 'global' || keyAction == 'site') {
+ return keyAction;
+ }
+ keyAction = 'global';
+ localStorage['keyaction'] = keyAction;
+ return keyAction;
+}
+
+function setKeyAction(keyAction) {
+ if (keyAction != 'global' && keyAction != 'site') {
+ keyAction = 'global';
+ }
+ localStorage['keyaction'] = keyAction;
+}
+
+function getDefaultScheme() {
+ var scheme = localStorage['scheme'];
+ if (scheme >= 0 && scheme <= MAX_SCHEME) {
+ return scheme;
+ }
+ scheme = DEFAULT_SCHEME;
+ localStorage['scheme'] = scheme;
+ return scheme;
+}
+
+function setDefaultScheme(scheme) {
+ if (!(scheme >= 0 && scheme <= MAX_SCHEME)) {
+ scheme = DEFAULT_SCHEME;
+ }
+ localStorage['scheme'] = scheme;
+}
+
+function getSiteScheme(site) {
+ var scheme = getDefaultScheme();
+ try {
+ var siteSchemes = JSON.parse(localStorage['siteschemes']);
+ scheme = siteSchemes[site];
+ if (!(scheme >= 0 && scheme <= MAX_SCHEME)) {
+ scheme = getDefaultScheme();
+ }
+ } catch (e) {
+ scheme = getDefaultScheme();
+ }
+ return scheme;
+}
+
+function setSiteScheme(site, scheme) {
+ if (!(scheme >= 0 && scheme <= MAX_SCHEME)) {
+ scheme = getDefaultScheme();
+ }
+ var siteSchemes = {};
+ try {
+ siteSchemes = JSON.parse(localStorage['siteschemes']);
+ siteSchemes['www.example.com'] = getDefaultScheme();
+ } catch (e) {
+ siteSchemes = {};
+ }
+ siteSchemes[site] = scheme;
+ localStorage['siteschemes'] = JSON.stringify(siteSchemes);
+}
+
+function resetSiteSchemes() {
+ var siteSchemes = {};
+ localStorage['siteschemes'] = JSON.stringify(siteSchemes);
+}
+
+function siteFromUrl(url) {
+ var a = document.createElement('a');
+ a.href = url;
+ return a.hostname;
+}
+
+function isDisallowedUrl(url) {
+ return url.startsWith('chrome') || url.startsWith('about');
+}
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-128.png b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-128.png
new file mode 100644
index 00000000000..2814544cafb
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-128.png
Binary files differ
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-16.png b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-16.png
new file mode 100644
index 00000000000..9313a62fc67
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-16.png
Binary files differ
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-19.png b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-19.png
new file mode 100644
index 00000000000..84d1b8271dd
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-19.png
Binary files differ
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-38.png b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-38.png
new file mode 100644
index 00000000000..998b3f1a1dc
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-38.png
Binary files differ
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-48.png b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-48.png
new file mode 100644
index 00000000000..0f61571e41e
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/contrast-48.png
Binary files differ
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/help.html b/chromium/ui/accessibility/extensions/color_contrast_companion/help.html
new file mode 100644
index 00000000000..e99eb01c612
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/help.html
@@ -0,0 +1,151 @@
+<head>
+ <title>
+ Color Contrast Companion
+ </title>
+ <style>
+ .logo {
+ float: left;
+ }
+ h1 {
+ padding-top: 24px;
+ }
+ body, div, p {
+ font-family: system-ui, sans-serif;
+ font-size: 14pt;
+ line-height: 1.5;
+ }
+ p {
+ margin: 24px 0;
+ }
+ body {
+ background-color: #eee;
+ padding: 0;
+ margin: 0;
+ }
+ #main {
+ background-color: #fff;
+ width: 780px;
+ margin: 0 auto;
+ border-left: 1px solid #444;
+ border-right: 1px solid #444;
+ padding: 24px;
+ min-height: 500px;
+ }
+ .top {
+ width: 100%;
+ float: left;
+ }
+ .note {
+ padding: 0 16px;
+ border: 1px solid #88e;
+ }
+ </style>
+</head>
+<body>
+ <div id="main">
+ <div class="top">
+ <div class="logo">
+ <img src="contrast-128.png" alt="Color Contrast Companion logo">
+ </div>
+ <h1>Color Contrast Companion</h2>
+ <p>
+ <i>Quickly compute the color contrast of pixels anywhere on your screen.</i>
+ </p>
+ </div>
+
+ <p>
+ This is a tool to compute the contrast between a foreground and
+ background color, to help test whether an application is
+ providing enough contrast so that text can be read by people
+ with moderately low vision.
+ </p>
+
+ <p>
+ <a href="https://www.w3.org/TR/WCAG21/#contrast-minimum">WCAG</a>
+ recommends these contrast ratios:
+ <ul>
+ <li> AA: 4.5:1 for all text, 3:1 for 18 pts and larger.
+ <li> AAA: 7:1 for all text, 4.5:1 for 18 pts and larger.
+ <li> Focus indicators: 3:1 if 3px thick, 4.5:1 if less.
+ <a href="https://www.w3.org/WAI/GL/low-vision-a11y-tf/wiki/Contrast_(Minimum)#Focus_Indicators">(Source)</a>
+ </ul>
+ </p>
+ <p>
+ For more information, see the links at the bottom of this page.
+ </p>
+
+ <h2>How to use Color Contrast Companion</h2>
+
+ <div class="note">
+ <p>
+ <b>Note</b>:
+ If you're a web developer or if you're testing a web app, you already
+ have great color contrast tools built directly into Chrome's
+ Developer Tools! For more information, see
+ <a href="https://developers.google.com/web/updates/2018/01/devtools#contrast">
+ Contrast ratio in the Color Picker</a>.
+ </p>
+ <p>
+ However, there are cases where you might want to check the contrast of
+ something on your screen that you can't inspect in Chrome's Developer Tools -
+ such as Chrome's UI, text inside an image, or another app outside of Chrome.
+ For those cases, this extension can help!
+ </p>
+ </div>
+
+ <p>
+ <b>Step 1</b>:
+ Click on the Color Contrast Companion icon on the right side of the
+ Chrome toolbar. If you have a lot of extensions installed, the icon
+ might be inside the Chrome menu (the three dots).
+ </p>
+
+ <p>
+ <img src="browser_action.png" alt="Image of icon in Chrome toolbar">
+ </p>
+
+ <p>
+ <b>Step 2</b>:
+ A <b>Share Your Screen</b> dialog pops up asking for your permission to
+ share your entire screen with Color Contrast Companion.
+ This is necessary for Color Contrast Companion to take a screenshot of
+ your entire desktop. This image is never saved or sent to any server, it's
+ only used to let you pick colors. Click the Share button.
+ </p>
+
+ <p>
+ <b>Step 3</b>:
+ A window opens up showing a screenshot of your computer screen, magnified.
+ Scroll to the portion of the screen containing the pixels you're interested
+ in. Click once to pick the foreground color, click again to pick the
+ background color. Keep clicking as many times as necessary if you're not
+ happy with the colors you picked the first time.
+ </p>
+
+ <p>
+ <b>Step 4</b>:
+ The contrast ratio is shown at the top of the page. Copy the text from the
+ text box and paste it directly into a bug report if necessary, then click
+ the Close button.
+ </p>
+
+ <h2>Further reading</h2>
+
+ <p>
+ <ul>
+ <li>
+ <a href="https://www.w3.org/TR/WCAG21/#contrast-minimum">
+ Web Contents Accessibility Guidelines (WCAG) 2.1 spec
+ </a>
+ <li>
+
+ <a href="https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html">
+ Understanding Success Criterion 1.4.3: Contrast (Minimum)
+ </a>
+ </li>
+ </ul>
+ </p>
+
+ </div>
+
+</body>
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/highcontrast.js b/chromium/ui/accessibility/extensions/color_contrast_companion/highcontrast.js
new file mode 100644
index 00000000000..83e31cfee98
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/highcontrast.js
@@ -0,0 +1,177 @@
+// Copyright (c) 2014 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.
+
+var mode;
+var enabled = false;
+var scheme = '';
+var timeoutId = null;
+
+var filterMap = {
+ '0': 'url("#hc_extension_off")',
+ '1': 'url("#hc_extension_highcontrast")',
+ '2': 'url("#hc_extension_grayscale")',
+ '3': 'url("#hc_extension_invert")',
+ '4': 'url("#hc_extension_invert_grayscale")',
+ '5': 'url("#hc_extension_yellow_on_black")'
+};
+
+var svgContent =
+ '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"><defs><filter x="0" y="0" width="99999" height="99999" id="hc_extension_off"><feComponentTransfer><feFuncR type="table" tableValues="0 1"/><feFuncG type="table" tableValues="0 1"/><feFuncB type="table" tableValues="0 1"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_highcontrast"><feComponentTransfer><feFuncR type="gamma" exponent="3.0"/><feFuncG type="gamma" exponent="3.0"/><feFuncB type="gamma" exponent="3.0"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_highcontrast_back"><feComponentTransfer><feFuncR type="gamma" exponent="0.33"/><feFuncG type="gamma" exponent="0.33"/><feFuncB type="gamma" exponent="0.33"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_grayscale"><feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/><feComponentTransfer><feFuncR type="gamma" exponent="3"/><feFuncG type="gamma" exponent="3"/><feFuncB type="gamma" exponent="3"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_grayscale_back"><feComponentTransfer><feFuncR type="gamma" exponent="0.33"/><feFuncG type="gamma" exponent="0.33"/><feFuncB type="gamma" exponent="0.33"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_invert"><feComponentTransfer><feFuncR type="gamma" amplitude="-1" exponent="3" offset="1"/><feFuncG type="gamma" amplitude="-1" exponent="3" offset="1"/><feFuncB type="gamma" amplitude="-1" exponent="3" offset="1"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_invert_back"><feComponentTransfer><feFuncR type="table" tableValues="1 0"/><feFuncG type="table" tableValues="1 0"/><feFuncB type="table" tableValues="1 0"/></feComponentTransfer><feComponentTransfer><feFuncR type="gamma" exponent="1.7"/><feFuncG type="gamma" exponent="1.7"/><feFuncB type="gamma" exponent="1.7"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_invert_grayscale"><feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/><feComponentTransfer><feFuncR type="gamma" amplitude="-1" exponent="3" offset="1"/><feFuncG type="gamma" amplitude="-1" exponent="3" offset="1"/><feFuncB type="gamma" amplitude="-1" exponent="3" offset="1"/></feComponentTransfer></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_yellow_on_black"><feComponentTransfer><feFuncR type="gamma" amplitude="-1" exponent="3" offset="1"/><feFuncG type="gamma" amplitude="-1" exponent="3" offset="1"/><feFuncB type="gamma" amplitude="-1" exponent="3" offset="1"/></feComponentTransfer><feColorMatrix type="matrix" values="0.3 0.5 0.2 0 0 0.3 0.5 0.2 0 0 0 0 0 0 0 0 0 0 1 0"/></filter><filter x="0" y="0" width="99999" height="99999" id="hc_extension_yellow_on_black_back"><feComponentTransfer><feFuncR type="table" tableValues="1 0"/><feFuncG type="table" tableValues="1 0"/><feFuncB type="table" tableValues="1 0"/></feComponentTransfer><feComponentTransfer><feFuncR type="gamma" exponent="0.33"/><feFuncG type="gamma" exponent="0.33"/><feFuncB type="gamma" exponent="0.33"/></feComponentTransfer></filter></defs></svg>';
+
+var cssTemplate =
+ 'html[hc="a0"] { -webkit-filter: url("#hc_extension_off"); } html[hcx="0"] img[src*="jpg"], html[hcx="0"] img[src*="jpeg"], html[hcx="0"] svg image, html[hcx="0"] img.rg_i, html[hcx="0"] embed, html[hcx="0"] object, html[hcx="0"] video { -webkit-filter: url("#hc_extension_off"); } html[hc="a1"] { -webkit-filter: url("#hc_extension_highcontrast"); } html[hcx="1"] img[src*="jpg"], html[hcx="1"] img[src*="jpeg"], html[hcx="1"] img.rg_i, html[hcx="1"] svg image, html[hcx="1"] embed, html[hcx="1"] object, html[hcx="1"] video { -webkit-filter: url("#hc_extension_highcontrast_back"); } html[hc="a2"] { -webkit-filter: url("#hc_extension_grayscale"); } html[hcx="2"] img[src*="jpg"], html[hcx="2"] img[src*="jpeg"], html[hcx="2"] img.rg_i, html[hcx="2"] svg image, html[hcx="2"] embed, html[hcx="2"] object, html[hcx="2"] video { -webkit-filter: url("#hc_extension_grayscale_back"); } html[hc="a3"] { -webkit-filter: url("#hc_extension_invert"); } html[hcx="3"] img[src*="jpg"], html[hcx="3"] img[src*="jpeg"], html[hcx="3"] img.rg_i, html[hcx="3"] svg image, html[hcx="3"] embed, html[hcx="3"] object, html[hcx="3"] video { -webkit-filter: url("#hc_extension_invert_back"); } html[hc="a4"] { -webkit-filter: url("#hc_extension_invert_grayscale"); } html[hcx="4"] img[src*="jpg"], html[hcx="4"] img[src*="jpeg"], html[hcx="4"] img.rg_i, html[hcx="4"] svg image, html[hcx="4"] embed, html[hcx="4"] object, html[hcx="4"] video { -webkit-filter: url("#hc_extension_invert_back"); } html[hc="a5"] { -webkit-filter: url("#hc_extension_yellow_on_black"); } html[hcx="5"] img[src*="jpg"], html[hcx="5"] img[src*="jpeg"], html[hcx="5"] img.rg_i, html[hcx="5"] svg image, html[hcx="5"] embed, html[hcx="5"] object, html[hcx="5"] video { -webkit-filter: url("#hc_extension_yellow_on_black_back"); }';
+
+/**
+ * Add the elements to the pgae that make high-contrast adjustments possible.
+ */
+function addOrUpdateExtraElements() {
+ if (!enabled)
+ return;
+
+ // We used to include the CSS, but that doesn't work when the document
+ // uses the <base> element to set a relative url. So instead we
+ // add a <style> element directly to the document with the right
+ // urls hard-coded into it.
+ var style = document.getElementById('hc_style');
+ if (!style) {
+ var baseUrl = window.location.href.replace(window.location.hash, '');
+ var css = cssTemplate.replace(/#/g, baseUrl + '#');
+ style = document.createElement('style');
+ style.id = 'hc_style';
+ style.setAttribute('type', 'text/css');
+ style.innerHTML = css;
+ document.head.appendChild(style);
+ }
+
+ // Starting in Chrome 45 we can't apply a filter to the html element,
+ // so instead we create an element with low z-index that copies the
+ // body's background.
+ var bg = document.getElementById('hc_extension_bkgnd');
+ if (!bg) {
+ bg = document.createElement('div');
+ bg.id = 'hc_extension_bkgnd';
+ bg.style.position = 'fixed';
+ bg.style.left = '0px';
+ bg.style.top = '0px';
+ bg.style.right = '0px';
+ bg.style.bottom = '0px';
+ bg.style.zIndex = -1999999999;
+ document.body.appendChild(bg);
+ }
+ bg.style.display = 'block';
+ bg.style.background = window.getComputedStyle(document.body).background;
+
+ // As a special case, replace a zero-alpha background with white,
+ // otherwise we can't invert it.
+ var c = bg.style.backgroundColor;
+ c = c.replace(/\s\s*/g, '');
+ if (m = /^rgba\(([\d]+),([\d]+),([\d]+),([\d]+|[\d]*.[\d]+)\)/.exec(c)) {
+ if (m[4] == '0') {
+ bg.style.backgroundColor = '#fff';
+ }
+ }
+
+ // Add a hidden element with the SVG filters.
+ var wrap = document.getElementById('hc_extension_svg_filters');
+ if (wrap)
+ return;
+
+ wrap = document.createElement('span');
+ wrap.id = 'hc_extension_svg_filters';
+ wrap.setAttribute('hidden', '');
+ wrap.innerHTML = svgContent;
+ document.body.appendChild(wrap);
+}
+
+/**
+ * This is called on load and every time the mode might have changed
+ * (i.e. enabling/disabling, or changing the type of contrast adjustment
+ * for this page).
+ */
+function update() {
+ var html = document.documentElement;
+ if (enabled) {
+ if (!document.body) {
+ window.setTimeout(update, 100);
+ return;
+ }
+ addOrUpdateExtraElements();
+ if (html.getAttribute('hc') != mode + scheme)
+ html.setAttribute('hc', mode + scheme);
+ if (html.getAttribute('hcx') != scheme)
+ html.setAttribute('hcx', scheme);
+
+ if (window == window.top) {
+ window.scrollBy(0, 1);
+ window.scrollBy(0, -1);
+ }
+ } else {
+ html.setAttribute('hc', mode + '0');
+ html.setAttribute('hcx', '0');
+ window.setTimeout(function() {
+ html.removeAttribute('hc');
+ html.removeAttribute('hcx');
+ var bg = document.getElementById('hc_extension_bkgnd');
+ if (bg)
+ bg.style.display = 'none';
+ }, 0);
+ }
+}
+
+/**
+ * Called when we get a message from the background page.
+ */
+function onExtensionMessage(request) {
+ if (enabled != request.enabled || scheme != request.scheme) {
+ enabled = request.enabled;
+ scheme = request.scheme;
+ update();
+ }
+}
+
+/**
+ * KeyDown event handler
+ */
+function onKeyDown(evt) {
+ if (evt.keyCode == 122 /* F11 */ && evt.shiftKey) {
+ chrome.extension.sendRequest({'toggle_global': true});
+ evt.stopPropagation();
+ evt.preventDefault();
+ return false;
+ }
+ if (evt.keyCode == 123 /* F12 */ && evt.shiftKey) {
+ chrome.extension.sendRequest({'toggle_site': true});
+ evt.stopPropagation();
+ evt.preventDefault();
+ return false;
+ }
+ return true;
+}
+
+function init() {
+ if (window == window.top) {
+ mode = 'a';
+ } else {
+ mode = 'b';
+ }
+ chrome.extension.onRequest.addListener(onExtensionMessage);
+ chrome.extension.sendRequest({'init': true}, onExtensionMessage);
+ document.addEventListener('keydown', onKeyDown, false);
+
+ // Update again after a few seconds and again after load so that
+ // the background isn't wrong for long.
+ window.setTimeout(addOrUpdateExtraElements, 2000);
+ window.addEventListener('load', function() {
+ addOrUpdateExtraElements();
+
+ // Also update when the document body attributes change.
+ var config = {attributes: true, childList: false, characterData: false};
+ var observer = new MutationObserver(function(mutations) {
+ addOrUpdateExtraElements();
+ });
+ observer.observe(document.body, config);
+ });
+}
+
+init();
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/manifest.json b/chromium/ui/accessibility/extensions/color_contrast_companion/manifest.json
new file mode 100644
index 00000000000..2f065558368
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/manifest.json
@@ -0,0 +1,22 @@
+{
+ "background": {
+ "scripts": [ "background.js" ]
+ },
+ "browser_action": {
+ "default_icon": {
+ "19": "contrast-19.png",
+ "38": "contrast-38.png"
+ },
+ "default_title": "Color Contrast Companion"
+ },
+ "description": "Quickly compute the color contrast of pixels anywhere on your screen.",
+ "icons": {
+ "128": "contrast-128.png",
+ "16": "contrast-16.png",
+ "48": "contrast-48.png"
+ },
+ "manifest_version": 2,
+ "name": "Color Contrast Companion",
+ "permissions": [ "desktopCapture", "tabs" ],
+ "version": "0.0.5"
+}
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/ui.html b/chromium/ui/accessibility/extensions/color_contrast_companion/ui.html
new file mode 100644
index 00000000000..fafc5183701
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/ui.html
@@ -0,0 +1,130 @@
+<head>
+ <style>
+ #top_panel {
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ height: 96px;
+ display: flex;
+ }
+ #img_panel {
+ cursor: crosshair;
+ position: fixed;
+ bottom: 0px;
+ left: 0px;
+ right: 0px;
+ top: 96px;
+ overflow: scroll;
+ padding: 4px;
+ border: 4px solid #ff9900;
+ }
+ .color {
+ margin: 8px;
+ width: 128px;
+ height: 48px;
+ border: 1px solid #000;
+ }
+ .rgb {
+ }
+ .ratio {
+ width: 88px;
+ height: 48px;
+ border: 1px solid #000;
+ text-align: center;
+ line-height: 48px;
+ font-size: 18px;
+ margin: 8px;
+ }
+ #details {
+ width: 148px;
+ height: 72px;
+ margin: 8px;
+ }
+ .current {
+ outline: 3px solid #f00;
+ }
+ .group {
+ }
+ .gtop {
+ }
+ .caption {
+ margin-left: 8px;
+ }
+ button {
+ width: 90px;
+ height: 32px;
+ margin: 8px 8px 0 8px;
+ color: #000;
+ font-weight: bold;
+ background-color: #fc9;
+ }
+ button:disabled {
+ color: #999;
+ }
+ canvas {
+ image-rendering: pixelated;
+ transform-origin: top left;
+ }
+ #highlight_container {
+ position: relative;
+ width: 0;
+ height: 0;
+ overflow: visible;
+ }
+ #highlight {
+ position: absolute;
+ outline: 2px solid #ff9900;
+ outline-offset: 2px;
+ z-index: 2;
+ pointer-events: none;
+ }
+ </style>
+</head>
+<body>
+ <div id="top_panel">
+ <div style="width:96px">
+ <img src="contrast-128.png" width=96 height=96 >
+ </div>
+ <div class="group">
+ <div class="gtop">
+ <div class="color" id="hover"></div>
+ </div>
+ <div class="caption">Hover: <span class="rgb" id="hoverrgb"></span></div>
+ </div>
+ <div class="group">
+ <div class="gtop">
+ <div class="color" id="fg"></div>
+ </div>
+ <div class="caption">Foreground: <span class="rgb" id="fgrgb"><span></div>
+ </div>
+ <div class="group">
+ <div class="gtop">
+ <div class="color" id="bg"></div>
+ </div>
+ <div class="caption">Background: <span class="rgb" id="bgrgb"></span></div>
+ </div>
+ <div>
+ <div class="ratio" id="ratio"></div>
+ <div class="caption">Contrast Ratio</div>
+ </div>
+ <div>
+ <textarea id="details"></textarea>
+ </div>
+ <div style="width: 104px">
+ <button id="zoomin">Zoom In (+)</button>
+ <button id="zoomout">Zoom Out (-)</button>
+ </div>
+ <div style="width: 104px">
+ <button id="close">Close</button>
+ <button id="help">Help</button>
+ </div>
+ </div>
+ <div id="img_panel">
+ <div id="highlight_container">
+ <div id="highlight"></div>
+ </div>
+ <canvas></canvas>
+ </div>
+ <script src="ui.js"></script>
+</body>
diff --git a/chromium/ui/accessibility/extensions/color_contrast_companion/ui.js b/chromium/ui/accessibility/extensions/color_contrast_companion/ui.js
new file mode 100644
index 00000000000..4fc2ff3df9c
--- /dev/null
+++ b/chromium/ui/accessibility/extensions/color_contrast_companion/ui.js
@@ -0,0 +1,290 @@
+// Copyright 2022 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.
+
+var maxScale = 32;
+
+var scale = 4;
+var fgtoggle = true;
+var fgcolor = null;
+var bgcolor = null;
+var srcImage = null;
+var imageData = null;
+var prevScale = 1;
+
+let close = document.getElementById('close');
+close.addEventListener('click', () => {
+ console.log('Sending message');
+ chrome.runtime.sendMessage({'close': true});
+});
+let help = document.getElementById('help');
+help.addEventListener('click', () => {
+ window.open(chrome.runtime.getURL('help.html'), '_blank');
+});
+
+let canvas = document.querySelector('canvas');
+
+function repaint() {
+ console.log('Repaint ' + scale);
+ let width = srcImage.naturalWidth;
+ let height = srcImage.naturalHeight;
+ let context = canvas.getContext('2d');
+ context.imageSmoothingEnabled = false;
+ canvas.style.transform = 'scale(' + scale + ')';
+
+ canvas.width = 1;
+ canvas.height = 1;
+ canvas.offsetLeft;
+ canvas.width = width;
+ canvas.height = height;
+ canvas.offsetLeft;
+
+ context.drawImage(srcImage, 0, 0, width, height);
+ imageData = context.getImageData(0, 0, width, height).data;
+}
+
+
+chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
+ if (request.imageDataUrl) {
+ var img = document.createElement('img');
+ img.addEventListener('load', () => {
+ srcImage = img;
+ repaint();
+ });
+ img.src = request.imageDataUrl;
+ }
+});
+
+let hover = document.getElementById('hover');
+let hoverrgb = document.getElementById('hoverrgb');
+let fg = document.getElementById('fg');
+let fgrgb = document.getElementById('fgrgb');
+let bg = document.getElementById('bg');
+let bgrgb = document.getElementById('bgrgb');
+let scrollpanel = document.getElementById('img_panel');
+let ratio = document.getElementById('ratio');
+let details = document.getElementById('details');
+let zoomin = document.getElementById('zoomin');
+let zoomout = document.getElementById('zoomout');
+let highlight = document.getElementById('highlight');
+
+fg.classList.add('current');
+bg.classList.remove('current');
+
+function rgbToHex(color) {
+ var r = color[0];
+ var g = color[1];
+ var b = color[2];
+ return '#' + ((r << 16) | (g << 8) | b).toString(16);
+}
+
+function getRelativeLuminance(color) {
+ var rSRGB = color[0] / 255;
+ var gSRGB = color[1] / 255;
+ var bSRGB = color[2] / 255;
+ var r =
+ rSRGB <= .03928 ? rSRGB / 12.92 : Math.pow((rSRGB + .055) / 1.055, 2.4);
+ var g =
+ gSRGB <= .03928 ? gSRGB / 12.92 : Math.pow((gSRGB + .055) / 1.055, 2.4);
+ var b =
+ bSRGB <= .03928 ? bSRGB / 12.92 : Math.pow((bSRGB + .055) / 1.055, 2.4);
+ return .2126 * r + .7152 * g + .0722 * b;
+};
+
+function getContrast(color1, color2) {
+ var c1lum = getRelativeLuminance(color1);
+ var c2lum = getRelativeLuminance(color2);
+ return (Math.max(c1lum, c2lum) + .05) / (Math.min(c1lum, c2lum) + .05);
+}
+
+let context = canvas.getContext('2d');
+let bounds = canvas.getBoundingClientRect();
+
+function getXY(evt) {
+ var x = evt.clientX + scrollpanel.scrollLeft - bounds.left;
+ var y = evt.clientY + scrollpanel.scrollTop - bounds.top;
+ return [Math.floor(x / scale), Math.floor(y / scale)];
+}
+
+function getColor(x, y) {
+ try {
+ var pixelIndex = y * srcImage.naturalWidth + x;
+ return imageData.slice(4 * pixelIndex, 4 * (pixelIndex + 1));
+ } catch (e) {
+ return [0, 0, 0, 0];
+ }
+}
+
+function brightness(color) {
+ return (color[0] + color[1] + color[2]) / 3;
+}
+
+function localMax(x, y) {
+ // This needs to be optional. Doesn't always do what we want.
+ return [x, y];
+
+ if (x < 0 || x >= srcImage.naturalWidth || y < 0 ||
+ y + j >= srcImage.naturalHeight) {
+ return [x, y];
+ }
+
+ var ctr = getColor(x, y);
+ var max = brightness(ctr);
+
+ var max;
+ var amax;
+ for (var i = -2; i <= 2; i++) {
+ for (var j = -2; j <= 2; j++) {
+ if (x + i < 0 || x + i >= srcImage.naturalWidth)
+ continue;
+ if (y + j < 0 || y + j >= srcImage.naturalHeight)
+ continue;
+ var c = getColor(x + i, y + j);
+ var cbright = brightness(c);
+ if (max > 128 && cbright > max) {
+ max = cbright;
+ amax = [x + i, y + j];
+ } else if (max < 128 && cbright < max) {
+ max = cbright;
+ amax = [x + i, y + j];
+ }
+ }
+ }
+
+ if (amax)
+ return amax;
+ else
+ return [x, y];
+}
+
+canvas.addEventListener('mousemove', (evt) => {
+ var x1, y1, x, y;
+ [x1, y1] = getXY(evt);
+ [x, y] = localMax(x1, y1);
+ var color = getColor(x, y);
+ var hex = rgbToHex(color);
+ hover.style.backgroundColor = hex;
+ hoverrgb.innerText = hex;
+
+ if (scale >= 8) {
+ highlight.style.display = 'block';
+ highlight.style.left = (scale * x) + 'px';
+ highlight.style.top = (scale * y) + 'px';
+ highlight.style.width = scale + 'px';
+ highlight.style.height = scale + 'px';
+ highlight.style.top = (scale * y) + 'px';
+ } else {
+ highlight.style.display = 'none';
+ }
+});
+
+canvas.addEventListener('mouseenter', (evt) => {
+ highlight.style.display = 'block';
+});
+
+canvas.addEventListener('mouseleave', (evt) => {
+ highlight.style.display = 'none';
+});
+
+canvas.addEventListener('click', (evt) => {
+ var x1, y1, x, y;
+ [x1, y1] = getXY(evt);
+ [x, y] = localMax(x1, y1);
+ var color = getColor(x, y);
+ var hex = rgbToHex(color);
+ if (fgtoggle) {
+ fg.style.backgroundColor = hex;
+ fgrgb.innerText = hex;
+ fgcolor = color;
+ bg.classList.add('current');
+ fg.classList.remove('current');
+ } else {
+ bg.style.backgroundColor = hex;
+ bgrgb.innerText = hex;
+ bgcolor = color;
+ fg.classList.add('current');
+ bg.classList.remove('current');
+ }
+ fgtoggle = !fgtoggle;
+ if (fgcolor && bgcolor) {
+ var contrast = getContrast(fgcolor, bgcolor);
+ ratio.innerText = contrast.toFixed(2);
+ details.innerHTML = 'Foreground: ' + fgrgb.innerText + '\n' +
+ 'Background: ' + bgrgb.innerText + '\n' +
+ 'Ratio: ' + ratio.innerText;
+ details.select();
+ }
+});
+
+function updateZoom() {
+ highlight.style.display = 'none';
+ var prevScrollLeft = scrollpanel.scrollLeft;
+ var prevScrollTop = scrollpanel.scrollTop;
+ var panelBounds = scrollpanel.getBoundingClientRect();
+
+ localStorage.setItem('scale', scale);
+ zoomout.disabled = (scale == 1);
+ zoomin.disabled = (scale >= maxScale);
+ if (srcImage)
+ repaint();
+
+ console.log('prev: ' + prevScrollLeft + ', ' + prevScrollTop);
+ console.log('factor: ' + (scale / prevScale));
+ var newLeft = prevScrollLeft * (scale / prevScale);
+ var newTop = prevScrollTop * (scale / prevScale);
+ console.log('newLeft: ' + newLeft);
+ console.log('newTop: ' + newTop);
+ if (scale > prevScale) {
+ newLeft += panelBounds.width / 2;
+ newTop += panelBounds.height / 2;
+ console.log('c newLeft: ' + newLeft);
+ console.log('c newTop: ' + newTop);
+ } else if (scale < prevScale) {
+ newLeft -= panelBounds.width / 4;
+ newTop -= panelBounds.height / 4;
+ console.log('c newLeft: ' + newLeft);
+ console.log('c newTop: ' + newTop);
+ }
+ scrollpanel.scrollLeft = newLeft;
+ scrollpanel.scrollTop = newTop;
+ prevScale = scale;
+}
+
+var scalevalue = localStorage.getItem('scale');
+scale = parseInt(scalevalue, 10);
+if (!scale || scale < 1 || scale > maxScale)
+ scale = 4;
+prevScale = scale;
+
+updateZoom();
+
+function onZoomIn() {
+ if (scale < maxScale)
+ scale *= 2;
+ updateZoom();
+}
+
+function onZoomOut() {
+ if (scale > 1)
+ scale /= 2;
+ updateZoom();
+}
+
+zoomin.addEventListener('click', () => {
+ onZoomIn();
+});
+
+zoomout.addEventListener('click', () => {
+ onZoomOut();
+});
+
+document.addEventListener('keydown', function(e) {
+ if (e.key == '+' || e.key == '=') {
+ onZoomIn();
+ }
+ if (e.key == '-') {
+ onZoomOut();
+ }
+
+ console.log(e.key);
+});
diff --git a/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_as.xtb b/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_as.xtb
index 5285dcc23f5..75516aee16e 100644
--- a/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_as.xtb
+++ b/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_as.xtb
@@ -19,7 +19,7 @@
<translation id="2648340354586434750">শব্দ অনুসৰি আঁতৰাবলৈ &lt;span class=’key’&gt;Option&lt;/span&gt; ধৰি থাকক।</translation>
<translation id="2795227192542594043">এই এক্সটেনশ্বনটোৱে আপোনাক ৱেব পৃষ্ঠাত এটা স্থানান্তৰযোগ্য কাৰ্ছৰ প্ৰদান কৰে যি আপোনাক কীব’ৰ্ডৰ দ্বাৰা পাঠ বাছনি কৰাৰ সুবিধা প্ৰদান কৰে।</translation>
<translation id="2808027189040546825">১ম প্ৰদক্ষেপ: আটাইতকৈ বেছি ধূসৰ তৰাযুক্ত শাৰীটো বাছনি কৰক:</translation>
-<translation id="2965611304828530558">&lt;p&gt;আপুনি যেতিয়া কোনো লিংক বা নিয়ন্ত্ৰণ গৈ পায় তেতিয়া তাত স্বয়ংক্ৰিয়ভাৱে ফ’কাছ কৰা হয়। কোনো লিংক বা বুটামত ক্লিক কৰিবলৈ &lt;span class=’key’&gt;Enter&lt;/span&gt; টিপক। &lt;/p&gt; &lt;p&gt; যেতিয়া কোনো ফ’কাছ কৰি ৰখা নিয়ন্ত্ৰণে (যেনে পাঠ বাকচ বা সূচীৰ বাকচ) কাঁড় কী কেপচাৰ কৰি থাকে তেতিয়া , কাৰেট ব্ৰাউজিং অব্যাহত ৰাখিবলৈ বাওঁ বা সোঁ কাঁড়ৰ পিছত &lt;span class=’key’&gt;Esc&lt;/span&gt;টিপক। &lt;/p&gt; &lt;p&gt; ইয়াৰ পৰিৱৰ্তে পৰৱৰ্তী ফ’কাছ কৰিবপৰা নিয়ন্ত্ৰণলৈ যাবলৈ &lt;span class=’key’&gt;Tab&lt;/span&gt; টিপক। &lt;/p&gt;</translation>
+<translation id="2965611304828530558">&lt;p&gt;আপুনি যেতিয়া কোনো লিংক বা নিয়ন্ত্ৰণ গৈ পায় তেতিয়া তাত স্বয়ংক্ৰিয়ভাৱে ফ’কাছ কৰা হয়। কোনো লিংক বা বুটামত ক্লিক কৰিবলৈ &lt;span class=’key’&gt;Enter&lt;/span&gt; টিপক। &lt;/p&gt; &lt;p&gt; যেতিয়া কোনো ফ’কাছ কৰি ৰখা নিয়ন্ত্ৰণে (যেনে পাঠ বাকচ বা সূচীৰ বাকচ) কাঁড় কী কেপচাৰ কৰি থাকে তেতিয়া , কাৰেট ব্ৰাউজিং অব্যাহত ৰাখিবলৈ বাওঁ বা সোঁ কাঁড়ৰ পাছত &lt;span class=’key’&gt;Esc&lt;/span&gt;টিপক। &lt;/p&gt; &lt;p&gt; ইয়াৰ পৰিৱৰ্তে পৰৱৰ্তী ফ’কাছ কৰিবপৰা নিয়ন্ত্ৰণলৈ যাবলৈ &lt;span class=’key’&gt;Tab&lt;/span&gt; টিপক। &lt;/p&gt;</translation>
<translation id="3252573918265662711">ছেট আপ</translation>
<translation id="3410969471888629217">ছাইট কাষ্টমাইজেশ্বনৰ সুবিধাটো পাহৰি যাওক</translation>
<translation id="3435896845095436175">সক্ষম কৰক</translation>
diff --git a/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_te.xtb b/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_te.xtb
index 63539df4ebb..e603638b369 100644
--- a/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_te.xtb
+++ b/chromium/ui/accessibility/extensions/strings/accessibility_extensions_strings_te.xtb
@@ -13,7 +13,7 @@
<translation id="1996252509865389616">ప్రారంభించాలా?</translation>
<translation id="2079545284768500474">చర్య రద్దు</translation>
<translation id="2179565792157161713">సుదీర్ఘ వివరణను కొత్త ట్యాబ్‌లో తెరువు</translation>
-<translation id="2223143012868735942">రంగు గ్రాహ్యతను మెరుగుపరచడానికి వెబ్‌పేజీలకు వర్తింపజేసే అనుకూలీకరించగల రంగు ఫిల్టర్.</translation>
+<translation id="2223143012868735942">రంగు గ్రాహ్యతను మెరుగుపరచడానికి వెబ్‌పేజీలకు వర్తింపజేసే అనుకూలంగా మార్చగల రంగు ఫిల్టర్.</translation>
<translation id="2394933097471027016">ఇప్పుడే దీన్ని ప్రయత్నించండి - కేరెట్ బ్రౌజింగ్ ఈ పేజీలో ఎల్లప్పుడూ ప్రారంభించబడి ఉంటుంది!</translation>
<translation id="2471847333270902538"><ph name="SITE" /> కోసం రంగు స్కీమ్:</translation>
<translation id="2648340354586434750">పదాల వారీగా తరలించడానికి &lt;span class='key'&gt;Option&lt;/span&gt;ని నొక్కి పట్టుకోండి.</translation>
diff --git a/chromium/ui/accessibility/mojom/BUILD.gn b/chromium/ui/accessibility/mojom/BUILD.gn
index 3302ef749a9..af01cc903fb 100644
--- a/chromium/ui/accessibility/mojom/BUILD.gn
+++ b/chromium/ui/accessibility/mojom/BUILD.gn
@@ -5,6 +5,7 @@
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mojom") {
+ generate_java = true
sources = [
"ax_action_data.mojom",
"ax_event.mojom",
@@ -26,7 +27,27 @@ mojom("mojom") {
"//url/mojom:url_mojom_gurl",
]
- cpp_typemaps = [
+ common_typemaps = [
+ {
+ types = [
+ {
+ mojom = "ax.mojom.AXRelativeBounds"
+ cpp = "::ui::AXRelativeBounds"
+ },
+ ]
+ traits_headers = [ "ax_relative_bounds_mojom_traits.h" ]
+ traits_public_deps = [
+ ":mojom_traits",
+ "//ui/gfx",
+ "//ui/gfx/geometry/mojom",
+ "//ui/gfx/geometry/mojom:mojom_traits",
+ "//ui/gfx/mojom",
+ ]
+ },
+ ]
+
+ cpp_typemaps = common_typemaps
+ cpp_typemaps += [
{
types = [
{
@@ -78,12 +99,10 @@ mojom("mojom") {
cpp = "::ui::AXRelativeBounds"
},
]
- traits_sources = [ "ax_relative_bounds_mojom_traits.cc" ]
traits_headers = [ "ax_relative_bounds_mojom_traits.h" ]
traits_public_deps = [
- "//ui/gfx",
+ "//ui/accessibility:ax_base",
"//ui/gfx/geometry/mojom",
- "//ui/gfx/geometry/mojom:mojom_traits",
"//ui/gfx/mojom",
]
},
@@ -121,7 +140,10 @@ mojom("mojom") {
traits_public_deps = [ "//ui/accessibility:ax_base" ]
},
]
+
+ blink_cpp_typemaps = common_typemaps
}
+
mojom("ax_assistant_mojom") {
sources = [ "ax_assistant_structure.mojom" ]
@@ -160,3 +182,17 @@ mojom("ax_assistant_mojom") {
"//url/mojom:url_mojom_gurl",
]
}
+
+source_set("mojom_traits") {
+ sources = [
+ "ax_relative_bounds_mojom_traits.cc",
+ "ax_relative_bounds_mojom_traits.h",
+ ]
+ public_deps = [
+ "//ui/accessibility:ax_base",
+ "//ui/accessibility/mojom:mojom_shared_cpp_sources",
+ "//ui/gfx",
+ "//ui/gfx/geometry/mojom:mojom_traits",
+ "//ui/gfx/mojom:mojom",
+ ]
+}
diff --git a/chromium/ui/accessibility/platform/BUILD.gn b/chromium/ui/accessibility/platform/BUILD.gn
index 052fb4b18db..f602ee43c03 100644
--- a/chromium/ui/accessibility/platform/BUILD.gn
+++ b/chromium/ui/accessibility/platform/BUILD.gn
@@ -87,6 +87,7 @@ source_set("platform") {
public_deps = [
"//ui/accessibility:ax_base",
+ "//ui/base:buildflags",
"//ui/display",
]
@@ -180,6 +181,8 @@ source_set("platform") {
"ax_utils_mac.mm",
"inspect/ax_call_statement_invoker_mac.h",
"inspect/ax_call_statement_invoker_mac.mm",
+ "inspect/ax_element_wrapper_mac.h",
+ "inspect/ax_element_wrapper_mac.mm",
"inspect/ax_event_recorder_mac.h",
"inspect/ax_event_recorder_mac.mm",
"inspect/ax_inspect_utils_mac.h",
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
index 7cae2b153b7..34f8ee47116 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -4,8 +4,7 @@
#include "ui/accessibility/platform/ax_fragment_root_win.h"
-#include <unordered_map>
-
+#include "base/containers/flat_map.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
@@ -252,7 +251,7 @@ class AXFragmentRootMapWin {
}
private:
- std::unordered_map<gfx::AcceleratedWidget, AXFragmentRootWin*> map_;
+ base::flat_map<gfx::AcceleratedWidget, AXFragmentRootWin*> map_;
};
AXFragmentRootWin::AXFragmentRootWin(gfx::AcceleratedWidget widget,
diff --git a/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc b/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc
index 889f7496405..1635f46a8b9 100644
--- a/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc
@@ -7,6 +7,7 @@
#include <string>
#include <utility>
+#include "ui/accessibility/ax_enum_localization_util.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
@@ -115,120 +116,6 @@ static void AXPlatformAtkHyperlinkClassInit(AtkHyperlinkClass* klass) {
klass->get_end_index = AXPlatformAtkHyperlinkGetEndIndex;
}
-//
-// AtkAction interface.
-//
-
-static AXPlatformNodeAuraLinux* ToAXPlatformNodeAuraLinuxFromHyperlinkAction(
- AtkAction* atk_action) {
- if (!IS_AX_PLATFORM_ATK_HYPERLINK(atk_action))
- return nullptr;
-
- return ToAXPlatformNodeAuraLinux(AX_PLATFORM_ATK_HYPERLINK(atk_action));
-}
-
-static gboolean ax_platform_atk_hyperlink_do_action(AtkAction* action,
- gint index) {
- g_return_val_if_fail(ATK_IS_ACTION(action), FALSE);
- g_return_val_if_fail(!index, FALSE);
-
- AXPlatformNodeAuraLinux* obj =
- ToAXPlatformNodeAuraLinuxFromHyperlinkAction(action);
- if (!obj)
- return FALSE;
-
- obj->DoDefaultAction();
-
- return TRUE;
-}
-
-static gint ax_platform_atk_hyperlink_get_n_actions(AtkAction* action) {
- g_return_val_if_fail(ATK_IS_ACTION(action), FALSE);
-
- AXPlatformNodeAuraLinux* obj =
- ToAXPlatformNodeAuraLinuxFromHyperlinkAction(action);
- if (!obj)
- return 0;
-
- return 1;
-}
-
-static const gchar* ax_platform_atk_hyperlink_get_description(AtkAction* action,
- gint index) {
- g_return_val_if_fail(ATK_IS_ACTION(action), FALSE);
- g_return_val_if_fail(!index, FALSE);
-
- AXPlatformNodeAuraLinux* obj =
- ToAXPlatformNodeAuraLinuxFromHyperlinkAction(action);
- if (!obj)
- return nullptr;
-
- // Not implemented
- return nullptr;
-}
-
-static const gchar* ax_platform_atk_hyperlink_get_keybinding(AtkAction* action,
- gint index) {
- g_return_val_if_fail(ATK_IS_ACTION(action), FALSE);
- g_return_val_if_fail(!index, FALSE);
-
- AXPlatformNodeAuraLinux* obj =
- ToAXPlatformNodeAuraLinuxFromHyperlinkAction(action);
- if (!obj)
- return nullptr;
-
- return obj->GetStringAttribute(ax::mojom::StringAttribute::kAccessKey)
- .c_str();
-}
-
-static const gchar* ax_platform_atk_hyperlink_get_name(AtkAction* atk_action,
- gint index) {
- g_return_val_if_fail(ATK_IS_ACTION(atk_action), FALSE);
- g_return_val_if_fail(!index, FALSE);
-
- AXPlatformNodeAuraLinux* obj =
- ToAXPlatformNodeAuraLinuxFromHyperlinkAction(atk_action);
- if (!obj)
- return nullptr;
-
- int action;
- if (!obj->GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb,
- &action))
- return nullptr;
- std::string action_verb =
- ui::ToString(static_cast<ax::mojom::DefaultActionVerb>(action));
- ATK_AURALINUX_RETURN_STRING(action_verb);
-}
-
-static const gchar* ax_platform_atk_hyperlink_get_localized_name(
- AtkAction* atk_action,
- gint index) {
- g_return_val_if_fail(ATK_IS_ACTION(atk_action), FALSE);
- g_return_val_if_fail(!index, FALSE);
-
- AXPlatformNodeAuraLinux* obj =
- ToAXPlatformNodeAuraLinuxFromHyperlinkAction(atk_action);
- if (!obj)
- return nullptr;
-
- int action;
- if (!obj->GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb,
- &action))
- return nullptr;
- std::string action_verb =
- ui::ToLocalizedString(static_cast<ax::mojom::DefaultActionVerb>(action));
- ATK_AURALINUX_RETURN_STRING(action_verb);
-}
-
-static void atk_action_interface_init(AtkActionIface* iface) {
- iface->do_action = ax_platform_atk_hyperlink_do_action;
- iface->get_n_actions = ax_platform_atk_hyperlink_get_n_actions;
- iface->get_description = ax_platform_atk_hyperlink_get_description;
- iface->get_keybinding = ax_platform_atk_hyperlink_get_keybinding;
- iface->get_name = ax_platform_atk_hyperlink_get_name;
- iface->get_localized_name = ax_platform_atk_hyperlink_get_localized_name;
-}
-
void ax_platform_atk_hyperlink_set_object(
AXPlatformAtkHyperlink* atk_hyperlink,
AXPlatformNodeAuraLinux* platform_node) {
@@ -263,13 +150,8 @@ GType ax_platform_atk_hyperlink_get_type() {
nullptr /* value table */
};
- static const GInterfaceInfo actionInfo = {
- (GInterfaceInitFunc)(GInterfaceInitFunc)atk_action_interface_init,
- (GInterfaceFinalizeFunc)0, 0};
-
GType type = g_type_register_static(
ATK_TYPE_HYPERLINK, "AXPlatformAtkHyperlink", &tinfo, GTypeFlags(0));
- g_type_add_interface_static(type, ATK_TYPE_ACTION, &actionInfo);
g_once_init_leave(&type_volatile, type);
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node.cc b/chromium/ui/accessibility/platform/ax_platform_node.cc
index 1c72c838ae2..a0c15c29d8b 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node.cc
@@ -94,7 +94,6 @@ void AXPlatformNode::RemoveAXModeObserver(AXModeObserver* observer) {
// static
void AXPlatformNode::NotifyAddAXModeFlags(AXMode mode_flags) {
- // Note: this is only called on Windows, and in tests.
AXMode new_ax_mode(ax_mode_);
new_ax_mode |= mode_flags;
@@ -108,7 +107,6 @@ void AXPlatformNode::NotifyAddAXModeFlags(AXMode mode_flags) {
// static
void AXPlatformNode::SetAXMode(AXMode new_mode) {
- // Note: this is only called on Windows.
ax_mode_ = new_mode;
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 5254242e25e..9650287fb80 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "ui/accessibility/platform/ax_platform_node_auralinux.h"
+#include "base/memory/raw_ptr.h"
#include <dlfcn.h>
#include <stdint.h>
@@ -125,7 +126,7 @@ typedef struct _AXPlatformNodeAuraLinuxClass AXPlatformNodeAuraLinuxClass;
struct _AXPlatformNodeAuraLinuxObject {
AtkObject parent;
- AXPlatformNodeAuraLinux* m_object;
+ raw_ptr<AXPlatformNodeAuraLinux> m_object;
};
struct _AXPlatformNodeAuraLinuxClass {
@@ -330,8 +331,10 @@ const char* BuildDescriptionFromHeaders(AXPlatformNodeDelegate* delegate,
std::vector<std::string> names;
for (const auto& node_id : ids) {
if (AXPlatformNode* header = delegate->GetFromNodeID(node_id)) {
- if (AtkObject* atk_header = header->GetNativeViewAccessible())
- names.push_back(atk_object_get_name(atk_header));
+ if (AtkObject* atk_header = header->GetNativeViewAccessible()) {
+ if (const gchar* name = atk_object_get_name(atk_header))
+ names.push_back(name);
+ }
}
}
@@ -606,13 +609,10 @@ gboolean DoAction(AtkAction* atk_action, gint index) {
if (!obj)
return FALSE;
- const std::vector<ax::mojom::Action> actions = obj->GetSupportedActions();
+ const std::vector<ax::mojom::Action> actions =
+ obj->GetDelegate()->GetSupportedActions();
g_return_val_if_fail(index < static_cast<gint>(actions.size()), FALSE);
- if (index == 0 && obj->HasDefaultActionVerb()) {
- // If there is a default action, it will always be at index 0.
- return obj->DoDefaultAction();
- }
AXActionData data;
data.action = actions[index];
return obj->GetDelegate()->AccessibilityPerformAction(data);
@@ -627,7 +627,7 @@ gint GetNActions(AtkAction* atk_action) {
if (!obj)
return 0;
- return static_cast<gint>(obj->GetSupportedActions().size());
+ return static_cast<gint>(obj->GetDelegate()->GetSupportedActions().size());
}
const gchar* GetDescription(AtkAction*, gint) {
@@ -645,10 +645,11 @@ const gchar* GetName(AtkAction* atk_action, gint index) {
if (!obj)
return nullptr;
- const std::vector<ax::mojom::Action> actions = obj->GetSupportedActions();
+ const std::vector<ax::mojom::Action> actions =
+ obj->GetDelegate()->GetSupportedActions();
g_return_val_if_fail(index < static_cast<gint>(actions.size()), nullptr);
- if (index == 0 && obj->HasDefaultActionVerb()) {
+ if (index == 0 && obj->GetDelegate()->HasDefaultActionVerb()) {
// If there is a default action, it will always be at index 0.
return obj->GetDefaultActionName();
}
@@ -664,10 +665,11 @@ const gchar* GetKeybinding(AtkAction* atk_action, gint index) {
if (!obj)
return nullptr;
- const std::vector<ax::mojom::Action> actions = obj->GetSupportedActions();
+ const std::vector<ax::mojom::Action> actions =
+ obj->GetDelegate()->GetSupportedActions();
g_return_val_if_fail(index < static_cast<gint>(actions.size()), nullptr);
- if (index == 0 && obj->HasDefaultActionVerb()) {
+ if (index == 0 && obj->GetDelegate()->HasDefaultActionVerb()) {
// If there is a default action, it will always be at index 0. Only the
// default action has a key binding.
return obj->GetStringAttribute(ax::mojom::StringAttribute::kAccessKey)
@@ -1013,10 +1015,11 @@ gunichar GetCharacterAtOffset(AtkText* atk_text, int offset) {
return 0;
std::u16string text = obj->GetHypertext();
- int32_t text_length = text.length();
+ size_t text_length = text.length();
offset = obj->UnicodeToUTF16OffsetInText(offset);
- int32_t limited_offset = base::clamp(offset, 0, text_length);
+ offset = std::max(offset, 0);
+ size_t limited_offset = std::min(static_cast<size_t>(offset), text_length);
base_icu::UChar32 code_point;
base::ReadUnicodeCharacter(text.c_str(), text_length + 1, &limited_offset,
@@ -3892,14 +3895,7 @@ void AXPlatformNodeAuraLinux::EmitCaretChangedSignal() {
return;
}
-#if DCHECK_IS_ON()
- AXTree::Selection unignored_selection =
- GetDelegate()->GetUnignoredSelection();
- DCHECK(HasCaret(&unignored_selection));
-#endif
-
std::pair<int, int> selection = GetSelectionOffsetsForAtk();
-
AtkObject* atk_object = GetOrCreateAtkObject();
if (!atk_object)
return;
@@ -4343,8 +4339,8 @@ AXPlatformNodeAuraLinux::GetHypertextAdjustments() {
text_unicode_adjustments_.emplace();
std::u16string text = GetHypertext();
- int32_t text_length = text.size();
- for (int32_t i = 0; i < text_length; i++) {
+ size_t text_length = text.size();
+ for (size_t i = 0; i < text_length; i++) {
base_icu::UChar32 code_point;
size_t original_i = i;
base::ReadUnicodeCharacter(text.c_str(), text_length + 1, &i, &code_point);
@@ -4618,12 +4614,6 @@ bool AXPlatformNodeAuraLinux::
return false;
}
-bool AXPlatformNodeAuraLinux::DoDefaultAction() {
- AXActionData action_data;
- action_data.action = ax::mojom::Action::kDoDefault;
- return delegate_->AccessibilityPerformAction(action_data);
-}
-
const gchar* AXPlatformNodeAuraLinux::GetDefaultActionName() {
int action;
if (!GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb, &action))
@@ -4732,9 +4722,7 @@ bool AXPlatformNodeAuraLinux::IsNameExposed() {
}
int AXPlatformNodeAuraLinux::GetCaretOffset() {
- AXTree::Selection unignored_selection =
- GetDelegate()->GetUnignoredSelection();
- if (!HasCaret(&unignored_selection)) {
+ if (!HasVisibleCaretOrSelection()) {
absl::optional<FindInPageResultInfo> result =
GetSelectionOffsetsFromFindInPage();
AtkObject* atk_object = GetOrCreateAtkObject();
@@ -5221,28 +5209,4 @@ std::pair<int, int> AXPlatformNodeAuraLinux::GetSelectionOffsetsForAtk() {
return selection;
}
-bool AXPlatformNodeAuraLinux::HasDefaultActionVerb() const {
- return GetData().GetDefaultActionVerb() !=
- ax::mojom::DefaultActionVerb::kNone;
-}
-
-std::vector<ax::mojom::Action> AXPlatformNodeAuraLinux::GetSupportedActions()
- const {
- static const base::NoDestructor<std::vector<ax::mojom::Action>>
- kActionsThatCanBeExposedViaAtkAction{
- {ax::mojom::Action::kDecrement, ax::mojom::Action::kIncrement}};
- std::vector<ax::mojom::Action> supported_actions;
-
- // The default action, if it exists, must be listed at index 0.
- if (HasDefaultActionVerb())
- supported_actions.push_back(ax::mojom::Action::kDoDefault);
-
- for (const auto& item : *kActionsThatCanBeExposedViaAtkAction) {
- if (HasAction(item))
- supported_actions.push_back(item);
- }
-
- return supported_actions;
-}
-
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
index c71f1359aea..1635ac83ae5 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -154,7 +154,6 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
bool GrabFocusOrSetSequentialFocusNavigationStartingPointAtOffset(int offset);
bool GrabFocusOrSetSequentialFocusNavigationStartingPoint();
bool SetSequentialFocusNavigationStartingPoint();
- bool DoDefaultAction();
const gchar* GetDefaultActionName();
AtkAttributeSet* GetAtkAttributes();
@@ -293,9 +292,6 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// nullopt.
absl::optional<std::pair<int, int>> GetEmbeddedObjectIndices();
- std::vector<ax::mojom::Action> GetSupportedActions() const;
- bool HasDefaultActionVerb() const;
-
std::string accessible_name_;
protected:
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index e2c5a8e317e..bd381d55d09 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -7,6 +7,7 @@
#include <utility>
#include <vector>
+#include "base/memory/raw_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/platform/atk_util_auralinux.h"
@@ -1000,6 +1001,7 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkActionGetNActions) {
root.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
root.AddAction(ax::mojom::Action::kDecrement);
root.AddAction(ax::mojom::Action::kIncrement);
+ // Additionally, any object will have a context menu action, that makes it 4
Init(root);
AtkObject* root_obj(GetRootAtkObject());
@@ -1009,7 +1011,7 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkActionGetNActions) {
gint number_of_actions = atk_action_get_n_actions(ATK_ACTION(root_obj));
- EXPECT_EQ(3, number_of_actions);
+ EXPECT_EQ(4, number_of_actions);
g_object_unref(root_obj);
}
@@ -1027,7 +1029,9 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkActionGetNActionsNoActions) {
gint number_of_actions = atk_action_get_n_actions(ATK_ACTION(root_obj));
- EXPECT_EQ(0, number_of_actions);
+ // In absence of any other actions, we would expose the default and the
+ // context menu actions.
+ EXPECT_EQ(2, number_of_actions);
g_object_unref(root_obj);
}
@@ -1047,14 +1051,16 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkActionGetName) {
g_object_ref(root_obj);
const gchar* action_name = atk_action_get_name(ATK_ACTION(root_obj), 0);
- // The index 0 is reserved for the default action. The rest of actions are
- // presented in the order they were added.
+ // The index 0 is reserved for the default action, and the index 1 to the
+ // context menu action. The rest of actions are presented in the order they
+ // were added.
EXPECT_STREQ("click", action_name);
action_name = atk_action_get_name(ATK_ACTION(root_obj), 1);
- EXPECT_STREQ("decrement", action_name);
+ EXPECT_STREQ("showContextMenu", action_name);
action_name = atk_action_get_name(ATK_ACTION(root_obj), 2);
+ EXPECT_STREQ("decrement", action_name);
+ action_name = atk_action_get_name(ATK_ACTION(root_obj), 3);
EXPECT_STREQ("increment", action_name);
- atk_action_do_action(ATK_ACTION(root_obj), 2);
g_object_unref(root_obj);
}
@@ -1078,10 +1084,11 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkActionDoAction) {
EXPECT_EQ(root_node, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
EXPECT_TRUE(atk_action_do_action(ATK_ACTION(root_obj), 1));
EXPECT_TRUE(atk_action_do_action(ATK_ACTION(root_obj), 2));
+ EXPECT_TRUE(atk_action_do_action(ATK_ACTION(root_obj), 3));
// Test that querying actions out of bounds doesn't crash
EXPECT_FALSE(atk_action_do_action(ATK_ACTION(root_obj), -1));
- EXPECT_FALSE(atk_action_do_action(ATK_ACTION(root_obj), 3));
+ EXPECT_FALSE(atk_action_do_action(ATK_ACTION(root_obj), 4));
g_object_unref(root_obj);
}
@@ -1258,6 +1265,38 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkHyperlink) {
g_object_unref(root_obj);
}
+TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkHyperlinkActions) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kLink;
+ root.AddStringAttribute(ax::mojom::StringAttribute::kUrl, "http://foo.com");
+ root.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
+ Init(root);
+
+ AtkObject* root_obj(GetRootAtkObject());
+ ASSERT_TRUE(ATK_IS_OBJECT(root_obj));
+ ASSERT_TRUE(ATK_IS_HYPERLINK_IMPL(root_obj));
+ ASSERT_TRUE(ATK_IS_ACTION(root_obj));
+ g_object_ref(root_obj);
+ auto* root_node = GetRootAsAXNode();
+
+ gint number_of_actions = atk_action_get_n_actions(ATK_ACTION(root_obj));
+ EXPECT_EQ(2, number_of_actions);
+
+ // The index 0 is reserved for the default action, and the index 1 to the
+ // context menu action. The rest of actions are presented in the order they
+ // were added.
+ const gchar* action_name = atk_action_get_name(ATK_ACTION(root_obj), 0);
+ EXPECT_STREQ("click", action_name);
+ action_name = atk_action_get_name(ATK_ACTION(root_obj), 1);
+ EXPECT_STREQ("showContextMenu", action_name);
+
+ EXPECT_TRUE(atk_action_do_action(ATK_ACTION(root_obj), 0));
+ EXPECT_EQ(root_node, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
+
+ g_object_unref(root_obj);
+}
+
//
// AtkText interface
//
@@ -1738,7 +1777,7 @@ class ActivationTester {
g_signal_handler_disconnect(target_, deactivate_id_);
}
- AtkObject* target_;
+ raw_ptr<AtkObject> target_;
bool saw_activate_ = false;
bool saw_deactivate_ = false;
gulong activate_id_ = 0;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.cc b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
index 7ec9cd89979..0bf97a38d7c 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
@@ -10,9 +10,10 @@
#include <set>
#include <sstream>
#include <string>
-#include <unordered_map>
+#include "base/containers/flat_map.h"
#include "base/no_destructor.h"
+#include "base/numerics/checked_math.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
@@ -65,7 +66,7 @@ bool FindDescendantRoleWithMaxDepth(const AXPlatformNodeBase* node,
const char16_t AXPlatformNodeBase::kEmbeddedCharacter = u'\xfffc';
// Map from each AXPlatformNode's unique id to its instance.
-using UniqueIdMap = std::unordered_map<int32_t, AXPlatformNode*>;
+using UniqueIdMap = base::flat_map<int32_t, AXPlatformNode*>;
base::LazyInstance<UniqueIdMap>::Leaky g_unique_id_map =
LAZY_INSTANCE_INITIALIZER;
@@ -193,6 +194,7 @@ std::string AXPlatformNodeBase::GetName() const {
name += extra_text;
}
+ DCHECK(base::IsStringUTF8AllowingNoncharacters(name)) << "Invalid UTF8";
return name;
}
return std::string();
@@ -824,10 +826,6 @@ bool AXPlatformNodeBase::IsPlatformDocument() const {
return delegate_ && delegate_->IsPlatformDocument();
}
-bool AXPlatformNodeBase::IsPlatformDocumentWithContent() const {
- return delegate_ && delegate_->IsPlatformDocumentWithContent();
-}
-
bool AXPlatformNodeBase::IsStructuredAnnotation() const {
// The node represents a structured annotation if it can trace back to a
// target node that is being annotated.
@@ -1115,27 +1113,8 @@ absl::optional<float> AXPlatformNodeBase::GetFontSizeInPoints() const {
return absl::nullopt;
}
-bool AXPlatformNodeBase::HasCaret(const AXTree::Selection* selection) {
- if (IsAtomicTextField() &&
- HasIntAttribute(ax::mojom::IntAttribute::kTextSelStart) &&
- HasIntAttribute(ax::mojom::IntAttribute::kTextSelEnd)) {
- return true;
- }
-
- // The caret is always at the focus of the selection.
- int32_t focus_id;
- if (selection)
- focus_id = selection->focus_object_id;
- else
- focus_id = delegate_->GetTreeData().sel_focus_object_id;
-
- AXPlatformNodeBase* focus_object =
- static_cast<AXPlatformNodeBase*>(delegate_->GetFromNodeID(focus_id));
-
- if (!focus_object)
- return false;
-
- return focus_object->IsDescendantOf(this);
+bool AXPlatformNodeBase::HasVisibleCaretOrSelection() const {
+ return delegate_ && delegate_->HasVisibleCaretOrSelection();
}
bool AXPlatformNodeBase::IsLeaf() const {
@@ -1153,7 +1132,7 @@ bool AXPlatformNodeBase::IsInvisibleOrIgnored() const {
if (HasState(ax::mojom::State::kFocusable))
return !IsFocused();
- return !const_cast<AXPlatformNodeBase*>(this)->HasCaret();
+ return !HasVisibleCaretOrSelection();
}
bool AXPlatformNodeBase::IsFocused() const {
@@ -1262,11 +1241,13 @@ void AXPlatformNodeBase::ComputeAttributes(PlatformAttributeList* attributes) {
case ax::mojom::DescriptionFrom::kPopupElement:
// The following types of markup are mapped to "tooltip":
// * The title attribute.
- // * A related popup=hint related via togglepopup/showpopup/hidepopup.
+ // * A related popup=hint related via popuptoggletarget /
+ // popupshowtarget / popuphidetarget.
// * A tooltip related via aria-describedby (see kRelatedElement above).
from = "tooltip";
break;
case ax::mojom::DescriptionFrom::kNone:
+ case ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty:
NOTREACHED();
}
DCHECK(!from.empty());
@@ -1777,6 +1758,31 @@ int32_t AXPlatformNodeBase::GetHypertextOffsetFromChild(
return GetHypertextOffsetFromHyperlinkIndex(hyperlink_index);
}
+int AXPlatformNodeBase::HypertextOffsetFromChildIndex(int child_index) const {
+ DCHECK_GE(child_index, 0);
+ DCHECK_LE(child_index, static_cast<int>(GetChildCount()));
+
+ // Use both a child index and an iterator to avoid an O(n^2) complexity which
+ // would be the case if we were to call GetChildAtIndex on each child.
+ int hypertext_offset = 0;
+ int endpoint_child_index = 0;
+ for (AXPlatformNodeChildIterator child_iter = AXPlatformNodeChildrenBegin();
+ child_iter != AXPlatformNodeChildrenEnd(); ++child_iter) {
+ if (endpoint_child_index >= child_index) {
+ break;
+ }
+
+ int child_text_len = 1;
+ if (child_iter->IsText())
+ child_text_len =
+ base::checked_cast<int>(child_iter->GetHypertext().size());
+
+ endpoint_child_index++;
+ hypertext_offset += child_text_len;
+ }
+ return hypertext_offset;
+}
+
int32_t AXPlatformNodeBase::GetHypertextOffsetFromDescendant(
AXPlatformNodeBase* descendant) {
auto* parent_object = static_cast<AXPlatformNodeBase*>(
@@ -1795,44 +1801,50 @@ int32_t AXPlatformNodeBase::GetHypertextOffsetFromDescendant(
int AXPlatformNodeBase::GetHypertextOffsetFromEndpoint(
AXPlatformNodeBase* endpoint_object,
int endpoint_offset) {
+ DCHECK_GE(endpoint_offset, 0);
+
// There are three cases:
- // 1. The selection endpoint is inside this object but not one of its
- // descendants, or is in an ancestor of this object. endpoint_offset should be
+ // 1. The selection endpoint is this object itself: endpoint_offset should be
// returned, possibly adjusted from a child offset to a hypertext offset.
- // 2. The selection endpoint is a descendant of this object. The offset of the
+ // 2. The selection endpoint is an ancestor of this object. If endpoint_offset
+ // points out after this object, then this object text length is returned,
+ // otherwise 0.
+ // 3. The selection endpoint is a descendant of this object. The offset of the
// character in this object's hypertext corresponding to the subtree in which
// the endpoint is located should be returned.
- // 3. The selection endpoint is in a completely different part of the tree.
+ // 4. The selection endpoint is in a completely different part of the tree.
// Either 0 or hypertext length should be returned depending on the direction
// that one needs to travel to find the endpoint.
//
// TODO(nektar): Replace all this logic with the use of AXNodePosition.
- // Case 1. Is the endpoint object equal to this object or an ancestor of this
- // object?
- //
- // IsDescendantOf includes the case when endpoint_object == this.
- if (IsDescendantOf(endpoint_object)) {
- if (endpoint_object->IsLeaf()) {
- DCHECK_EQ(endpoint_object, this) << "Text objects cannot have children.";
+ // Case 1. Is the endpoint object equal to this object
+ if (endpoint_object == this) {
+ if (endpoint_object->IsLeaf())
return endpoint_offset;
- } else {
- DCHECK_GE(endpoint_offset, 0);
- DCHECK_LE(static_cast<size_t>(endpoint_offset),
- endpoint_object->GetDelegate()->GetChildCount());
-
- // Adjust the |endpoint_offset| because the selection endpoint is a tree
- // position, i.e. it represents a child index and not a text offset.
- if (static_cast<size_t>(endpoint_offset) >=
- endpoint_object->GetChildCount()) {
- return static_cast<int>(endpoint_object->GetHypertext().size());
- } else {
- auto* child = static_cast<AXPlatformNodeBase*>(FromNativeViewAccessible(
- endpoint_object->ChildAtIndex(endpoint_offset)));
- DCHECK(child);
- return endpoint_object->GetHypertextOffsetFromChild(child);
- }
+ return HypertextOffsetFromChildIndex(endpoint_offset);
+ }
+
+ // Case 2. Is the endpoint an ancestor of this object.
+ if (IsDescendantOf(endpoint_object)) {
+ DCHECK_LE(endpoint_offset,
+ static_cast<int>(endpoint_object->GetChildCount()));
+
+ AXPlatformNodeBase* closest_ancestor = this;
+ while (closest_ancestor) {
+ AXPlatformNodeBase* parent = static_cast<AXPlatformNodeBase*>(
+ FromNativeViewAccessible(closest_ancestor->GetParent()));
+ if (parent == endpoint_object)
+ break;
+ closest_ancestor = parent;
}
+
+ // If the endpoint is after this node, then return the node's
+ // hypertext length, otherwise 0 as the endpoint points before the node.
+ if (endpoint_offset >
+ static_cast<int>(*closest_ancestor->GetIndexInParent()))
+ return static_cast<int>(GetHypertext().size());
+ return 0;
}
AXPlatformNodeBase* common_parent = this;
@@ -1895,14 +1907,15 @@ AXPlatformNodeBase::AXPosition AXPlatformNodeBase::HypertextOffsetToEndpoint(
DCHECK_GE(hypertext_offset, 0);
DCHECK_LT(hypertext_offset, static_cast<int>(GetHypertext().size()));
- int32_t current_hypertext_offset = hypertext_offset;
+ int current_hypertext_offset = hypertext_offset;
for (auto child_iter = AXPlatformNodeChildrenBegin();
child_iter != AXPlatformNodeChildrenEnd() &&
current_hypertext_offset >= 0;
++child_iter) {
int child_text_len = 1;
if (child_iter->IsText())
- child_text_len = child_iter->GetHypertext().size();
+ child_text_len =
+ base::checked_cast<int>(child_iter->GetHypertext().size());
if (current_hypertext_offset < child_text_len) {
int endpoint_offset = child_text_len - current_hypertext_offset;
@@ -1989,7 +2002,7 @@ void AXPlatformNodeBase::GetSelectionOffsetsFromTree(
// outside this object in their entirety.
// Selections that span more than one character are by definition inside
// this object, so checking them is not necessary.
- if (*selection_start == *selection_end && !HasCaret(selection)) {
+ if (*selection_start == *selection_end && !HasVisibleCaretOrSelection()) {
*selection_start = -1;
*selection_end = -1;
return;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.h b/chromium/ui/accessibility/platform/ax_platform_node_base.h
index ef0715b28f4..9a78c92ee3c 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.h
@@ -267,13 +267,8 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// Returns the font size converted to points, if available.
absl::optional<float> GetFontSizeInPoints() const;
- // Returns true if either a descendant has selection (sel_focus_object_id) or
- // if this node is a simple text element and has text selection attributes.
- // Optionally accepts a selection, which can be useful if checking the
- // unignored selection is required. If not provided, uses the selection from
- // the tree data, which is safe and fast but does not take ignored nodes into
- // account.
- bool HasCaret(const AXTree::Selection* selection = nullptr);
+ // See `AXNode::HasVisibleCaretOrSelection`.
+ bool HasVisibleCaretOrSelection() const;
// See AXPlatformNodeDelegate::IsChildOfLeaf().
bool IsChildOfLeaf() const;
@@ -425,10 +420,6 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// PDF.
bool IsPlatformDocument() const;
- // Returns true if this object is a platform document as described above and
- // also has at least some content.
- bool IsPlatformDocumentWithContent() const;
-
protected:
AXPlatformNodeBase();
@@ -547,6 +538,7 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
int32_t GetHyperlinkIndexFromChild(AXPlatformNodeBase* child);
int32_t GetHypertextOffsetFromHyperlinkIndex(int32_t hyperlink_index);
int32_t GetHypertextOffsetFromChild(AXPlatformNodeBase* child);
+ int HypertextOffsetFromChildIndex(int child_index) const;
int32_t GetHypertextOffsetFromDescendant(AXPlatformNodeBase* descendant);
// If the selection endpoint is either equal to or an ancestor of this object,
@@ -592,6 +584,8 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
friend AXPlatformNode* AXPlatformNode::Create(
AXPlatformNodeDelegate* delegate);
+
+ FRIEND_TEST_ALL_PREFIXES(AXPlatformNodeTest, HypertextOffsetFromEndpoint);
};
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_base_unittest.cc
index 4072a27b660..d19651211ed 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base_unittest.cc
@@ -7,22 +7,14 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/platform/ax_platform_node_unittest.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h"
+#include "ui/accessibility/platform/test_ax_tree_update.h"
+
+using ax::mojom::Role;
+using ax::mojom::State;
namespace ui {
namespace {
-void MakeStaticText(AXNodeData* node, int id, const std::string& text) {
- node->id = id;
- node->role = ax::mojom::Role::kStaticText;
- node->SetName(text);
-}
-
-void MakeGroup(AXNodeData* node, int id, std::vector<int> child_ids) {
- node->id = id;
- node->role = ax::mojom::Role::kGroup;
- node->child_ids = child_ids;
-}
-
void SetIsInvisible(AXTree* tree, int id, bool invisible) {
AXTreeUpdate update;
update.nodes.resize(1);
@@ -45,33 +37,18 @@ void SetRole(AXTree* tree, int id, ax::mojom::Role role) {
} // namespace
TEST_F(AXPlatformNodeTest, GetHypertext) {
- AXTreeUpdate update;
-
// RootWebArea #1
// ++++StaticText "text1" #2
// ++++StaticText "text2" #3
// ++++StaticText "text3" #4
-
- update.root_id = 1;
- update.nodes.resize(4);
-
- update.nodes[0].id = 1;
- update.nodes[0].role = ax::mojom::Role::kRootWebArea;
- update.nodes[0].child_ids = {2, 3, 4};
-
- MakeStaticText(&update.nodes[1], 2, "text1");
- MakeStaticText(&update.nodes[2], 3, "text2");
- MakeStaticText(&update.nodes[3], 4, "text3");
-
- Init(update);
- AXTree& tree = *GetTree();
+ AXTree* tree = Init({Role::kRootWebArea, {{"text1"}, {"text2"}, {"text3"}}});
// Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
// determine if it should enable accessibility.
testing::ScopedAxModeSetter ax_mode_setter(kAXModeComplete);
AXPlatformNodeBase* root = static_cast<AXPlatformNodeBase*>(
- TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
+ TestAXNodeWrapper::GetOrCreate(tree, tree->root())->ax_platform_node());
EXPECT_EQ(root->GetHypertext(), u"text1text2text3");
@@ -89,8 +66,6 @@ TEST_F(AXPlatformNodeTest, GetHypertext) {
}
TEST_F(AXPlatformNodeTest, GetHypertextIgnoredContainerSiblings) {
- AXTreeUpdate update;
-
// RootWebArea #1
// ++genericContainer IGNORED #2
// ++++StaticText "text1" #3
@@ -98,41 +73,18 @@ TEST_F(AXPlatformNodeTest, GetHypertextIgnoredContainerSiblings) {
// ++++StaticText "text2" #5
// ++genericContainer IGNORED #6
// ++++StaticText "text3" #7
+ AXTree* tree =
+ Init({Role::kRootWebArea,
+ {{Role::kGenericContainer, State::kIgnored, {{"text1"}}},
+ {Role::kGenericContainer, State::kIgnored, {{"text2"}}},
+ {Role::kGenericContainer, State::kIgnored, {{"text3"}}}}});
- update.root_id = 1;
- update.nodes.resize(7);
-
- update.nodes[0].id = 1;
- update.nodes[0].role = ax::mojom::Role::kRootWebArea;
- update.nodes[0].child_ids = {2, 4, 6};
-
- update.nodes[1].id = 2;
- update.nodes[1].child_ids = {3};
- update.nodes[1].role = ax::mojom::Role::kGenericContainer;
- update.nodes[1].AddState(ax::mojom::State::kIgnored);
- MakeStaticText(&update.nodes[2], 3, "text1");
-
- update.nodes[3].id = 4;
- update.nodes[3].child_ids = {5};
- update.nodes[3].role = ax::mojom::Role::kGenericContainer;
- update.nodes[3].AddState(ax::mojom::State::kIgnored);
- MakeStaticText(&update.nodes[4], 5, "text2");
-
- update.nodes[5].id = 6;
- update.nodes[5].child_ids = {7};
- update.nodes[5].role = ax::mojom::Role::kGenericContainer;
- update.nodes[5].AddState(ax::mojom::State::kIgnored);
- MakeStaticText(&update.nodes[6], 7, "text3");
-
- Init(update);
-
- AXTree& tree = *GetTree();
// Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
// determine if it should enable accessibility.
ui::testing::ScopedAxModeSetter ax_mode_setter(kAXModeComplete);
AXPlatformNodeBase* root = static_cast<AXPlatformNodeBase*>(
- TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
+ TestAXNodeWrapper::GetOrCreate(tree, tree->root())->ax_platform_node());
EXPECT_EQ(root->GetHypertext(), u"text1text2text3");
@@ -153,25 +105,16 @@ TEST_F(AXPlatformNodeTest, GetHypertextIgnoredContainerSiblings) {
}
TEST_F(AXPlatformNodeTest, GetTextContentIgnoresInvisibleAndIgnored) {
- AXTreeUpdate update;
-
- update.root_id = 1;
- update.nodes.resize(6);
-
- MakeStaticText(&update.nodes[1], 2, "a");
- MakeStaticText(&update.nodes[2], 3, "b");
-
- MakeStaticText(&update.nodes[4], 5, "d");
- MakeStaticText(&update.nodes[5], 6, "e");
-
- MakeGroup(&update.nodes[3], 4, {5, 6});
- MakeGroup(&update.nodes[0], 1, {2, 3, 4});
-
- Init(update);
-
- AXTree& tree = *GetTree();
+ // kGroup
+ // ++kStaticText "a"
+ // ++kStaticText "b"
+ // ++kGroup
+ // ++++kStaticText "d"
+ // ++++kStaticText "e"
+ AXTree* tree =
+ Init({Role::kGroup, {{"a"}, {"b"}, {Role::kGroup, {{"d"}, {"e"}}}}});
auto* root = static_cast<AXPlatformNodeBase*>(
- TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
+ TestAXNodeWrapper::GetOrCreate(tree, tree->root())->ax_platform_node());
// Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
// determine if it should enable accessibility.
@@ -182,26 +125,26 @@ TEST_F(AXPlatformNodeTest, GetTextContentIgnoresInvisibleAndIgnored) {
// Setting invisible or ignored on a static text node causes it to be included
// or excluded from the root node's text content:
{
- SetIsInvisible(&tree, 2, true);
+ SetIsInvisible(tree, 2, true);
EXPECT_EQ(root->GetTextContentUTF16(), u"bde");
- SetIsInvisible(&tree, 2, false);
+ SetIsInvisible(tree, 2, false);
EXPECT_EQ(root->GetTextContentUTF16(), u"abde");
- SetRole(&tree, 2, ax::mojom::Role::kNone);
+ SetRole(tree, 2, ax::mojom::Role::kNone);
EXPECT_EQ(root->GetTextContentUTF16(), u"bde");
- SetRole(&tree, 2, ax::mojom::Role::kStaticText);
+ SetRole(tree, 2, ax::mojom::Role::kStaticText);
EXPECT_EQ(root->GetTextContentUTF16(), u"abde");
}
// Setting invisible or ignored on a group node has no effect on the
// text content:
{
- SetIsInvisible(&tree, 4, true);
+ SetIsInvisible(tree, 4, true);
EXPECT_EQ(root->GetTextContentUTF16(), u"abde");
- SetRole(&tree, 4, ax::mojom::Role::kNone);
+ SetRole(tree, 4, ax::mojom::Role::kNone);
EXPECT_EQ(root->GetTextContentUTF16(), u"abde");
}
}
@@ -573,4 +516,73 @@ TEST_F(AXPlatformNodeTest, CompareTo) {
}
}
}
+
+TEST_F(AXPlatformNodeTest, HypertextOffsetFromEndpoint) {
+ // <p>
+ // <a href="google.com">link</a>
+ // </p>
+ //
+ // kRootWebArea
+ // ++kParagraph
+ // ++++kLink
+ // ++++++kStaticText "link"
+ // ++++++kStaticText "link#2"
+ AXTree* tree =
+ Init({Role::kRootWebArea,
+ {{Role::kParagraph, {{Role::kLink, {{"link"}, {"link#2"}}}}}}});
+ auto* root = static_cast<AXPlatformNodeBase*>(
+ TestAXNodeWrapper::GetOrCreate(tree, tree->root())->ax_platform_node());
+
+ // Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
+ // determine if it should enable accessibility.
+ ui::testing::ScopedAxModeSetter ax_mode_setter(kAXModeComplete);
+
+ auto* paragraph = static_cast<AXPlatformNodeBase*>(
+ AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(0)));
+
+ auto* link = static_cast<AXPlatformNodeBase*>(
+ AXPlatformNode::FromNativeViewAccessible(paragraph->ChildAtIndex(0)));
+
+ auto* static_text = static_cast<AXPlatformNodeBase*>(
+ AXPlatformNode::FromNativeViewAccessible(link->ChildAtIndex(0)));
+
+ auto* static_text2 = static_cast<AXPlatformNodeBase*>(
+ AXPlatformNode::FromNativeViewAccessible(link->ChildAtIndex(1)));
+
+ // End point is a parent, points before/after the link.
+ {
+ EXPECT_EQ(link->GetHypertextOffsetFromEndpoint(paragraph, 0), 0);
+ EXPECT_EQ(link->GetHypertextOffsetFromEndpoint(paragraph, 1), 10);
+ }
+
+ // End point is a parent, points before/after the static texts.
+ {
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(link, 0), 0);
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(link, 1), 4);
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(link, 2), 4);
+
+ EXPECT_EQ(static_text2->GetHypertextOffsetFromEndpoint(link, 0), 0);
+ EXPECT_EQ(static_text2->GetHypertextOffsetFromEndpoint(link, 1), 0);
+ EXPECT_EQ(static_text2->GetHypertextOffsetFromEndpoint(link, 2), 6);
+ }
+
+ // End point is a grand parent, points before/after the static text.
+ {
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(paragraph, 0), 0);
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(paragraph, 1), 4);
+ }
+
+ // End point is |this|, points into |this| text leaf object.
+ {
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(static_text, 0), 0);
+ EXPECT_EQ(static_text->GetHypertextOffsetFromEndpoint(static_text, 4), 4);
+ }
+
+ // End point is |this|, points into |this| hypertext object.
+ {
+ EXPECT_EQ(link->GetHypertextOffsetFromEndpoint(link, 0), 0);
+ EXPECT_EQ(link->GetHypertextOffsetFromEndpoint(link, 1), 4);
+ }
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_cocoa.mm b/chromium/ui/accessibility/platform/ax_platform_node_cocoa.mm
index 84fa4180d2f..3abaab364ac 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_cocoa.mm
+++ b/chromium/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -301,6 +301,7 @@ bool IsAXSetter(SEL selector) {
case ax::mojom::Role::kGenericContainer:
case ax::mojom::Role::kGroup:
case ax::mojom::Role::kRadioGroup:
+ case ax::mojom::Role::kTabPanel:
return true;
default:
break;
@@ -684,16 +685,22 @@ bool IsAXSetter(SEL selector) {
}
- (BOOL)isImage {
- bool isImage =
+ bool has_image_semantics =
ui::IsImage(_node->GetRole()) &&
- !_node->GetBoolAttribute(ax::mojom::BoolAttribute::kCanvasHasFallback);
- DCHECK(!([[self accessibilityRole] isEqualToString:NSAccessibilityImageRole] ^
- isImage))
- << "Internal and native roles do not match when determining if this "
- "object is an image. "
- << "Chrome role: " << ui::ToString(_node->GetRole())
- << ", NSAccessibility role: " << [self accessibilityRole];
- return isImage;
+ !_node->GetBoolAttribute(ax::mojom::BoolAttribute::kCanvasHasFallback) &&
+ !_node->GetChildCount() &&
+ _node->GetNameFrom() != ax::mojom::NameFrom::kAttributeExplicitlyEmpty;
+#if DCHECK_IS_ON()
+ bool is_native_image =
+ [[self accessibilityRole] isEqualToString:NSAccessibilityImageRole];
+ DCHECK_EQ(is_native_image, has_image_semantics)
+ << "\nPresence/lack of native image role do not match the expected "
+ "internal semantics:"
+ << "\n* Chrome role: " << ui::ToString(_node->GetRole())
+ << "\n* NSAccessibility role: " << [self accessibilityRole]
+ << "\n* AXNode: " << *_node;
+#endif
+ return has_image_semantics;
}
- (NSString*)getName {
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate.cc b/chromium/ui/accessibility/platform/ax_platform_node_delegate.cc
index b1d01d7440e..02b953c3be9 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate.cc
@@ -4,6 +4,8 @@
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
+#include "base/containers/fixed_flat_set.h"
+
namespace ui {
gfx::Rect AXPlatformNodeDelegate::GetClippedScreenBoundsRect(
@@ -42,4 +44,38 @@ gfx::Rect AXPlatformNodeDelegate::GetUnclippedFrameBoundsRect(
AXClippingBehavior::kUnclipped, offscreen_result);
}
+bool AXPlatformNodeDelegate::HasDefaultActionVerb() const {
+ return GetData().GetDefaultActionVerb() !=
+ ax::mojom::DefaultActionVerb::kNone;
+}
+
+std::vector<ax::mojom::Action> AXPlatformNodeDelegate::GetSupportedActions()
+ const {
+ static constexpr auto kActionsThatCanBeExposed =
+ base::MakeFixedFlatSet<ax::mojom::Action>(
+ {ax::mojom::Action::kDecrement, ax::mojom::Action::kIncrement,
+ ax::mojom::Action::kScrollUp, ax::mojom::Action::kScrollDown,
+ ax::mojom::Action::kScrollLeft, ax::mojom::Action::kScrollRight,
+ ax::mojom::Action::kScrollForward,
+ ax::mojom::Action::kScrollBackward});
+ std::vector<ax::mojom::Action> supported_actions;
+
+ // The default action must be listed at index 0.
+ // TODO(crbug.com/1370076): Find out why some nodes do not expose a
+ // default action (HasDefaultActionVerb() is false).
+ supported_actions.push_back(ax::mojom::Action::kDoDefault);
+
+ // Users expect to be able to bring a context menu on any object via e.g.
+ // right click, so we make the context menu action available to any object
+ // unconditionally.
+ supported_actions.push_back(ax::mojom::Action::kShowContextMenu);
+
+ for (const auto& item : kActionsThatCanBeExposed) {
+ if (HasAction(item))
+ supported_actions.push_back(item);
+ }
+
+ return supported_actions;
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
index f4b11c48a1d..4e4b7cba0d8 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -168,8 +168,11 @@ class AX_EXPORT AXPlatformNodeDelegate {
virtual bool HasState(ax::mojom::State state) const = 0;
virtual ax::mojom::State GetState() const = 0;
virtual bool HasAction(ax::mojom::Action action) const = 0;
+ bool HasDefaultActionVerb() const;
+ std::vector<ax::mojom::Action> GetSupportedActions() const;
virtual bool HasTextStyle(ax::mojom::TextStyle text_style) const = 0;
virtual ax::mojom::NameFrom GetNameFrom() const = 0;
+ virtual ax::mojom::DescriptionFrom GetDescriptionFrom() const = 0;
// Returns the text of this node and all descendant nodes; including text
// found in embedded objects.
@@ -186,9 +189,7 @@ class AX_EXPORT AXPlatformNodeDelegate {
// field.
virtual std::u16string GetValueForControl() const = 0;
- // Get the unignored selection from the tree, meaning the selection whose
- // endpoints are on unignored nodes. (An ignored node means that the node
- // should not be exposed to platform APIs: See `IsIgnored`.)
+ // See `AXNode::GetUnignoredSelection`.
virtual const AXTree::Selection GetUnignoredSelection() const = 0;
// Creates a text position rooted at this object if it's a leaf node, or a
@@ -270,10 +271,6 @@ class AX_EXPORT AXPlatformNodeDelegate {
// PDF.
virtual bool IsPlatformDocument() const = 0;
- // Returns true if this object is a platform document as described above and
- // also has at least some content.
- virtual bool IsPlatformDocumentWithContent() const = 0;
-
// Returns true if this node is ignored and should be hidden from the
// accessibility tree. Methods that are used to navigate the accessibility
// tree, such as "ChildAtIndex", "GetParent", and "GetChildCount", among
@@ -347,6 +344,11 @@ class AX_EXPORT AXPlatformNodeDelegate {
// Returns the accessible name for the node.
virtual const std::string& GetName() const = 0;
+ // Returns the accessible description for the node.
+ // An accessible description gives more information about the node in
+ // contrast to the accessible name which is a shorter label for the node.
+ virtual const std::string& GetDescription() const = 0;
+
// Returns the text of this node and represent the text of descendant nodes
// with a special character in place of every embedded object. This represents
// the concept of text in ATK and IA2 APIs.
@@ -459,7 +461,7 @@ class AX_EXPORT AXPlatformNodeDelegate {
// Get whether this node is marked as read-only or is disabled.
virtual bool IsReadOnlyOrDisabled() const = 0;
- // Returns true if the caret or selection is visible on this object.
+ // See `AXNode::HasVisibleCaretOrSelection`.
virtual bool HasVisibleCaretOrSelection() const = 0;
// Get another node from this same tree.
@@ -559,8 +561,8 @@ class AX_EXPORT AXPlatformNodeDelegate {
// or treegrid.
virtual bool IsCellOrHeaderOfAriaGrid() const = 0;
- // True if this is a web area, and its grandparent is a presentational iframe.
- virtual bool IsWebAreaForPresentationalIframe() const = 0;
+ // See `AXNode::IsRootWebAreaForPresentationalIframe()`.
+ virtual bool IsRootWebAreaForPresentationalIframe() const = 0;
// Ordered-set-like and item-like nodes.
virtual bool IsOrderedSetItem() const = 0;
@@ -618,7 +620,7 @@ class AX_EXPORT AXPlatformNodeDelegate {
std::string SubtreeToString() { return SubtreeToStringHelper(0u); }
friend std::ostream& operator<<(std::ostream& stream,
- AXPlatformNodeDelegate& delegate) {
+ const AXPlatformNodeDelegate& delegate) {
return stream << delegate.ToString();
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
index 14c9080cf1c..58414cdb95a 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -213,6 +213,11 @@ ax::mojom::NameFrom AXPlatformNodeDelegateBase::GetNameFrom() const {
return GetData().GetNameFrom();
}
+ax::mojom::DescriptionFrom AXPlatformNodeDelegateBase::GetDescriptionFrom()
+ const {
+ return GetData().GetDescriptionFrom();
+}
+
std::u16string AXPlatformNodeDelegateBase::GetTextContentUTF16() const {
// Unlike in web content The "kValue" attribute always takes precedence,
// because we assume that users of this base class, such as Views controls,
@@ -385,10 +390,6 @@ bool AXPlatformNodeDelegateBase::IsPlatformDocument() const {
return ui::IsPlatformDocument(GetRole());
}
-bool AXPlatformNodeDelegateBase::IsPlatformDocumentWithContent() const {
- return IsPlatformDocument() && GetChildCount();
-}
-
bool AXPlatformNodeDelegateBase::IsDescendantOfAtomicTextField() const {
// TODO(nektar): Add const to all tree traversal methods and remove
// const_cast.
@@ -562,6 +563,10 @@ const std::string& AXPlatformNodeDelegateBase::GetName() const {
return GetStringAttribute(ax::mojom::StringAttribute::kName);
}
+const std::string& AXPlatformNodeDelegateBase::GetDescription() const {
+ return GetStringAttribute(ax::mojom::StringAttribute::kDescription);
+}
+
std::u16string AXPlatformNodeDelegateBase::GetHypertext() const {
return std::u16string();
}
@@ -774,14 +779,12 @@ bool AXPlatformNodeDelegateBase::IsCellOrHeaderOfAriaGrid() const {
return false;
}
-bool AXPlatformNodeDelegateBase::IsWebAreaForPresentationalIframe() const {
+bool AXPlatformNodeDelegateBase::IsRootWebAreaForPresentationalIframe() const {
if (!ui::IsPlatformDocument(GetRole()))
return false;
-
AXPlatformNodeDelegate* parent = GetParentDelegate();
if (!parent)
return false;
-
return parent->GetRole() == ax::mojom::Role::kIframePresentational;
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
index 61a378dd299..3113e7144d9 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -96,6 +96,7 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
bool HasAction(ax::mojom::Action action) const override;
bool HasTextStyle(ax::mojom::TextStyle text_style) const override;
ax::mojom::NameFrom GetNameFrom() const override;
+ ax::mojom::DescriptionFrom GetDescriptionFrom() const override;
std::u16string GetTextContentUTF16() const override;
std::u16string GetValueForControl() const override;
const AXTree::Selection GetUnignoredSelection() const override;
@@ -139,7 +140,6 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
bool IsChildOfLeaf() const override;
bool IsDescendantOfAtomicTextField() const override;
bool IsPlatformDocument() const override;
- bool IsPlatformDocumentWithContent() const override;
bool IsLeaf() const override;
bool IsFocused() const override;
bool IsToplevelBrowserWindow() override;
@@ -171,6 +171,7 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
std::unique_ptr<AXPlatformNodeDelegate::ChildIterator> ChildrenEnd() override;
const std::string& GetName() const override;
+ const std::string& GetDescription() const override;
std::u16string GetHypertext() const override;
const std::map<int, int>& GetHypertextOffsetToHyperlinkChildIndex()
const override;
@@ -318,7 +319,7 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
int col_index) const override;
absl::optional<int32_t> CellIndexToId(int cell_index) const override;
bool IsCellOrHeaderOfAriaGrid() const override;
- bool IsWebAreaForPresentationalIframe() const override;
+ bool IsRootWebAreaForPresentationalIframe() const override;
// Ordered-set-like and item-like nodes.
bool IsOrderedSetItem() const override;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
index c78802354ff..cae2a44f1ca 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -28,8 +28,12 @@ void PostAnnouncementNotification(NSString* announcement,
notification_info);
}
void NotifyMacEvent(AXPlatformNodeCocoa* target, ax::mojom::Event event_type) {
- if (![target AXWindow])
+ if (![target AXWindow]) {
+ // A child tree is not attached to the window. Return early, otherwise
+ // AppKit will hang trying to reach the root, resulting in a bug where
+ // VoiceOver keeps repeating "[appname] is not responding".
return;
+ }
NSString* notification =
[AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type];
if (notification)
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
index 37a21fafde0..ad2a2d7e953 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -470,6 +470,28 @@ HRESULT AXPlatformNodeTextRangeProviderWin::FindText(
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_FINDTEXT);
WIN_ACCESSIBILITY_API_PERF_HISTOGRAM(UMA_API_TEXTRANGE_FINDTEXT);
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(string, result);
+ // On Windows, there's a dichotomy in the definition of a text offset in a
+ // text position between different APIs:
+ // - on UIA, a text offset translates to the offset in the text itself
+ // - on IA2, it translates to the offset in the hypertext
+ //
+ // All unignored non-text nodes are represented with an "embedded object
+ // character" in their parent's text representation on IA2, but aren't on UIA.
+ // This leads to different expected MaxTextOffset values for a same text
+ // position. If `string` is found in the text represented by the start/end
+ // endpoints, we'll create text positions in the least common ancestor, use
+ // the flat text representation's offsets of found string, then convert the
+ // positions to leaf. If 'embedded object characters' are considered, instead
+ // of the flat text representation, this falls apart.
+ //
+ // Whether we expose embedded object characters for nodes is managed by the
+ // |g_ax_embedded_object_behavior| global variable set in ax_node_position.cc.
+ // When on Windows, this variable is always set to kExposeCharacter... which
+ // is incorrect if we run UIA-specific code. To avoid problems caused by that,
+ // we use the following ScopedAXEmbeddedObjectBehaviorSetter to modify the
+ // value of the global variable to what is really expected on UIA.
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
+ AXEmbeddedObjectBehavior::kSuppressCharacter);
std::u16string search_string = base::WideToUTF16(string);
if (search_string.length() <= 0)
@@ -1108,9 +1130,7 @@ AXPlatformNodeWin* AXPlatformNodeTextRangeProviderWin::GetOwner() const {
const AXNode* anchor = position->GetAnchor();
DCHECK(anchor);
- AXTreeID tree_id = anchor->tree()->GetAXTreeID();
- const AXTreeManager* ax_tree_manager =
- AXTreeManagerMap::GetInstance().GetManager(tree_id);
+ const AXTreeManager* ax_tree_manager = position->GetManager();
DCHECK(ax_tree_manager);
const AXPlatformTreeManager* platform_tree_manager =
@@ -1359,8 +1379,7 @@ void AXPlatformNodeTextRangeProviderWin::NormalizeAsUnignoredTextRange(
AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetRootDelegate(
const ui::AXTreeID tree_id) {
- const AXTreeManager* ax_tree_manager =
- AXTreeManagerMap::GetInstance().GetManager(tree_id);
+ const AXTreeManager* ax_tree_manager = AXTreeManager::FromID(tree_id);
DCHECK(ax_tree_manager);
AXNode* root_node = ax_tree_manager->GetRootAsAXNode();
const AXPlatformNode* root_platform_node =
@@ -1595,18 +1614,16 @@ void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::SetEnd(
void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::AddObserver(
const AXTreeID tree_id) {
- AXTreeManager* ax_tree_manager =
- AXTreeManagerMap::GetInstance().GetManager(tree_id);
+ AXTreeManager* ax_tree_manager = AXTreeManager::FromID(tree_id);
DCHECK(ax_tree_manager);
- ax_tree_manager->AddObserver(this);
+ ax_tree_manager->ax_tree()->AddObserver(this);
}
void AXPlatformNodeTextRangeProviderWin::TextRangeEndpoints::RemoveObserver(
const AXTreeID tree_id) {
- AXTreeManager* ax_tree_manager =
- AXTreeManagerMap::GetInstance().GetManager(tree_id);
+ AXTreeManager* ax_tree_manager = AXTreeManager::FromID(tree_id);
if (ax_tree_manager)
- ax_tree_manager->RemoveObserver(this);
+ ax_tree_manager->ax_tree()->RemoveObserver(this);
}
// Ensures that our endpoints are located on non-deleted nodes (step 1, case A
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_fuzzer.cc b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_fuzzer.cc
new file mode 100644
index 00000000000..0ebe9747757
--- /dev/null
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_fuzzer.cc
@@ -0,0 +1,245 @@
+// Copyright 2022 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 "base/win/atl.h" // Must be before UIAutomationCore.h
+
+#include <UIAutomationClient.h>
+#include <UIAutomationCore.h>
+#include <UIAutomationCoreApi.h>
+
+#include <memory>
+#include <tuple>
+#include <utility>
+
+#include "base/at_exit.h"
+#include "base/i18n/icu_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/win/atl.h"
+#include "base/win/scoped_bstr.h"
+#include "base/win/scoped_safearray.h"
+#include "base/win/scoped_variant.h"
+#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/accessibility/ax_node_position.h"
+#include "ui/accessibility/ax_position.h"
+#include "ui/accessibility/ax_range.h"
+#include "ui/accessibility/ax_role_properties.h"
+#include "ui/accessibility/ax_tree.h"
+#include "ui/accessibility/ax_tree_data.h"
+#include "ui/accessibility/ax_tree_fuzzer_util.h"
+#include "ui/accessibility/ax_tree_id.h"
+#include "ui/accessibility/ax_tree_update.h"
+#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
+#include "ui/accessibility/platform/ax_fragment_root_win.h"
+#include "ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h"
+#include "ui/accessibility/platform/test_ax_node_wrapper.h"
+#include "ui/accessibility/test_ax_tree_manager.h"
+
+using Microsoft::WRL::ComPtr;
+
+// We generate positions using fuzz data, this constant should be aligned
+// with the amount of bytes needed to generate a new text range.
+constexpr size_t kBytesNeededToGenerateTextRange = 4;
+// This should be aligned with the amount of bytes needed to mutate a text range
+// in ...MutateTextRangeProvider...
+constexpr size_t kBytesNeededToMutateTextRange = 4;
+
+// Min/Max node size for the initial tree.
+constexpr size_t kMinNodeCount = 10;
+constexpr size_t kMaxNodeCount = kMinNodeCount + 50;
+
+// Min fuzz data needed for fuzzer to function.
+// We need to ensure we have enough data to create a tree with text, as well as
+// generate a couple of text ranges and mutate them.
+constexpr size_t kMinFuzzDataSize =
+ kMinNodeCount * AXTreeFuzzerGenerator::kMinimumNewNodeFuzzDataSize +
+ kMinNodeCount * AXTreeFuzzerGenerator::kMinTextFuzzDataSize +
+ 2 * kBytesNeededToGenerateTextRange + 2 * kBytesNeededToMutateTextRange;
+
+// Cap fuzz data to avoid slowness.
+constexpr size_t kMaxFuzzDataSize = 20000;
+
+ui::AXPlatformNode* AXPlatformNodeFromNode(ui::AXTree* tree, ui::AXNode* node) {
+ const ui::TestAXNodeWrapper* wrapper =
+ ui::TestAXNodeWrapper::GetOrCreate(tree, node);
+ return wrapper ? wrapper->ax_platform_node() : nullptr;
+}
+
+template <typename T>
+ComPtr<T> QueryInterfaceFromNode(ui::AXTree* tree, ui::AXNode* node) {
+ ui::AXPlatformNode* ax_platform_node = AXPlatformNodeFromNode(tree, node);
+ if (!ax_platform_node)
+ return ComPtr<T>();
+ ComPtr<T> result;
+ ax_platform_node->GetNativeViewAccessible()->QueryInterface(__uuidof(T),
+ &result);
+ return result;
+}
+
+// This method returns a text range scoped to this node.
+void GetTextRangeProviderFromTextNode(
+ ui::AXTree* tree,
+ ui::AXNode* text_node,
+ ComPtr<ITextRangeProvider>& text_range_provider) {
+ ComPtr<IRawElementProviderSimple> provider_simple =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(tree, text_node);
+ if (!provider_simple.Get())
+ return;
+ ComPtr<ITextProvider> text_provider;
+ provider_simple->GetPatternProvider(UIA_TextPatternId, &text_provider);
+
+ if (!text_provider.Get())
+ return;
+
+ text_provider->get_DocumentRange(&text_range_provider);
+
+ ComPtr<ui::AXPlatformNodeTextRangeProviderWin> text_range_provider_interal;
+ text_range_provider->QueryInterface(
+ IID_PPV_ARGS(&text_range_provider_interal));
+ ui::AXPlatformNode* ax_platform_node =
+ AXPlatformNodeFromNode(tree, text_node);
+ text_range_provider_interal->SetOwnerForTesting(
+ static_cast<ui::AXPlatformNodeWin*>(ax_platform_node));
+}
+
+void CallComparisonAPIs(const ComPtr<ITextRangeProvider>& text_range,
+ const ComPtr<ITextRangeProvider>& other_text_range) {
+ BOOL are_same;
+ std::ignore = text_range->Compare(other_text_range.Get(), &are_same);
+ int compare_endpoints_result;
+ std::ignore = text_range->CompareEndpoints(
+ TextPatternRangeEndpoint_Start, other_text_range.Get(),
+ TextPatternRangeEndpoint_Start, &compare_endpoints_result);
+ std::ignore = text_range->CompareEndpoints(
+ TextPatternRangeEndpoint_End, other_text_range.Get(),
+ TextPatternRangeEndpoint_Start, &compare_endpoints_result);
+ std::ignore = text_range->CompareEndpoints(
+ TextPatternRangeEndpoint_Start, other_text_range.Get(),
+ TextPatternRangeEndpoint_End, &compare_endpoints_result);
+ std::ignore = text_range->CompareEndpoints(
+ TextPatternRangeEndpoint_End, other_text_range.Get(),
+ TextPatternRangeEndpoint_End, &compare_endpoints_result);
+}
+
+TextPatternRangeEndpoint GenerateEndpoint(unsigned char byte) {
+ return (byte % 2) ? TextPatternRangeEndpoint_Start
+ : TextPatternRangeEndpoint_End;
+}
+
+TextUnit GenerateTextUnit(unsigned char byte) {
+ return static_cast<TextUnit>(byte % 7);
+}
+
+enum class TextRangeMutation {
+ kMoveEndpointByRange,
+ kExpandToEnclosingUnit,
+ kMove,
+ kMoveEndpointByUnit,
+ kLast
+};
+
+TextRangeMutation GenerateTextRangeMutation(unsigned char byte) {
+ constexpr unsigned char max =
+ static_cast<unsigned char>(TextRangeMutation::kLast);
+ return static_cast<TextRangeMutation>(byte % max);
+}
+
+void MutateTextRangeProvider(ComPtr<ITextRangeProvider>& text_range,
+ const ComPtr<ITextRangeProvider>& other_text_range,
+ FuzzerData& fuzz_data) {
+ const int kMaxMoveCount = 20;
+ TextUnit unit = GenerateTextUnit(fuzz_data.NextByte());
+ int units_moved;
+
+ TextRangeMutation mutation_type =
+ GenerateTextRangeMutation(fuzz_data.NextByte());
+ switch (mutation_type) {
+ case TextRangeMutation::kMoveEndpointByRange:
+ if (other_text_range.Get()) {
+ text_range->MoveEndpointByRange(GenerateEndpoint(fuzz_data.NextByte()),
+ other_text_range.Get(),
+ GenerateEndpoint(fuzz_data.NextByte()));
+ return;
+ }
+ ABSL_FALLTHROUGH_INTENDED;
+ case TextRangeMutation::kExpandToEnclosingUnit:
+ text_range->ExpandToEnclosingUnit(unit);
+ return;
+ case TextRangeMutation::kMove:
+ text_range->Move(unit, fuzz_data.NextByte() % kMaxMoveCount,
+ &units_moved);
+ return;
+ case TextRangeMutation::kMoveEndpointByUnit:
+ text_range->MoveEndpointByUnit(GenerateEndpoint(fuzz_data.NextByte()),
+ unit, fuzz_data.NextByte() % kMaxMoveCount,
+ &units_moved);
+ return;
+ case TextRangeMutation::kLast:
+ NOTREACHED();
+ }
+}
+
+struct Environment {
+ Environment() { CHECK(base::i18n::InitializeICU()); }
+ base::AtExitManager at_exit_manager;
+};
+
+// Entry point for LibFuzzer.
+extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) {
+ if (size < kMinFuzzDataSize || size > kMaxFuzzDataSize)
+ return 0;
+ static Environment env;
+
+ FuzzerData fuzz_data(data, size);
+ AXTreeFuzzerGenerator generator;
+
+ // Create initial tree.
+ const size_t node_count =
+ kMinNodeCount + fuzz_data.NextByte() % (kMaxNodeCount - kMinNodeCount);
+ generator.GenerateInitialUpdate(fuzz_data, node_count);
+
+ ui::AXTree* tree = generator.GetTree();
+
+ // Run with --v=1 to aid in debugging a specific crash.
+ VLOG(1) << tree->ToString();
+
+ // Loop until data is expended.
+ std::vector<ComPtr<ITextRangeProvider>> created_ranges;
+ while (fuzz_data.RemainingBytes() > kBytesNeededToGenerateTextRange) {
+ // Create a new position on a random node in the tree.
+ {
+ // To ensure that anchor_id is between |ui::kInvalidAXNodeID| and the max
+ // ID of the tree (non-inclusive), get a number [0, max_id - 1) and then
+ // shift by 1 to get [1, max_id)
+ ui::AXNodeID anchor_id =
+ (fuzz_data.NextByte() % (generator.GetMaxAssignedID() - 1)) + 1;
+ ui::AXNode* anchor = tree->GetFromId(anchor_id);
+ if (!anchor)
+ continue;
+ ComPtr<ITextRangeProvider> text_range_provider;
+ GetTextRangeProviderFromTextNode(tree, anchor, text_range_provider);
+ if (text_range_provider)
+ created_ranges.push_back(std::move(text_range_provider));
+ }
+ for (size_t i = 0; i < created_ranges.size(); ++i) {
+ ComPtr<ITextRangeProvider> text_range = created_ranges[i];
+ ComPtr<ITextRangeProvider> other_range;
+ if (i > 0)
+ other_range = created_ranges[i - 1].Get();
+ if (!other_range.Get())
+ continue;
+
+ CallComparisonAPIs(text_range.Get(), other_range);
+ if (fuzz_data.RemainingBytes() > kBytesNeededToMutateTextRange)
+ MutateTextRangeProvider(text_range, other_range, fuzz_data);
+ }
+ // Do a Tree Update.
+ if (!generator.GenerateTreeUpdate(fuzz_data, 5))
+ break;
+ }
+ tree->Destroy();
+ ui::TestAXNodeWrapper::ResetGlobalState();
+ return 0;
+}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index ceee20b402c..8ff0bf08649 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -444,6 +444,8 @@ class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
text_field.AddState(ax::mojom::State::kEditable);
text_field.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
+ text_field.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "text");
text_field.SetValue(ALL_TEXT);
text_field.AddIntListAttribute(ax::mojom::IntListAttribute::kLineStarts,
std::vector<int32_t>{0, 7});
@@ -2993,6 +2995,8 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
text_input_data.AddState(ax::mojom::State::kEditable);
text_input_data.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
+ text_input_data.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "text");
ui::AXNodeData group2_data;
group2_data.id = 5;
@@ -3015,7 +3019,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
empty_text_data.role = ax::mojom::Role::kStaticText;
empty_text_data.AddState(ax::mojom::State::kEditable);
text_content = "";
- empty_text_data.SetName(text_content);
+ empty_text_data.SetNameExplicitlyEmpty();
ComputeWordBoundariesOffsets(text_content, word_start_offsets,
word_end_offsets);
empty_text_data.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
@@ -3376,6 +3380,8 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
search_box.role = ax::mojom::Role::kSearchBox;
search_box.AddState(ax::mojom::State::kEditable);
search_box.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "input");
+ search_box.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "search");
paragraph_data.child_ids.push_back(search_box.id);
ui::AXNodeData search_text;
@@ -3531,6 +3537,124 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
}
TEST_F(AXPlatformNodeTextRangeProviderTest,
+ TestITextRangeProviderGetEnclosingElementRichButton) {
+ // Set up ax tree with the following structure:
+ //
+ // root
+ // ++button_1
+ // ++++static_text_1
+ // ++++++inline_text_1
+ // ++button_2
+ // ++++heading
+ // ++++++statix_text_2
+ // ++++++++inline_text_2
+
+ ui::AXNodeData root;
+ ui::AXNodeData button_1;
+ ui::AXNodeData static_text_1;
+ ui::AXNodeData inline_text_1;
+ ui::AXNodeData button_2;
+ ui::AXNodeData heading;
+ ui::AXNodeData static_text_2;
+ ui::AXNodeData inline_text_2;
+
+ root.id = 1;
+ button_1.id = 2;
+ static_text_1.id = 3;
+ inline_text_1.id = 4;
+ button_2.id = 5;
+ heading.id = 6;
+ static_text_2.id = 7;
+ inline_text_2.id = 8;
+
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {button_1.id, button_2.id};
+
+ button_1.role = ax::mojom::Role::kButton;
+ button_1.child_ids.push_back(static_text_1.id);
+
+ static_text_1.role = ax::mojom::Role::kStaticText;
+ static_text_1.child_ids.push_back(inline_text_1.id);
+
+ inline_text_1.role = ax::mojom::Role::kInlineTextBox;
+
+ button_2.role = ax::mojom::Role::kButton;
+ button_2.child_ids.push_back(heading.id);
+
+ heading.role = ax::mojom::Role::kHeading;
+ heading.child_ids.push_back(static_text_2.id);
+
+ static_text_2.role = ax::mojom::Role::kStaticText;
+ static_text_2.child_ids.push_back(inline_text_2.id);
+
+ inline_text_2.role = ax::mojom::Role::kInlineTextBox;
+
+ ui::AXTreeUpdate update;
+ ui::AXTreeData tree_data;
+ tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+ update.tree_data = tree_data;
+ update.has_tree_data = true;
+ update.root_id = root.id;
+ update.nodes = {root, button_1, static_text_1, inline_text_1,
+ button_2, heading, static_text_2, inline_text_2};
+ Init(update);
+
+ // Set up variables from the tree for testing.
+ AXNode* button_1_node = GetRootAsAXNode()->children()[0];
+ AXNode* static_text_1_node = button_1_node->children()[0];
+ AXNode* inline_text_1_node = static_text_1_node->children()[0];
+ AXNode* button_2_node = GetRootAsAXNode()->children()[1];
+ AXNode* heading_node = button_2_node->children()[0];
+ AXNode* static_text_2_node = heading_node->children()[0];
+ AXNode* inline_text_2_node = static_text_2_node->children()[0];
+ AXPlatformNodeWin* owner =
+ static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(button_1_node));
+ ASSERT_NE(owner, nullptr);
+
+ ComPtr<IRawElementProviderSimple> button_1_node_raw =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(button_1_node);
+ ComPtr<IRawElementProviderSimple> inline_text_1_node_raw =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_1_node);
+
+ ComPtr<IRawElementProviderSimple> static_text_2_node_raw =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(static_text_2_node);
+ ComPtr<IRawElementProviderSimple> inline_text_2_node_raw =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_2_node);
+
+ // 1. The first button should hide its children since it contains a single
+ // text node. Thus, calling GetEnclosingElement on a descendant inline text
+ // box should return the button itself.
+ ComPtr<ITextProvider> text_provider;
+ EXPECT_HRESULT_SUCCEEDED(inline_text_1_node_raw->GetPatternProvider(
+ UIA_TextPatternId, &text_provider));
+
+ ComPtr<ITextRangeProvider> text_range_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ text_provider->get_DocumentRange(&text_range_provider));
+ SetOwner(owner, text_range_provider.Get());
+
+ ComPtr<IRawElementProviderSimple> enclosing_element;
+ EXPECT_HRESULT_SUCCEEDED(
+ text_range_provider->GetEnclosingElement(&enclosing_element));
+ EXPECT_EQ(button_1_node_raw.Get(), enclosing_element.Get());
+
+ // 2. The second button shouldn't hide its children since it doesn't contain a
+ // single text node (it contains a heading node). Thus, calling
+ // GetEnclosingElement on a descendant inline text box should return the
+ // parent node.
+ EXPECT_HRESULT_SUCCEEDED(inline_text_2_node_raw->GetPatternProvider(
+ UIA_TextPatternId, &text_provider));
+
+ EXPECT_HRESULT_SUCCEEDED(
+ text_provider->get_DocumentRange(&text_range_provider));
+ SetOwner(owner, text_range_provider.Get());
+
+ EXPECT_HRESULT_SUCCEEDED(
+ text_range_provider->GetEnclosingElement(&enclosing_element));
+ EXPECT_EQ(static_text_2_node_raw.Get(), enclosing_element.Get());
+}
+
+TEST_F(AXPlatformNodeTextRangeProviderTest,
TestITextRangeProviderMoveEndpointByRange) {
Init(BuildTextDocument({"some text", "more text"}));
@@ -3903,6 +4027,8 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
input_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xFFADC0DEU);
input_text_data.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
+ input_text_data.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "text");
input_text_data.SetName("placeholder");
input_text_data.child_ids = {13};
@@ -3928,6 +4054,8 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
0xFFADC0DEU);
input_text_data2.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
+ input_text_data2.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "text");
input_text_data2.SetName("foo");
input_text_data2.child_ids = {15};
@@ -5112,6 +5240,92 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderFindText) {
}
TEST_F(AXPlatformNodeTextRangeProviderTest,
+ FindTextWithEmbeddedObjectCharacter) {
+ // ++1 kRootWebArea
+ // ++++2 kList
+ // ++++++3 kListItem
+ // ++++++++4 kStaticText
+ // ++++++++++5 kInlineTextBox
+ // ++++++6 kListItem
+ // ++++++++7 kStaticText
+ // ++++++++++8 kInlineTextBox
+ ui::AXNodeData root_1;
+ ui::AXNodeData list_2;
+ ui::AXNodeData list_item_3;
+ ui::AXNodeData static_text_4;
+ ui::AXNodeData inline_box_5;
+ ui::AXNodeData list_item_6;
+ ui::AXNodeData static_text_7;
+ ui::AXNodeData inline_box_8;
+
+ root_1.id = 1;
+ list_2.id = 2;
+ list_item_3.id = 3;
+ static_text_4.id = 4;
+ inline_box_5.id = 5;
+ list_item_6.id = 6;
+ static_text_7.id = 7;
+ inline_box_8.id = 8;
+
+ root_1.role = ax::mojom::Role::kRootWebArea;
+ root_1.child_ids = {list_2.id};
+
+ list_2.role = ax::mojom::Role::kList;
+ list_2.child_ids = {list_item_3.id, list_item_6.id};
+
+ list_item_3.role = ax::mojom::Role::kListItem;
+ list_item_3.child_ids = {static_text_4.id};
+
+ static_text_4.role = ax::mojom::Role::kStaticText;
+ static_text_4.SetName("foo");
+ static_text_4.child_ids = {inline_box_5.id};
+
+ inline_box_5.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_5.SetName("foo");
+
+ list_item_6.role = ax::mojom::Role::kListItem;
+ list_item_6.child_ids = {static_text_7.id};
+
+ static_text_7.role = ax::mojom::Role::kStaticText;
+ static_text_7.child_ids = {inline_box_8.id};
+ static_text_7.SetName("bar");
+
+ inline_box_8.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_8.SetName("bar");
+
+ ui::AXTreeUpdate update;
+ ui::AXTreeData tree_data;
+ tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+ update.tree_data = tree_data;
+ update.has_tree_data = true;
+ update.root_id = root_1.id;
+ update.nodes = {root_1, list_2, list_item_3, static_text_4,
+ inline_box_5, list_item_6, static_text_7, inline_box_8};
+
+ Init(update);
+
+ AXNode* root_node = GetRootAsAXNode();
+ ComPtr<ITextRangeProvider> text_range_provider;
+ GetTextRangeProviderFromTextNode(text_range_provider, root_node);
+
+ base::win::ScopedBstr find_string(L"oobar");
+ Microsoft::WRL::ComPtr<ITextRangeProvider> text_range_provider_found;
+ EXPECT_HRESULT_SUCCEEDED(text_range_provider->FindText(find_string.Get(),
+ false, false, &text_range_provider_found));
+ ASSERT_TRUE(text_range_provider_found.Get());
+ Microsoft::WRL::ComPtr<AXPlatformNodeTextRangeProviderWin>
+ text_range_provider_win;
+ text_range_provider_found->QueryInterface(
+ IID_PPV_ARGS(&text_range_provider_win));
+ ASSERT_TRUE(GetStart(text_range_provider_win.Get())->IsTextPosition());
+ EXPECT_EQ(5, GetStart(text_range_provider_win.Get())->anchor_id());
+ EXPECT_EQ(1, GetStart(text_range_provider_win.Get())->text_offset());
+ ASSERT_TRUE(GetEnd(text_range_provider_win.Get())->IsTextPosition());
+ EXPECT_EQ(8, GetEnd(text_range_provider_win.Get())->anchor_id());
+ EXPECT_EQ(3, GetEnd(text_range_provider_win.Get())->text_offset());
+}
+
+TEST_F(AXPlatformNodeTextRangeProviderTest,
TestITextRangeProviderFindTextBackwards) {
Init(BuildTextDocument({"text", "some", "text"},
false /* build_word_boundaries_offsets */,
@@ -6294,7 +6508,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestValidateStartAndEnd) {
// Now modify the tree so that start_ is pointing to a node that has been
// removed from the tree.
- text_data.SetName("");
+ text_data.SetNameExplicitlyEmpty();
AXTreeUpdate test_update2;
test_update2.nodes = {text_data};
ASSERT_TRUE(GetTree()->Unserialize(test_update2));
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
index 9d8c8f1fd06..d955e088225 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
@@ -7,6 +7,7 @@
#include "ui/accessibility/ax_constants.mojom.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h"
+#include "ui/accessibility/platform/test_ax_tree_update.h"
namespace ui {
@@ -74,6 +75,12 @@ void AXPlatformNodeTest::Init(
Init(update);
}
+AXTree* AXPlatformNodeTest::Init(const TestAXTreeUpdateNode& root) {
+ TestAXTreeUpdate update(root);
+ Init(update);
+ return GetTree();
+}
+
AXTreeUpdate AXPlatformNodeTest::BuildTextField() {
AXNodeData text_field_node;
text_field_node.id = 1;
@@ -81,6 +88,8 @@ AXTreeUpdate AXPlatformNodeTest::BuildTextField() {
text_field_node.AddState(ax::mojom::State::kEditable);
text_field_node.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
+ text_field_node.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "text");
text_field_node.SetValue("How now brown cow.");
AXTreeUpdate update;
@@ -98,6 +107,8 @@ AXTreeUpdate AXPlatformNodeTest::BuildTextFieldWithSelectionRange(
text_field_node.AddState(ax::mojom::State::kEditable);
text_field_node.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
"input");
+ text_field_node.AddStringAttribute(ax::mojom::StringAttribute::kInputType,
+ "text");
text_field_node.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
text_field_node.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart,
start);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_unittest.h b/chromium/ui/accessibility/platform/ax_platform_node_unittest.h
index b006162bd24..a795d986e1a 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_unittest.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_unittest.h
@@ -15,6 +15,8 @@
namespace ui {
+struct TestAXTreeUpdateNode;
+
class AXPlatformNodeTest : public ::testing::Test, public TestAXTreeManager {
public:
AXPlatformNodeTest();
@@ -42,6 +44,9 @@ class AXPlatformNodeTest : public ::testing::Test, public TestAXTreeManager {
const AXNodeData& node11 = AXNodeData(),
const AXNodeData& node12 = AXNodeData());
+ // Initialize given an AXTreeUpdate by given TestAXTreeUpdateNode instance.
+ AXTree* Init(const TestAXTreeUpdateNode& root);
+
AXTreeUpdate BuildTextField();
AXTreeUpdate BuildTextFieldWithSelectionRange(int32_t start, int32_t stop);
AXTreeUpdate BuildContentEditable();
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.cc b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
index 7daf94358ff..36f633b07ac 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
@@ -11,10 +11,10 @@
#include <map>
#include <set>
#include <string>
-#include <unordered_set>
#include <utility>
#include <vector>
+#include "base/containers/flat_set.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram_functions.h"
@@ -40,6 +40,7 @@
#include "ui/accessibility/ax_action_handler_registry.h"
#include "ui/accessibility/ax_active_popup.h"
#include "ui/accessibility/ax_constants.mojom.h"
+#include "ui/accessibility/ax_enum_localization_util.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_mode_observer.h"
#include "ui/accessibility/ax_node_data.h"
@@ -208,7 +209,7 @@ namespace ui {
namespace {
-typedef std::unordered_set<AXPlatformNodeWin*> AXPlatformNodeWinSet;
+typedef base::flat_set<AXPlatformNodeWin*> AXPlatformNodeWinSet;
// Set of all AXPlatformNodeWin objects that were the target of an
// alert event.
base::LazyInstance<AXPlatformNodeWinSet>::Leaky g_alert_targets =
@@ -471,6 +472,18 @@ SAFEARRAY* AXPlatformNodeWin::CreateUIAControllerForArray() {
platform_node_list.push_back(view_popup_node_win);
}
+ // The aria-errormessage attribute (mapped to the kErrormessageId) is expected
+ // to be exposed through the ControllerFor property on UIA:
+ // https://www.w3.org/TR/wai-aria-1.1/#aria-errormessage.
+ if (HasIntAttribute(ax::mojom::IntAttribute::kErrormessageId)) {
+ AXPlatformNodeWin* error_message_node_win =
+ static_cast<AXPlatformNodeWin*>(GetFromUniqueId(
+ GetIntAttribute(ax::mojom::IntAttribute::kErrormessageId)));
+
+ if (IsValidUiaRelationTarget(error_message_node_win))
+ platform_node_list.push_back(error_message_node_win);
+ }
+
return CreateUIAElementsSafeArray(platform_node_list);
}
@@ -761,7 +774,7 @@ AXPlatformNodeWin::UIARoleProperties AXPlatformNodeWin::GetUIARoleProperties() {
// If this is a web area for a presentational iframe, give it a role of
// something other than document so that the fact that it's a separate doc
// is not exposed to AT.
- if (GetDelegate()->IsWebAreaForPresentationalIframe())
+ if (GetDelegate()->IsRootWebAreaForPresentationalIframe())
return {UIALocalizationStrategy::kSupply, UIA_GroupControlTypeId, L"group"};
// See UIARoleProperties for descriptions of the properties.
@@ -1010,8 +1023,8 @@ AXPlatformNodeWin::UIARoleProperties AXPlatformNodeWin::GetUIARoleProperties() {
L"document"};
case ax::mojom::Role::kGraphicsObject:
- return {UIALocalizationStrategy::kSupply, UIA_PaneControlTypeId,
- L"region"};
+ return {UIALocalizationStrategy::kSupply, UIA_GroupControlTypeId,
+ L"group"};
case ax::mojom::Role::kGraphicsSymbol:
return {UIALocalizationStrategy::kSupply, UIA_ImageControlTypeId, L"img"};
@@ -1214,7 +1227,7 @@ AXPlatformNodeWin::UIARoleProperties AXPlatformNodeWin::GetUIARoleProperties() {
L"document"};
case ax::mojom::Role::kPopUpButton: {
- const std::string html_tag =
+ const std::string& html_tag =
GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
if (html_tag == "select") {
return {UIALocalizationStrategy::kDeferToControlType,
@@ -4144,7 +4157,7 @@ IFACEMETHODIMP AXPlatformNodeWin::get_caretOffset(LONG* offset) {
AXPlatformNode::NotifyAddAXModeFlags(kScreenReaderAndHTMLAccessibilityModes);
*offset = 0;
- if (!HasCaret())
+ if (!HasVisibleCaretOrSelection())
return S_FALSE;
int selection_start, selection_end;
@@ -4561,9 +4574,11 @@ IFACEMETHODIMP AXPlatformNodeWin::setSelections(LONG nSelections,
return E_INVALIDARG;
AXPosition start_position =
- start_node->HypertextOffsetToEndpoint(selections->startOffset);
+ start_node->HypertextOffsetToEndpoint(selections->startOffset)
+ ->AsDomSelectionPosition();
AXPosition end_position =
- end_node->HypertextOffsetToEndpoint(selections->endOffset);
+ end_node->HypertextOffsetToEndpoint(selections->endOffset)
+ ->AsDomSelectionPosition();
if (!start_position->IsNullPosition() || end_position->IsNullPosition())
return E_INVALIDARG;
@@ -5223,7 +5238,7 @@ HRESULT AXPlatformNodeWin::GetPropertyValueImpl(PROPERTYID property_id,
// Localized Control Type of "output" whereas the Core-AAM states
// the Localized Control Type of the ARIA status role should be
// "status".
- const std::string html_tag =
+ const std::string& html_tag =
GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
std::u16string localized_control_type =
html_tag == "output"
@@ -6110,7 +6125,7 @@ int AXPlatformNodeWin::MSAARole() {
// If this is a web area for a presentational iframe, give it a role of
// something other than DOCUMENT so that the fact that it's a separate doc
// is not exposed to AT.
- if (GetDelegate()->IsWebAreaForPresentationalIframe())
+ if (GetDelegate()->IsRootWebAreaForPresentationalIframe())
return ROLE_SYSTEM_GROUPING;
switch (GetRole()) {
@@ -6707,7 +6722,7 @@ int32_t AXPlatformNodeWin::ComputeIA2Role() {
// If this is a web area for a presentational iframe, give it a role of
// something other than DOCUMENT so that the fact that it's a separate doc
// is not exposed to AT.
- if (GetDelegate()->IsWebAreaForPresentationalIframe()) {
+ if (GetDelegate()->IsRootWebAreaForPresentationalIframe()) {
return ROLE_SYSTEM_GROUPING;
}
@@ -7273,6 +7288,10 @@ bool AXPlatformNodeWin::IsUIAControl() const {
case ax::mojom::Role::kLabelText:
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kListItem:
+ // Treat the root of a MathML tree as content/control so that it is seen
+ // by UIA clients. The remainder of the tree remains as text for now until
+ // UIA mappings for MathML are defined (https://crbug.com/1260585).
+ case ax::mojom::Role::kMathMLMath:
case ax::mojom::Role::kMeter:
case ax::mojom::Role::kProgressIndicator:
case ax::mojom::Role::kRow:
@@ -7362,20 +7381,15 @@ bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
return true;
auto role = GetRole();
- if (HasPresentationalChildren(role))
- return true;
-
switch (role) {
- // Other elements that are expected by UIA to hide their children without
- // having "Children Presentational: True".
- //
+ // Even though a node with role kButton has presentational children, it
+ // should only hide its children from UIA when it has a single text node
+ // (to avoid having its name announced twice). This is because buttons can
+ // have complex structures and they shouldn't hide their subtree.
+ case ax::mojom::Role::kButton:
// TODO(bebeaudr): We might be able to remove ax::mojom::Role::kLink once
// http://crbug.com/1054514 is fixed. Links should not have to hide their
// children.
- // TODO(virens): |kPdfActionableHighlight| needs to follow a fix similar to
- // links. At present Pdf highlghts have text nodes as children. But, we may
- // enable pdf highlights to have complex children like links based on user
- // feedback.
case ax::mojom::Role::kLink:
// Links with a single text-only child should hide their subtree.
if (GetChildCount() == 1) {
@@ -7383,10 +7397,16 @@ bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
return only_child && only_child->IsText();
}
return false;
+ // TODO(virens): |kPdfActionableHighlight| needs to follow a fix similar to
+ // links. At present Pdf highlights have text nodes as children. But, we may
+ // enable pdf highlights to have complex children like links based on user
+ // feedback.
case ax::mojom::Role::kPdfActionableHighlight:
return true;
default:
- return false;
+ // UIA expects nodes that have "Children Presentational: True" to hide
+ // their children.
+ return HasPresentationalChildren(role);
}
}
@@ -7424,9 +7444,9 @@ int AXPlatformNodeWin::MSAAState() const {
// Exposing the busy state on the root web area means the NVDA user will end
// up without a virtualBuffer until the page fully loads. So if we have
// content, don't expose the busy state.
- if (GetBoolAttribute(ax::mojom::BoolAttribute::kBusy) &&
- !IsPlatformDocumentWithContent()) {
- msaa_state |= STATE_SYSTEM_BUSY;
+ if (GetBoolAttribute(ax::mojom::BoolAttribute::kBusy)) {
+ if (!IsPlatformDocument() || !GetChildCount())
+ msaa_state |= STATE_SYSTEM_BUSY;
}
if (HasState(ax::mojom::State::kCollapsed))
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 7e75543ab52..9c9dfb7f070 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -3485,19 +3485,6 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldGetCaretOffsetNoCaret) {
EXPECT_EQ(0, offset);
}
-TEST_F(AXPlatformNodeWinTest, IAccessibleTextTextFieldGetCaretOffsetHasCaret) {
- Init(BuildTextFieldWithSelectionRange(1, 2));
-
- ComPtr<IAccessible2> ia2_text_field = ToIAccessible2(GetRootIAccessible());
- ComPtr<IAccessibleText> text_field;
- ia2_text_field.As(&text_field);
- ASSERT_NE(nullptr, text_field.Get());
-
- LONG offset;
- EXPECT_HRESULT_SUCCEEDED(text_field->get_caretOffset(&offset));
- EXPECT_EQ(2, offset);
-}
-
TEST_F(AXPlatformNodeWinTest,
IAccessibleTextContextEditableGetCaretOffsetNoCaret) {
Init(BuildContentEditable());
@@ -4387,7 +4374,7 @@ TEST_F(AXPlatformNodeWinTest, UIAGetControllerForPropertyId) {
AXNodeData root;
root.id = 1;
root.role = ax::mojom::Role::kRootWebArea;
- root.child_ids = {2, 3, 4};
+ root.child_ids = {2, 3, 4, 5, 6};
AXNodeData tab;
tab.id = 2;
@@ -4407,7 +4394,17 @@ TEST_F(AXPlatformNodeWinTest, UIAGetControllerForPropertyId) {
panel2.role = ax::mojom::Role::kTabPanel;
panel2.SetName("panel2");
- Init(root, tab, panel1, panel2);
+ AXNodeData group1;
+ group1.id = 5;
+ group1.role = ax::mojom::Role::kGenericContainer;
+ group1.AddIntAttribute(ax::mojom::IntAttribute::kErrormessageId, 6);
+
+ AXNodeData text1;
+ text1.id = 6;
+ text1.role = ax::mojom::Role::kStaticText;
+ text1.SetName("text1");
+
+ Init(root, tab, panel1, panel2, group1, text1);
TestAXNodeWrapper* root_wrapper =
TestAXNodeWrapper::GetOrCreate(GetTree(), GetRootAsAXNode());
root_wrapper->BuildAllWrappers(GetTree(), GetRootAsAXNode());
@@ -4429,6 +4426,17 @@ TEST_F(AXPlatformNodeWinTest, UIAGetControllerForPropertyId) {
EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
tab_node, UIA_ControllerForPropertyId, UIA_NamePropertyId,
expected_names_2);
+
+ // The aria-errormessage attribute should be exposed through the ControllerFor
+ // UIA property.
+ ComPtr<IRawElementProviderSimple> group1_node =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(
+ GetRootAsAXNode()->children()[3]);
+
+ std::vector<std::wstring> expected_names_3 = {L"text1"};
+ EXPECT_UIA_PROPERTY_ELEMENT_ARRAY_BSTR_EQ(
+ group1_node, UIA_ControllerForPropertyId, UIA_NamePropertyId,
+ expected_names_3);
}
TEST_F(AXPlatformNodeWinTest, UIAGetDescribedByPropertyId) {
@@ -5785,7 +5793,12 @@ TEST_F(AXPlatformNodeWinTest, ComputeUIAControlType) {
child6.role = ax::mojom::Role::kDialog;
root.child_ids.push_back(child6.id);
- Init(root, child1, child2, child3, child4, child5, child6);
+ AXNodeData child7;
+ child7.id = 8;
+ child7.role = ax::mojom::Role::kGraphicsObject;
+ root.child_ids.push_back(child7.id);
+
+ Init(root, child1, child2, child3, child4, child5, child6, child7);
EXPECT_UIA_INT_EQ(
QueryInterfaceFromNodeId<IRawElementProviderSimple>(child1.id),
@@ -5805,6 +5818,9 @@ TEST_F(AXPlatformNodeWinTest, ComputeUIAControlType) {
EXPECT_UIA_INT_EQ(
QueryInterfaceFromNodeId<IRawElementProviderSimple>(child6.id),
UIA_ControlTypePropertyId, int{UIA_WindowControlTypeId});
+ EXPECT_UIA_INT_EQ(
+ QueryInterfaceFromNodeId<IRawElementProviderSimple>(child7.id),
+ UIA_ControlTypePropertyId, int{UIA_GroupControlTypeId});
}
TEST_F(AXPlatformNodeWinTest, UIALandmarkType) {
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
index 2376d6bb2f8..085d6b3d55d 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
@@ -134,7 +134,7 @@ class AXPlatformNodeWinTest : public AXPlatformNodeTest {
std::unique_ptr<AXFragmentRootWin> ax_fragment_root_;
std::unique_ptr<TestFragmentRootDelegate> test_fragment_root_delegate_;
- testing::ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior_;
+ ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior_;
base::test::ScopedFeatureList scoped_feature_list_;
};
diff --git a/chromium/ui/accessibility/platform/ax_platform_tree_manager.h b/chromium/ui/accessibility/platform/ax_platform_tree_manager.h
index 8d2944a433d..642104fa655 100644
--- a/chromium/ui/accessibility/platform/ax_platform_tree_manager.h
+++ b/chromium/ui/accessibility/platform/ax_platform_tree_manager.h
@@ -25,6 +25,11 @@ class AX_EXPORT AXPlatformTreeManager : public AXTreeManager {
// Returns an AXPlatformNode that corresponds to the given |node|.
virtual AXPlatformNode* GetPlatformNodeFromTree(const AXNode& node) const = 0;
+
+ protected:
+ explicit AXPlatformTreeManager(const AXTreeID& tree_id,
+ std::unique_ptr<AXTree> tree)
+ : AXTreeManager(tree_id, std::move(tree)) {}
};
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_unique_id.cc b/chromium/ui/accessibility/platform/ax_unique_id.cc
index cfb433a1280..08353303bf6 100644
--- a/chromium/ui/accessibility/platform/ax_unique_id.cc
+++ b/chromium/ui/accessibility/platform/ax_unique_id.cc
@@ -5,9 +5,9 @@
#include "ui/accessibility/platform/ax_unique_id.h"
#include <memory>
-#include <unordered_set>
#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
@@ -15,7 +15,7 @@ namespace ui {
namespace {
-base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_assigned_ids =
+base::LazyInstance<base::flat_set<int32_t>>::Leaky g_assigned_ids =
LAZY_INSTANCE_INITIALIZER;
} // namespace
diff --git a/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.h b/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.h
index c42f3f4e77d..01fe3a72cbc 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.h
@@ -7,16 +7,13 @@
#include "base/memory/raw_ptr.h"
#include "ui/accessibility/ax_export.h"
-#include "ui/accessibility/platform/inspect/ax_optional.h"
#include "ui/accessibility/platform/inspect/ax_tree_indexer_mac.h"
namespace ui {
+class AXElementWrapper;
class AXPropertyNode;
-// Optional tri-state id object.
-using AXOptionalNSObject = AXOptional<id>;
-
// Invokes a script instruction describing a call unit which represents
// a sequence of calls.
class AX_EXPORT AXCallStatementInvoker final {
@@ -46,7 +43,7 @@ class AX_EXPORT AXCallStatementInvoker final {
// Invokes a property node for a given AXElement.
AXOptionalNSObject InvokeForAXElement(
- const id target,
+ const AXElementWrapper& ax_element,
const AXPropertyNode& property_node) const;
// Invokes a property node for a given AXTextMarkerRange.
@@ -65,7 +62,7 @@ class AX_EXPORT AXCallStatementInvoker final {
// Invokes setAccessibilityFocused method.
AXOptionalNSObject InvokeSetAccessibilityFocused(
- const id target,
+ const AXElementWrapper& ax_element,
const AXPropertyNode& property_node) const;
// Returns a parameterized attribute parameter by a property node representing
diff --git a/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.mm b/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.mm
index 060fad61719..41663d9127c 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.mm
+++ b/chromium/ui/accessibility/platform/inspect/ax_call_statement_invoker_mac.mm
@@ -6,6 +6,8 @@
#include "base/strings/sys_string_conversions.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_mac.h"
#include "ui/accessibility/platform/inspect/ax_property_node.h"
namespace ui {
@@ -152,8 +154,8 @@ AXOptionalNSObject AXCallStatementInvoker::Invoke(
AXOptionalNSObject AXCallStatementInvoker::InvokeFor(
const id target,
const AXPropertyNode& property_node) const {
- if (IsNSAccessibilityElement(target) || IsAXUIElement(target))
- return InvokeForAXElement(target, property_node);
+ if (AXElementWrapper::IsValidElement(target))
+ return InvokeForAXElement({target}, property_node);
if (IsAXTextMarkerRange(target)) {
return InvokeForAXTextMarkerRange(target, property_node);
@@ -170,47 +172,48 @@ AXOptionalNSObject AXCallStatementInvoker::InvokeFor(
}
AXOptionalNSObject AXCallStatementInvoker::InvokeForAXElement(
- const id target,
+ const AXElementWrapper& ax_element,
const AXPropertyNode& property_node) const {
// Actions.
if (property_node.name_or_value == "AXActionNames") {
- return AXOptionalNSObject::NotNullOrNotApplicable(AXActionNamesOf(target));
+ return AXOptionalNSObject::NotNullOrNotApplicable(ax_element.ActionNames());
}
if (property_node.name_or_value == "AXPerformAction") {
AXOptionalNSObject param = ParamFrom(property_node);
if (param.IsNotNull()) {
- PerformAXAction(target, *param);
+ ax_element.PerformAction(*param);
return AXOptionalNSObject::Unsupported();
}
return AXOptionalNSObject::Error();
}
// Get or set attribute value if the attribute is supported.
- for (NSString* attribute : AXAttributeNamesOf(target)) {
+ for (NSString* attribute : ax_element.AttributeNames()) {
if (property_node.IsMatching(base::SysNSStringToUTF8(attribute))) {
// Setter
if (property_node.rvalue) {
AXOptionalNSObject rvalue = Invoke(*property_node.rvalue);
if (rvalue.IsNotNull()) {
- SetAXAttributeValueOf(target, attribute, *rvalue);
+ ax_element.SetAttributeValue(attribute, *rvalue);
return {rvalue};
}
return rvalue;
}
// Getter. Make sure to expose null values in ax scripts.
- id value = AXAttributeValueOf(target, attribute);
- return IsDumpingTree() ? AXOptionalNSObject::NotNullOrNotApplicable(value)
- : AXOptionalNSObject(value);
+ AXOptionalNSObject optional_value =
+ ax_element.GetAttributeValue(attribute);
+ return IsDumpingTree()
+ ? AXOptionalNSObject::NotNullOrNotApplicable(*optional_value)
+ : optional_value;
}
}
// Parameterized attributes.
- for (NSString* attribute : AXParameterizedAttributeNamesOf(target)) {
+ for (NSString* attribute : ax_element.ParameterizedAttributeNames()) {
if (property_node.IsMatching(base::SysNSStringToUTF8(attribute))) {
AXOptionalNSObject param = ParamFrom(property_node);
if (param.IsNotNull()) {
- return AXOptionalNSObject(
- AXParameterizedAttributeValueOf(target, attribute, *param));
+ return ax_element.GetParameterizedAttributeValue(attribute, *param);
}
return param;
}
@@ -240,33 +243,23 @@ AXOptionalNSObject AXCallStatementInvoker::InvokeForAXElement(
SEL selector =
NSSelectorFromString(base::SysUTF8ToNSString(selector_string));
- if (![target respondsToSelector:selector])
+ if (!ax_element.RespondsToSelector(selector))
return AXOptionalNSObject::Error();
- NSInvocation* invocation = [NSInvocation
- invocationWithMethodSignature:
- [[target class] instanceMethodSignatureForSelector:selector]];
- [invocation setSelector:selector];
- [invocation setTarget:target];
- if (optional_arg_selector) {
- // The target is at index 0 and the selector at index 1, so arguments
- // start at index 2.
- [invocation setArgument:&*optional_arg_selector atIndex:2];
- }
- [invocation invoke];
- BOOL return_value;
- [invocation getReturnValue:&return_value];
+ BOOL return_value =
+ optional_arg_selector
+ ? ax_element.Invoke<BOOL, SEL>(selector, *optional_arg_selector)
+ : ax_element.Invoke<BOOL>(selector);
return AXOptionalNSObject([NSNumber numberWithBool:return_value]);
}
if (property_node.name_or_value == "setAccessibilityFocused")
- return InvokeSetAccessibilityFocused(target, property_node);
+ return InvokeSetAccessibilityFocused(ax_element, property_node);
// accessibilityAttributeValue
if (property_node.name_or_value == "accessibilityAttributeValue") {
if (property_node.arguments.size() == 1) {
- return AXOptionalNSObject(AXAttributeValueOf(
- target,
+ return AXOptionalNSObject(ax_element.GetAttributeValue(
base::SysUTF8ToNSString(property_node.arguments[0].name_or_value)));
}
// Parameterized accessibilityAttributeValue.
@@ -277,8 +270,8 @@ AXOptionalNSObject AXCallStatementInvoker::InvokeForAXElement(
if (!param.HasValue())
return AXOptionalNSObject::Error();
- return AXOptionalNSObject(AXParameterizedAttributeValueOf(
- target, base::SysUTF8ToNSString(attribute), *param));
+ return AXOptionalNSObject(ax_element.GetParameterizedAttributeValue(
+ base::SysUTF8ToNSString(attribute), *param));
}
return AXOptionalNSObject::Error();
}
@@ -286,14 +279,15 @@ AXOptionalNSObject AXCallStatementInvoker::InvokeForAXElement(
if (base::StartsWith(property_node.name_or_value, "accessibility")) {
if (property_node.arguments.size() == 1) {
absl::optional<id> optional_id =
- PerformAXSelector(target, property_node.name_or_value,
- property_node.arguments[0].name_or_value);
+ ax_element.PerformSelector(property_node.name_or_value,
+ property_node.arguments[0].name_or_value);
if (optional_id) {
return AXOptionalNSObject(*optional_id);
}
}
if (property_node.arguments.empty()) {
- auto optional_id = PerformAXSelector(target, property_node.name_or_value);
+ auto optional_id =
+ ax_element.PerformSelector(property_node.name_or_value);
if (optional_id) {
return AXOptionalNSObject(*optional_id);
}
@@ -399,7 +393,7 @@ AXOptionalNSObject AXCallStatementInvoker::InvokeForDictionary(
}
AXOptionalNSObject AXCallStatementInvoker::InvokeSetAccessibilityFocused(
- const id target,
+ const AXElementWrapper& ax_element,
const AXPropertyNode& property_node) const {
std::string selector_string = property_node.name_or_value + ":";
if (property_node.arguments.size() != 1) {
@@ -410,24 +404,13 @@ AXOptionalNSObject AXCallStatementInvoker::InvokeSetAccessibilityFocused(
}
SEL selector = NSSelectorFromString(base::SysUTF8ToNSString(selector_string));
- if (![target respondsToSelector:selector]) {
+ if (!ax_element.RespondsToSelector(selector)) {
LOG(ERROR) << "Target doesn't answer to " << selector_string << " selector";
return AXOptionalNSObject::Error();
}
- NSInvocation* invocation = [NSInvocation
- invocationWithMethodSignature:
- [[target class] instanceMethodSignatureForSelector:selector]];
-
- [invocation setSelector:selector];
- [invocation setTarget:target];
-
- // The target is at index 0 and the selector at index 1, so arguments
- // start at index 2.
BOOL val = property_node.arguments[0].name_or_value == "FALSE" ? FALSE : TRUE;
- [invocation setArgument:&val atIndex:2];
- [invocation invoke];
-
+ ax_element.Invoke<void, BOOL>(selector, val);
return AXOptionalNSObject(nil);
}
diff --git a/chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.h b/chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.h
new file mode 100644
index 00000000000..e02150cb618
--- /dev/null
+++ b/chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.h
@@ -0,0 +1,158 @@
+// Copyright 2022 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.
+
+#ifndef UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_ELEMENT_WRAPPER_MAC_H_
+#define UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_ELEMENT_WRAPPER_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/callback_forward.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/platform/inspect/ax_inspect.h"
+#include "ui/accessibility/platform/inspect/ax_optional.h"
+
+namespace ui {
+
+// Optional tri-state id object.
+using AXOptionalNSObject = AXOptional<id>;
+
+// A wrapper around AXUIElement or NSAccessibilityElement object.
+class AX_EXPORT AXElementWrapper final {
+ public:
+ // Returns true if the object is either NSAccessibilityElement or
+ // AXUIElement.
+ static bool IsValidElement(const id node);
+
+ // Return true if the object is internal BrowserAccessibilityCocoa.
+ static bool IsNSAccessibilityElement(const id node);
+
+ // Returns true if the object is AXUIElement.
+ static bool IsAXUIElement(const id node);
+
+ // Returns the children of an accessible object, either AXUIElement or
+ // BrowserAccessibilityCocoa.
+ static NSArray* ChildrenOf(const id node);
+
+ // Returns the DOM id of a given node (either AXUIElement or
+ // BrowserAccessibilityCocoa).
+ static std::string DOMIdOf(const id node);
+
+ AXElementWrapper(const id node) : node_(node){};
+
+ // Returns true if the object is either an NSAccessibilityElement or
+ // AXUIElement.
+ bool IsValidElement() const;
+
+ // Return true if the object is an internal BrowserAccessibilityCocoa.
+ bool IsNSAccessibilityElement() const;
+
+ // Returns true if the object is an AXUIElement.
+ bool IsAXUIElement() const;
+
+ // Returns the wrapped object.
+ id AsId() const;
+
+ // Returns the DOM id of the object.
+ std::string DOMId() const;
+
+ // Returns the children of the object.
+ NSArray* Children() const;
+
+ // Returns the AXSize and AXPosition attributes for the object.
+ NSSize Size() const;
+ NSPoint Position() const;
+
+ // Returns the (parameterized) attributes of the object.
+ NSArray* AttributeNames() const;
+ NSArray* ParameterizedAttributeNames() const;
+
+ // Returns (parameterized) attribute value on the object.
+ AXOptionalNSObject GetAttributeValue(NSString* attribute) const;
+ AXOptionalNSObject GetParameterizedAttributeValue(NSString* attribute,
+ id parameter) const;
+
+ // Performs the given selector on the object and returns the result. If
+ // the object does not conform to the NSAccessibility protocol or the selector
+ // is not found, then returns nullopt.
+ absl::optional<id> PerformSelector(const std::string& selector) const;
+
+ // Performs the given selector on the object with exactly one string
+ // argument and returns the result. If the object does not conform to the
+ // NSAccessibility protocol or the selector is not found, then returns
+ // nullopt.
+ absl::optional<id> PerformSelector(const std::string& selector_string,
+ const std::string& argument_string) const;
+
+ // Sets attribute value on the object.
+ void SetAttributeValue(NSString* attribute, id value) const;
+
+ // Returns the list of actions supported on the object.
+ NSArray* ActionNames() const;
+
+ // Performs the given action on the object.
+ void PerformAction(NSString* action) const;
+
+ // Returns true if the object responds to the given selector.
+ bool RespondsToSelector(SEL selector) const {
+ return [node_ respondsToSelector:selector];
+ }
+
+ // Invokes a method of the given signature.
+ template <typename ReturnType, typename... Args>
+ typename std::enable_if<!std::is_same<ReturnType, void>::value,
+ ReturnType>::type
+ Invoke(SEL selector, Args... args) const {
+ NSInvocation* invocation = InvokeInternal(selector, args...);
+ ReturnType return_value;
+ [invocation getReturnValue:&return_value];
+ return return_value;
+ }
+
+ template <typename ReturnType, typename... Args>
+ typename std::enable_if<std::is_same<ReturnType, void>::value, void>::type
+ Invoke(SEL selector, Args... args) const {
+ InvokeInternal(selector, args...);
+ }
+
+ private:
+ template <typename... Args>
+ NSInvocation* InvokeInternal(SEL selector, Args... args) const {
+ NSInvocation* invocation = [NSInvocation
+ invocationWithMethodSignature:
+ [[node_ class] instanceMethodSignatureForSelector:selector]];
+ [invocation setSelector:selector];
+ [invocation setTarget:node_];
+ SetInvocationArguments<Args...>(invocation, 2, args...);
+ [invocation invoke];
+ return invocation;
+ }
+
+ template <typename Arg, typename... Args>
+ void SetInvocationArguments(NSInvocation* invocation,
+ int argument_index,
+ Arg& arg,
+ Args&... args) const {
+ [invocation setArgument:&arg atIndex:argument_index];
+ SetInvocationArguments<Args...>(invocation, argument_index + 1, args...);
+ }
+
+ template <typename... Args>
+ void SetInvocationArguments(NSInvocation*, int) const {}
+
+ // Generates an error message from the given error.
+ std::string AXErrorMessage(AXError, const std::string& message) const;
+
+ // Returns true on success, otherwise returns false and logs error.
+ bool AXSuccess(AXError result, const std::string& message) const;
+
+ // Converts the given value and the error object into AXOptional object.
+ AXOptionalNSObject ToOptional(id, AXError, const std::string& message) const;
+
+ const id node_;
+};
+
+} // namespace ui
+
+#endif // UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_ELEMENT_WRAPPER_MAC_H_
diff --git a/chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.mm b/chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.mm
new file mode 100644
index 00000000000..a8bb7e90298
--- /dev/null
+++ b/chromium/ui/accessibility/platform/inspect/ax_element_wrapper_mac.mm
@@ -0,0 +1,362 @@
+// Copyright 2022 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/platform/inspect/ax_element_wrapper_mac.h"
+
+#include <ostream>
+
+#include "base/callback.h"
+#include "base/containers/fixed_flat_set.h"
+#include "base/debug/stack_trace.h"
+#include "base/logging.h"
+#include "base/strings/pattern.h"
+#include "base/strings/sys_string_conversions.h"
+#include "ui/accessibility/platform/ax_private_attributes_mac.h"
+
+// error: 'accessibilityAttributeNames' is deprecated: first deprecated in
+// macOS 10.10 - Use the NSAccessibility protocol methods instead (see
+// NSAccessibilityProtocols.h
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
+namespace ui {
+
+using base::SysNSStringToUTF8;
+
+constexpr char kUnsupportedObject[] =
+ "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+
+// static
+bool AXElementWrapper::IsValidElement(const id node) {
+ return AXElementWrapper(node).IsValidElement();
+}
+
+bool AXElementWrapper::IsNSAccessibilityElement(const id node) {
+ return AXElementWrapper(node).IsNSAccessibilityElement();
+}
+
+bool AXElementWrapper::IsAXUIElement(const id node) {
+ return AXElementWrapper(node).IsAXUIElement();
+}
+
+NSArray* AXElementWrapper::ChildrenOf(const id node) {
+ return AXElementWrapper(node).Children();
+}
+
+// Returns DOM id of a given node (either AXUIElement or
+// BrowserAccessibilityCocoa).
+std::string AXElementWrapper::DOMIdOf(const id node) {
+ return AXElementWrapper(node).DOMId();
+}
+
+bool AXElementWrapper::IsValidElement() const {
+ return IsNSAccessibilityElement() || IsAXUIElement();
+}
+
+bool AXElementWrapper::IsNSAccessibilityElement() const {
+ return [node_ isKindOfClass:[NSAccessibilityElement class]];
+}
+
+bool AXElementWrapper::IsAXUIElement() const {
+ return CFGetTypeID(node_) == AXUIElementGetTypeID();
+}
+
+id AXElementWrapper::AsId() const {
+ return node_;
+}
+
+std::string AXElementWrapper::DOMId() const {
+ const id domid_value =
+ *GetAttributeValue(base::SysUTF8ToNSString("AXDOMIdentifier"));
+ return base::SysNSStringToUTF8(static_cast<NSString*>(domid_value));
+}
+
+NSArray* AXElementWrapper::Children() const {
+ if (IsNSAccessibilityElement())
+ return [node_ children];
+
+ if (IsAXUIElement()) {
+ CFTypeRef children_ref;
+ if ((AXUIElementCopyAttributeValue(static_cast<AXUIElementRef>(node_),
+ kAXChildrenAttribute, &children_ref)) ==
+ kAXErrorSuccess)
+ return static_cast<NSArray*>(children_ref);
+ return nil;
+ }
+
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return nil;
+}
+
+NSSize AXElementWrapper::Size() const {
+ if (IsNSAccessibilityElement()) {
+ return [node_ accessibilityFrame].size;
+ }
+
+ if (!IsAXUIElement()) {
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return NSMakeSize(0, 0);
+ }
+
+ id value = *GetAttributeValue(NSAccessibilitySizeAttribute);
+ if (value && CFGetTypeID(value) == AXValueGetTypeID()) {
+ AXValueType type = AXValueGetType(static_cast<AXValueRef>(value));
+ if (type == kAXValueCGSizeType) {
+ NSSize size;
+ if (AXValueGetValue(static_cast<AXValueRef>(value), type, &size)) {
+ return size;
+ }
+ }
+ }
+ return NSMakeSize(0, 0);
+}
+
+NSPoint AXElementWrapper::Position() const {
+ if (IsNSAccessibilityElement()) {
+ return [node_ accessibilityFrame].origin;
+ }
+
+ if (!IsAXUIElement()) {
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return NSMakePoint(0, 0);
+ }
+
+ id value = *GetAttributeValue(NSAccessibilityPositionAttribute);
+ if (value && CFGetTypeID(value) == AXValueGetTypeID()) {
+ AXValueType type = AXValueGetType(static_cast<AXValueRef>(value));
+ if (type == kAXValueCGPointType) {
+ NSPoint point;
+ if (AXValueGetValue(static_cast<AXValueRef>(value), type, &point)) {
+ return point;
+ }
+ }
+ }
+ return NSMakePoint(0, 0);
+}
+
+NSArray* AXElementWrapper::AttributeNames() const {
+ if (IsNSAccessibilityElement())
+ return [node_ accessibilityAttributeNames];
+
+ if (IsAXUIElement()) {
+ CFArrayRef attributes_ref;
+ AXError result = AXUIElementCopyAttributeNames(
+ static_cast<AXUIElementRef>(node_), &attributes_ref);
+ if (AXSuccess(result, "AXAttributeNamesOf"))
+ return static_cast<NSArray*>(attributes_ref);
+ return nil;
+ }
+
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return nil;
+}
+
+NSArray* AXElementWrapper::ParameterizedAttributeNames() const {
+ if (IsNSAccessibilityElement())
+ return [node_ accessibilityParameterizedAttributeNames];
+
+ if (IsAXUIElement()) {
+ CFArrayRef attributes_ref;
+ AXError result = AXUIElementCopyParameterizedAttributeNames(
+ static_cast<AXUIElementRef>(node_), &attributes_ref);
+ if (AXSuccess(result, "AXParameterizedAttributeNamesOf"))
+ return static_cast<NSArray*>(attributes_ref);
+ return nil;
+ }
+
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return nil;
+}
+
+AXOptionalNSObject AXElementWrapper::GetAttributeValue(
+ NSString* attribute) const {
+ if (IsNSAccessibilityElement())
+ return AXOptionalNSObject([node_ accessibilityAttributeValue:attribute]);
+
+ if (IsAXUIElement()) {
+ CFTypeRef value_ref;
+ AXError result = AXUIElementCopyAttributeValue(
+ static_cast<AXUIElementRef>(node_), static_cast<CFStringRef>(attribute),
+ &value_ref);
+ return ToOptional(
+ static_cast<id>(value_ref), result,
+ "AXGetAttributeValue(" + base::SysNSStringToUTF8(attribute) + ")");
+ }
+
+ return AXOptionalNSObject::Error(kUnsupportedObject);
+}
+
+AXOptionalNSObject AXElementWrapper::GetParameterizedAttributeValue(
+ NSString* attribute,
+ id parameter) const {
+ if (IsNSAccessibilityElement())
+ return AXOptionalNSObject([node_ accessibilityAttributeValue:attribute
+ forParameter:parameter]);
+
+ if (IsAXUIElement()) {
+ // Convert NSValue parameter to CFTypeRef if needed.
+ CFTypeRef parameter_ref = static_cast<CFTypeRef>(parameter);
+ if ([parameter isKindOfClass:[NSValue class]] &&
+ !strcmp([static_cast<NSValue*>(parameter) objCType],
+ @encode(NSRange))) {
+ NSRange range = [static_cast<NSValue*>(parameter) rangeValue];
+ parameter_ref = AXValueCreate(kAXValueTypeCFRange, &range);
+ }
+
+ // Get value.
+ CFTypeRef value_ref;
+ AXError result = AXUIElementCopyParameterizedAttributeValue(
+ static_cast<AXUIElementRef>(node_), static_cast<CFStringRef>(attribute),
+ parameter_ref, &value_ref);
+
+ return ToOptional(static_cast<id>(value_ref), result,
+ "GetParameterizedAttributeValue(" +
+ base::SysNSStringToUTF8(attribute) + ")");
+ }
+
+ return AXOptionalNSObject::Error(kUnsupportedObject);
+}
+
+absl::optional<id> AXElementWrapper::PerformSelector(
+ const std::string& selector_string) const {
+ if (![node_ conformsToProtocol:@protocol(NSAccessibility)])
+ return absl::nullopt;
+
+ NSString* selector_nsstring = base::SysUTF8ToNSString(selector_string);
+ SEL selector = NSSelectorFromString(selector_nsstring);
+
+ if ([node_ respondsToSelector:selector])
+ return [node_ valueForKey:selector_nsstring];
+ return absl::nullopt;
+}
+
+absl::optional<id> AXElementWrapper::PerformSelector(
+ const std::string& selector_string,
+ const std::string& argument_string) const {
+ if (![node_ conformsToProtocol:@protocol(NSAccessibility)])
+ return absl::nullopt;
+
+ SEL selector =
+ NSSelectorFromString(base::SysUTF8ToNSString(selector_string + ":"));
+ NSString* argument = base::SysUTF8ToNSString(argument_string);
+
+ if ([node_ respondsToSelector:selector])
+ return [node_ performSelector:selector withObject:argument];
+ return absl::nullopt;
+}
+
+void AXElementWrapper::SetAttributeValue(NSString* attribute, id value) const {
+ if (IsNSAccessibilityElement()) {
+ [node_ accessibilitySetValue:value forAttribute:attribute];
+ return;
+ }
+
+ if (IsAXUIElement()) {
+ AXUIElementSetAttributeValue(static_cast<AXUIElementRef>(node_),
+ static_cast<CFStringRef>(attribute),
+ static_cast<CFTypeRef>(value));
+ return;
+ }
+
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+}
+
+NSArray* AXElementWrapper::ActionNames() const {
+ if (IsNSAccessibilityElement())
+ return [node_ accessibilityActionNames];
+
+ if (IsAXUIElement()) {
+ CFArrayRef attributes_ref;
+ if ((AXUIElementCopyActionNames(static_cast<AXUIElementRef>(node_),
+ &attributes_ref)) == kAXErrorSuccess)
+ return static_cast<NSArray*>(attributes_ref);
+ return nil;
+ }
+
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return nil;
+}
+
+void AXElementWrapper::PerformAction(NSString* action) const {
+ if (IsNSAccessibilityElement()) {
+ [node_ accessibilityPerformAction:action];
+ return;
+ }
+
+ if (IsAXUIElement()) {
+ AXUIElementPerformAction(static_cast<AXUIElementRef>(node_),
+ static_cast<CFStringRef>(action));
+ return;
+ }
+
+ NOTREACHED()
+ << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+}
+
+std::string AXElementWrapper::AXErrorMessage(AXError result,
+ const std::string& message) const {
+ if (result == kAXErrorSuccess) {
+ return {};
+ }
+
+ std::string error;
+ switch (result) {
+ case kAXErrorAttributeUnsupported:
+ error = "attribute unsupported";
+ break;
+ case kAXErrorParameterizedAttributeUnsupported:
+ error = "parameterized attribute unsupported";
+ break;
+ case kAXErrorNoValue:
+ error = "no value";
+ break;
+ case kAXErrorIllegalArgument:
+ error = "illegal argument";
+ break;
+ case kAXErrorInvalidUIElement:
+ error = "invalid UIElement";
+ break;
+ case kAXErrorCannotComplete:
+ error = "cannot complete";
+ break;
+ case kAXErrorNotImplemented:
+ error = "not implemented";
+ break;
+ default:
+ error = "unknown error";
+ break;
+ }
+ return {message + ": " + error};
+}
+
+bool AXElementWrapper::AXSuccess(AXError result,
+ const std::string& message) const {
+ std::string message_text = AXErrorMessage(result, message);
+ if (message_text.empty())
+ return true;
+
+ LOG(WARNING) << message_text;
+ return false;
+}
+
+AXOptionalNSObject AXElementWrapper::ToOptional(
+ id value,
+ AXError result,
+ const std::string& message) const {
+ if (result == kAXErrorSuccess)
+ return AXOptionalNSObject(value);
+
+ return AXOptionalNSObject::Error(AXErrorMessage(result, message));
+}
+
+} // namespace ui
+
+#pragma clang diagnostic pop
diff --git a/chromium/ui/accessibility/platform/inspect/ax_inspect_scenario.h b/chromium/ui/accessibility/platform/inspect/ax_inspect_scenario.h
index 3f870ae4872..5be2b665b22 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_inspect_scenario.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_inspect_scenario.h
@@ -10,6 +10,7 @@
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/platform/inspect/ax_inspect.h"
namespace base {
class FilePath;
@@ -17,8 +18,6 @@ class FilePath;
namespace ui {
-struct AXPropertyFilter;
-struct AXNodeFilter;
class AXScriptInstruction;
// Describes the test execution flow, which is parsed from a sequence
diff --git a/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.h b/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.h
index e223ecc7e80..3d4fcae136d 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.h
@@ -16,97 +16,22 @@ using ui::AXTreeSelector;
namespace ui {
-//
// Returns true if the given accessibility attribute is valid, and could have
// been exposed on certain accessibility objects.
AX_EXPORT bool IsValidAXAttribute(const std::string& attribute);
-//
-// Return true if the given object is internal BrowserAccessibilityCocoa.
-AX_EXPORT bool IsNSAccessibilityElement(const id node);
-
-//
-// Returns true if the given object is AXUIElement.
-AX_EXPORT bool IsAXUIElement(const id node);
-
-//
-// Returns children of an accessible object, either AXUIElement or
-// BrowserAccessibilityCocoa.
-AX_EXPORT NSArray* AXChildrenOf(const id node);
-
-//
-// Returns AXSize and AXPosition attributes for an accessible object.
-AX_EXPORT NSSize AXSizeOf(const id node);
-AX_EXPORT NSPoint AXPositionOf(const id node);
-
-//
-// Returns (parameterized) attributes of an accessible object, (either
-// AXUIElement or BrowserAccessibilityCocoa).
-AX_EXPORT NSArray* AXAttributeNamesOf(const id node);
-AX_EXPORT NSArray* AXParameterizedAttributeNamesOf(const id node);
-
-//
-// Returns (parameterized) attribute value on a given node (either AXUIElement
-// or BrowserAccessibilityCocoa).
-AX_EXPORT id AXAttributeValueOf(const id node, NSString* attribute);
-AX_EXPORT id AXParameterizedAttributeValueOf(const id node,
- NSString* attribute,
- id parameter);
-
-//
-// Performs the given selector on the given node and returns the result. If
-// the node does not conform to the NSAccessibility protocol or the selector is
-// not found, then returns nullopt.
-AX_EXPORT absl::optional<id> PerformAXSelector(const id node,
- const std::string& selector);
-
-//
-// Performs the given selector on the given node with exactly one string
-// argument and returns the result. If the node does not conform to the
-// NSAccessibility protocol or the selector is not found, then returns nullopt.
-AX_EXPORT absl::optional<id> PerformAXSelector(
- const id node,
- const std::string& selector_string,
- const std::string& argument_string);
-
-//
-// Sets attribute value on a given node (either AXUIElement or
-// BrowserAccessibilityCocoa).
-AX_EXPORT void SetAXAttributeValueOf(const id node,
- NSString* attribute,
- id value);
-
-// Returns a list of actions supported on a given accessible node (either
-// AXUIElement or BrowserAccessibilityCocoa).
-AX_EXPORT NSArray* AXActionNamesOf(const id node);
-
-// Performs action on a given accessible node (either AXUIElement or
-// BrowserAccessibilityCocoa).
-AX_EXPORT void PerformAXAction(const id node, NSString* action);
-
-//
-// Returns DOM id of a given node (either AXUIElement or
-// BrowserAccessibilityCocoa).
-AX_EXPORT std::string GetDOMId(const id node);
-
-//
// Return AXElement in a tree by a given criteria.
using AXFindCriteria = base::RepeatingCallback<bool(const AXUIElementRef)>;
AX_EXPORT AXUIElementRef FindAXUIElement(const AXUIElementRef node,
const AXFindCriteria& criteria);
-//
// Returns AXUIElement and its application process id by a given tree selector.
AX_EXPORT std::pair<AXUIElementRef, int> FindAXUIElement(const AXTreeSelector&);
-//
// Returns AXUIElement for a window having title matching the given pattern.
AX_EXPORT AXUIElementRef FindAXWindowChild(AXUIElementRef parent,
const std::string& pattern);
-// Returns true on success, otherwise returns false and logs error.
-AX_EXPORT bool AXSuccess(AXError, const std::string& message);
-
} // namespace ui
#endif // UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_INSPECT_UTILS_MAC_H_
diff --git a/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm b/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
index 2a5a5b0f0a4..456192fdf02 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
+++ b/chromium/ui/accessibility/platform/inspect/ax_inspect_utils_mac.mm
@@ -8,10 +8,12 @@
#include "base/callback.h"
#include "base/containers/fixed_flat_set.h"
+#include "base/debug/stack_trace.h"
#include "base/logging.h"
#include "base/strings/pattern.h"
#include "base/strings/sys_string_conversions.h"
#include "ui/accessibility/platform/ax_private_attributes_mac.h"
+#include "ui/accessibility/platform/inspect/ax_element_wrapper_mac.h"
// error: 'accessibilityAttributeNames' is deprecated: first deprecated in
// macOS 10.10 - Use the NSAccessibility protocol methods instead (see
@@ -92,251 +94,12 @@ bool IsValidAXAttribute(const std::string& attribute) {
return kValidAttributes.contains(base::SysUTF8ToNSString(attribute));
}
-bool IsNSAccessibilityElement(const id node) {
- return [node isKindOfClass:[NSAccessibilityElement class]];
-}
-
-bool IsAXUIElement(const id node) {
- return CFGetTypeID(node) == AXUIElementGetTypeID();
-}
-
NSArray* AXChildrenOf(const id node) {
- if (IsNSAccessibilityElement(node))
- return [node children];
-
- if (IsAXUIElement(node)) {
- CFTypeRef children_ref;
- if ((AXUIElementCopyAttributeValue(static_cast<AXUIElementRef>(node),
- kAXChildrenAttribute, &children_ref)) ==
- kAXErrorSuccess)
- return static_cast<NSArray*>(children_ref);
- return nil;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return nil;
-}
-
-NSSize AXSizeOf(const id node) {
- if (IsNSAccessibilityElement(node)) {
- return [node accessibilityFrame].size;
- }
-
- if (!IsAXUIElement(node)) {
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return NSMakeSize(0, 0);
- }
-
- id value = AXAttributeValueOf(node, NSAccessibilitySizeAttribute);
- if (value && CFGetTypeID(value) == AXValueGetTypeID()) {
- AXValueType type = AXValueGetType(static_cast<AXValueRef>(value));
- if (type == kAXValueCGSizeType) {
- NSSize size;
- if (AXValueGetValue(static_cast<AXValueRef>(value), type, &size)) {
- return size;
- }
- }
- }
- return NSMakeSize(0, 0);
-}
-
-NSPoint AXPositionOf(const id node) {
- if (IsNSAccessibilityElement(node)) {
- return [node accessibilityFrame].origin;
- }
-
- if (!IsAXUIElement(node)) {
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return NSMakePoint(0, 0);
- }
-
- id value = AXAttributeValueOf(node, NSAccessibilityPositionAttribute);
- if (value && CFGetTypeID(value) == AXValueGetTypeID()) {
- AXValueType type = AXValueGetType(static_cast<AXValueRef>(value));
- if (type == kAXValueCGPointType) {
- NSPoint point;
- if (AXValueGetValue(static_cast<AXValueRef>(value), type, &point)) {
- return point;
- }
- }
- }
- return NSMakePoint(0, 0);
-}
-
-NSArray* AXAttributeNamesOf(const id node) {
- if (IsNSAccessibilityElement(node))
- return [node accessibilityAttributeNames];
-
- if (IsAXUIElement(node)) {
- CFArrayRef attributes_ref;
- AXError result = AXUIElementCopyAttributeNames(
- static_cast<AXUIElementRef>(node), &attributes_ref);
- if (AXSuccess(result, "AXAttributeNamesOf"))
- return static_cast<NSArray*>(attributes_ref);
- return nil;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return nil;
-}
-
-NSArray* AXParameterizedAttributeNamesOf(const id node) {
- if (IsNSAccessibilityElement(node))
- return [node accessibilityParameterizedAttributeNames];
-
- if (IsAXUIElement(node)) {
- CFArrayRef attributes_ref;
- AXError result = AXUIElementCopyParameterizedAttributeNames(
- static_cast<AXUIElementRef>(node), &attributes_ref);
- if (AXSuccess(result, "AXParameterizedAttributeNamesOf"))
- return static_cast<NSArray*>(attributes_ref);
- return nil;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return nil;
-}
-
-id AXAttributeValueOf(const id node, NSString* attribute) {
- if (IsNSAccessibilityElement(node))
- return [node accessibilityAttributeValue:attribute];
-
- if (IsAXUIElement(node)) {
- CFTypeRef value_ref;
- AXError result = AXUIElementCopyAttributeValue(
- static_cast<AXUIElementRef>(node), static_cast<CFStringRef>(attribute),
- &value_ref);
- if (AXSuccess(result, "AXAttributeValueOf(" +
- base::SysNSStringToUTF8(attribute) + ")"))
- return static_cast<id>(value_ref);
- return nil;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return nil;
-}
-
-id AXParameterizedAttributeValueOf(const id node,
- NSString* attribute,
- id parameter) {
- if (IsNSAccessibilityElement(node))
- return [node accessibilityAttributeValue:attribute forParameter:parameter];
-
- if (IsAXUIElement(node)) {
- // Convert NSValue parameter to CFTypeRef if needed.
- CFTypeRef parameter_ref = static_cast<CFTypeRef>(parameter);
- if ([parameter isKindOfClass:[NSValue class]] &&
- !strcmp([static_cast<NSValue*>(parameter) objCType],
- @encode(NSRange))) {
- NSRange range = [static_cast<NSValue*>(parameter) rangeValue];
- parameter_ref = AXValueCreate(kAXValueTypeCFRange, &range);
- }
-
- // Get value.
- CFTypeRef value_ref;
- AXError result = AXUIElementCopyParameterizedAttributeValue(
- static_cast<AXUIElementRef>(node), static_cast<CFStringRef>(attribute),
- parameter_ref, &value_ref);
- if (AXSuccess(result, "AXParameterizedAttributeValueOf(" +
- base::SysNSStringToUTF8(attribute) + ")"))
- return static_cast<id>(value_ref);
-
- return nil;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return nil;
-}
-
-absl::optional<id> PerformAXSelector(const id node,
- const std::string& selector_string) {
- if (![node conformsToProtocol:@protocol(NSAccessibility)])
- return absl::nullopt;
-
- NSString* selector_nsstring = base::SysUTF8ToNSString(selector_string);
- SEL selector = NSSelectorFromString(selector_nsstring);
-
- if ([node respondsToSelector:selector])
- return [node valueForKey:selector_nsstring];
- return absl::nullopt;
-}
-
-absl::optional<id> PerformAXSelector(const id node,
- const std::string& selector_string,
- const std::string& argument_string) {
- if (![node conformsToProtocol:@protocol(NSAccessibility)])
- return absl::nullopt;
-
- SEL selector =
- NSSelectorFromString(base::SysUTF8ToNSString(selector_string + ":"));
- NSString* argument = base::SysUTF8ToNSString(argument_string);
-
- if ([node respondsToSelector:selector])
- return [node performSelector:selector withObject:argument];
- return absl::nullopt;
-}
-
-void SetAXAttributeValueOf(const id node, NSString* attribute, id value) {
- if (IsNSAccessibilityElement(node)) {
- [node accessibilitySetValue:value forAttribute:attribute];
- return;
- }
-
- if (IsAXUIElement(node)) {
- AXUIElementSetAttributeValue(static_cast<AXUIElementRef>(node),
- static_cast<CFStringRef>(attribute),
- static_cast<CFTypeRef>(value));
- return;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
-}
-
-NSArray* AXActionNamesOf(const id node) {
- if (IsNSAccessibilityElement(node))
- return [node accessibilityActionNames];
-
- if (IsAXUIElement(node)) {
- CFArrayRef attributes_ref;
- if ((AXUIElementCopyActionNames(static_cast<AXUIElementRef>(node),
- &attributes_ref)) == kAXErrorSuccess)
- return static_cast<NSArray*>(attributes_ref);
- return nil;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
- return nil;
-}
-
-void PerformAXAction(const id node, NSString* action) {
- if (IsNSAccessibilityElement(node)) {
- [node accessibilityPerformAction:action];
- return;
- }
-
- if (IsAXUIElement(node)) {
- AXUIElementPerformAction(static_cast<AXUIElementRef>(node),
- static_cast<CFStringRef>(action));
- return;
- }
-
- NOTREACHED()
- << "Only AXUIElementRef and BrowserAccessibilityCocoa are supported.";
+ return AXElementWrapper(node).Children();
}
std::string GetDOMId(const id node) {
- const id domid_value =
- AXAttributeValueOf(node, base::SysUTF8ToNSString("AXDOMIdentifier"));
- return base::SysNSStringToUTF8(static_cast<NSString*>(domid_value));
+ return AXElementWrapper(node).DOMId();
}
AXUIElementRef FindAXUIElement(const AXUIElementRef node,
@@ -408,8 +171,9 @@ std::pair<AXUIElementRef, int> FindAXUIElement(const AXTreeSelector& selector) {
node, base::BindRepeating([](const AXUIElementRef node) {
// Only active tab in exposed in browsers, thus find first
// AXWebArea role.
- NSString* role = AXAttributeValueOf(static_cast<id>(node),
- NSAccessibilityRoleAttribute);
+ AXElementWrapper ax_node(static_cast<id>(node));
+ NSString* role =
+ *ax_node.GetAttributeValue(NSAccessibilityRoleAttribute);
return SysNSStringToUTF8(role) == "AXWebArea";
}));
}
@@ -428,54 +192,20 @@ AXUIElementRef FindAXWindowChild(AXUIElementRef parent,
return nil;
id window = [children objectAtIndex:0];
- NSString* role = AXAttributeValueOf(window, NSAccessibilityRoleAttribute);
+
+ AXElementWrapper ax_window(window);
+ NSString* role = *ax_window.GetAttributeValue(NSAccessibilityRoleAttribute);
if (SysNSStringToUTF8(role) != "AXWindow")
return nil;
NSString* window_title =
- AXAttributeValueOf(window, NSAccessibilityTitleAttribute);
+ *ax_window.GetAttributeValue(NSAccessibilityTitleAttribute);
if (base::MatchPattern(SysNSStringToUTF8(window_title), pattern))
return static_cast<AXUIElementRef>(window);
return nil;
}
-AX_EXPORT bool AXSuccess(AXError result, const std::string& message) {
- if (result == kAXErrorSuccess) {
- return true;
- }
-
- std::string error;
- switch (result) {
- case kAXErrorAttributeUnsupported:
- error = "attribute unsupported";
- break;
- case kAXErrorParameterizedAttributeUnsupported:
- error = "parameterized attribute unsupported";
- break;
- case kAXErrorNoValue:
- error = "no value";
- break;
- case kAXErrorIllegalArgument:
- error = "illegal argument";
- break;
- case kAXErrorInvalidUIElement:
- error = "invalid UIElement";
- break;
- case kAXErrorCannotComplete:
- error = "cannot complete";
- break;
- case kAXErrorNotImplemented:
- error = "not implemented";
- break;
- default:
- error = "unknown error";
- break;
- }
- LOG(WARNING) << message << ": " << error;
- return false;
-}
-
} // namespace ui
#pragma clang diagnostic pop
diff --git a/chromium/ui/accessibility/platform/inspect/ax_optional.h b/chromium/ui/accessibility/platform/inspect/ax_optional.h
index da0c195c607..aeb622c9efa 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_optional.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_optional.h
@@ -18,7 +18,12 @@ template <typename ValueType>
class AX_EXPORT AXOptional final {
public:
static constexpr AXOptional Unsupported() { return AXOptional(kUnsupported); }
- static constexpr AXOptional Error() { return AXOptional(kError); }
+ static constexpr AXOptional Error(const char* error_text = nullptr) {
+ return error_text ? AXOptional(kError, error_text) : AXOptional(kError);
+ }
+ static constexpr AXOptional Error(const std::string& error_text) {
+ return AXOptional(kError, error_text);
+ }
static constexpr AXOptional NotApplicable() {
return AXOptional(kNotApplicable);
}
@@ -31,11 +36,11 @@ class AX_EXPORT AXOptional final {
}
explicit constexpr AXOptional(ValueType value_)
- : value_(value_), flag_(kValue) {}
+ : value_(value_), state_(kValue) {}
- bool constexpr IsUnsupported() const { return flag_ == kUnsupported; }
- bool constexpr IsNotApplicable() const { return flag_ == kNotApplicable; }
- bool constexpr IsError() const { return flag_ == kError; }
+ bool constexpr IsUnsupported() const { return state_ == kUnsupported; }
+ bool constexpr IsNotApplicable() const { return state_ == kNotApplicable; }
+ bool constexpr IsError() const { return state_ == kError; }
template <typename T = ValueType>
bool constexpr IsNotNull(
@@ -49,9 +54,12 @@ class AX_EXPORT AXOptional final {
return true;
}
- bool constexpr HasValue() { return flag_ == kValue; }
+ bool constexpr HasValue() { return state_ == kValue; }
constexpr const ValueType& operator*() const { return value_; }
+ bool HasStateText() const { return !state_text_.empty(); }
+ std::string StateText() const { return state_text_; }
+
std::string ToString() const {
if (IsNotNull())
return "<value>";
@@ -85,12 +93,14 @@ class AX_EXPORT AXOptional final {
kUnsupported,
};
- explicit constexpr AXOptional(State flag_) : value_(nullptr), flag_(flag_) {}
- explicit constexpr AXOptional(ValueType value_, State flag_)
- : value_(value_), flag_(flag_) {}
+ explicit constexpr AXOptional(State state, const std::string& state_text = {})
+ : value_(nullptr), state_(state), state_text_(state_text) {}
+ explicit constexpr AXOptional(ValueType value, State state)
+ : value_(value), state_(state) {}
ValueType value_;
- State flag_;
+ State state_;
+ std::string state_text_;
};
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/inspect/ax_property_node.h b/chromium/ui/accessibility/platform/inspect/ax_property_node.h
index 64d34d1b2fd..ade1520c6b9 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_property_node.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_property_node.h
@@ -5,7 +5,9 @@
#ifndef UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_PROPERTY_NODE_H_
#define UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_PROPERTY_NODE_H_
+#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include "third_party/abseil-cpp/absl/types/optional.h"
diff --git a/chromium/ui/accessibility/platform/inspect/ax_transform_mac.mm b/chromium/ui/accessibility/platform/inspect/ax_transform_mac.mm
index 7cb34e119cd..397f01d7249 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_transform_mac.mm
+++ b/chromium/ui/accessibility/platform/inspect/ax_transform_mac.mm
@@ -11,6 +11,7 @@
#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 {
@@ -96,7 +97,7 @@ base::Value AXNSObjectToBaseValue(id value, const AXTreeIndexerMac* indexer) {
return AXTextMarkerRangeToBaseValue(value, indexer);
// Accessible object
- if (IsNSAccessibilityElement(value) || IsAXUIElement(value)) {
+ if (AXElementWrapper::IsValidElement(value)) {
return AXElementToBaseValue(value, indexer);
}
@@ -115,8 +116,8 @@ base::Value AXPositionToBaseValue(
if (position->IsNullPosition())
return AXNilToBaseValue();
- const AXPlatformTreeManager* manager = static_cast<AXPlatformTreeManager*>(
- AXTreeManagerMap::GetInstance().GetManager(position->tree_id()));
+ const AXPlatformTreeManager* manager =
+ static_cast<AXPlatformTreeManager*>(position->GetManager());
if (!manager)
return AXNilToBaseValue();
diff --git a/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.h b/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.h
index f011ed090c6..819f3ee5719 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.h
@@ -46,12 +46,12 @@ class AX_EXPORT AXTreeFormatterMac : public AXTreeFormatterBase {
base::Value BuildTree(const id root) const;
base::Value BuildTreeForAXUIElement(AXUIElementRef node) const;
- void RecursiveBuildTree(const id node,
+ void RecursiveBuildTree(const AXElementWrapper& ax_element,
const NSRect& root_rect,
const AXTreeIndexerMac* indexer,
base::Value* dict) const;
- void AddProperties(const id node,
+ void AddProperties(const AXElementWrapper& ax_element,
const NSRect& root_rect,
const AXTreeIndexerMac* indexer,
base::Value* dict) const;
@@ -62,7 +62,7 @@ class AX_EXPORT AXTreeFormatterMac : public AXTreeFormatterBase {
const AXPropertyNode& property_node,
const AXTreeIndexerMac* indexer) const;
- base::Value PopulateLocalPosition(const id node,
+ base::Value PopulateLocalPosition(const AXElementWrapper& ax_element,
const NSRect& root_rect) const;
std::string ProcessTreeForOutput(
diff --git a/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.mm b/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.mm
index c40fbd7a50d..b224dc70a34 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.mm
+++ b/chromium/ui/accessibility/platform/inspect/ax_tree_formatter_mac.mm
@@ -11,6 +11,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "ui/accessibility/platform/ax_platform_node_cocoa.h"
+#include "ui/accessibility/platform/inspect/ax_element_wrapper_mac.h"
#include "ui/accessibility/platform/inspect/ax_inspect_scenario.h"
#include "ui/accessibility/platform/inspect/ax_inspect_utils.h"
#include "ui/accessibility/platform/inspect/ax_inspect_utils_mac.h"
@@ -32,10 +33,11 @@ namespace ui {
namespace {
-const char kLocalPositionDictAttr[] = "LocalPosition";
+constexpr char kLocalPositionDictAttr[] = "LocalPosition";
-const char kFailedToParseError[] = "_const_ERROR:FAILED_TO_PARSE";
-const char kNotApplicable[] = "_const_n/a";
+constexpr char kFailedPrefix[] = "_const_ERROR:";
+constexpr char kFailedToParseError[] = "_const_ERROR:FAILED_TO_PARSE";
+constexpr char kNotApplicable[] = "_const_n/a";
} // namespace
@@ -85,11 +87,12 @@ base::Value AXTreeFormatterMac::BuildTree(const id root) const {
AXTreeIndexerMac indexer(root);
base::Value dict(base::Value::Type::DICTIONARY);
- NSPoint position = AXPositionOf(root);
- NSSize size = AXSizeOf(root);
+ AXElementWrapper ax_element(root);
+ NSPoint position = ax_element.Position();
+ NSSize size = ax_element.Size();
NSRect rect = NSMakeRect(position.x, position.y, size.width, size.height);
- RecursiveBuildTree(root, rect, &indexer, &dict);
+ RecursiveBuildTree(ax_element, rect, &indexer, &dict);
return dict;
}
@@ -142,7 +145,9 @@ std::string AXTreeFormatterMac::EvaluateScript(
base::Value result;
if (value.IsError()) {
- result = base::Value(kFailedToParseError);
+ result = value.HasStateText()
+ ? base::Value(kFailedPrefix + value.StateText())
+ : base::Value(kFailedToParseError);
} else if (value.IsNotApplicable()) {
result = base::Value(kNotApplicable);
} else {
@@ -172,61 +177,64 @@ base::Value AXTreeFormatterMac::BuildNode(const id node) const {
AXTreeIndexerMac indexer(node);
base::Value dict(base::Value::Type::DICTIONARY);
- NSPoint position = AXPositionOf(node);
- NSSize size = AXSizeOf(node);
+ AXElementWrapper ax_element(node);
+ NSPoint position = ax_element.Position();
+ NSSize size = ax_element.Size();
NSRect rect = NSMakeRect(position.x, position.y, size.width, size.height);
- AddProperties(node, rect, &indexer, &dict);
+ AddProperties(ax_element, rect, &indexer, &dict);
return dict;
}
-void AXTreeFormatterMac::RecursiveBuildTree(const id node,
+void AXTreeFormatterMac::RecursiveBuildTree(const AXElementWrapper& ax_element,
const NSRect& root_rect,
const AXTreeIndexerMac* indexer,
base::Value* dict) const {
- AXPlatformNodeDelegate* platform_node =
- IsNSAccessibilityElement(node) ? [node nodeDelegate] : nullptr;
+ AXPlatformNodeDelegate* platform_node = ax_element.IsNSAccessibilityElement()
+ ? [ax_element.AsId() nodeDelegate]
+ : nullptr;
if (platform_node && !ShouldDumpNode(*platform_node))
return;
- AddProperties(node, root_rect, indexer, dict);
+ AddProperties(ax_element, root_rect, indexer, dict);
if (platform_node && !ShouldDumpChildren(*platform_node))
return;
- NSArray* children = AXChildrenOf(node);
+ NSArray* children = ax_element.Children();
base::Value child_dict_list(base::Value::Type::LIST);
for (id child in children) {
base::Value child_dict(base::Value::Type::DICTIONARY);
- RecursiveBuildTree(child, root_rect, indexer, &child_dict);
+ RecursiveBuildTree({child}, root_rect, indexer, &child_dict);
child_dict_list.Append(std::move(child_dict));
}
dict->SetPath(kChildrenDictAttr, std::move(child_dict_list));
}
-void AXTreeFormatterMac::AddProperties(const id node,
+void AXTreeFormatterMac::AddProperties(const AXElementWrapper& ax_element,
const NSRect& root_rect,
const AXTreeIndexerMac* indexer,
base::Value* dict) const {
// Chromium special attributes.
- dict->SetPath(kLocalPositionDictAttr, PopulateLocalPosition(node, root_rect));
+ dict->SetPath(kLocalPositionDictAttr,
+ PopulateLocalPosition(ax_element, root_rect));
// Dump all attributes if match-all filter is specified.
if (HasMatchAllPropertyFilter()) {
- NSArray* attributes = AXAttributeNamesOf(node);
+ NSArray* attributes = ax_element.AttributeNames();
for (NSString* attribute : attributes) {
- dict->SetPath(
- SysNSStringToUTF8(attribute),
- AXNSObjectToBaseValue(AXAttributeValueOf(node, attribute), indexer));
+ dict->SetPath(SysNSStringToUTF8(attribute),
+ AXNSObjectToBaseValue(
+ *ax_element.GetAttributeValue(attribute), indexer));
}
return;
}
// Otherwise dump attributes matching allow filters only.
- std::string line_index = indexer->IndexBy(node);
+ std::string line_index = indexer->IndexBy(ax_element.AsId());
for (const AXPropertyNode& property_node :
PropertyFilterNodesFor(line_index)) {
- AXCallStatementInvoker invoker(node, indexer);
+ AXCallStatementInvoker invoker(ax_element.AsId(), indexer);
AXOptionalNSObject value = invoker.Invoke(property_node);
if (value.IsNotApplicable() || value.IsUnsupported()) {
continue;
@@ -242,7 +250,7 @@ void AXTreeFormatterMac::AddProperties(const id node,
}
base::Value AXTreeFormatterMac::PopulateLocalPosition(
- const id node,
+ const AXElementWrapper& ax_element,
const NSRect& root_rect) const {
// The NSAccessibility position of an object is in global coordinates and
// based on the lower-left corner of the object. To make this easier and
@@ -251,8 +259,8 @@ base::Value AXTreeFormatterMac::PopulateLocalPosition(
int root_top = -static_cast<int>(root_rect.origin.y + root_rect.size.height);
int root_left = static_cast<int>(root_rect.origin.x);
- NSPoint node_position = AXPositionOf(node);
- NSSize node_size = AXSizeOf(node);
+ NSPoint node_position = ax_element.Position();
+ NSSize node_size = ax_element.Size();
return AXNSPointToBaseValue(NSMakePoint(
static_cast<int>(node_position.x - root_left),
diff --git a/chromium/ui/accessibility/platform/inspect/ax_tree_indexer_mac.h b/chromium/ui/accessibility/platform/inspect/ax_tree_indexer_mac.h
index 6f9e8eb0cac..bd5904a55e5 100644
--- a/chromium/ui/accessibility/platform/inspect/ax_tree_indexer_mac.h
+++ b/chromium/ui/accessibility/platform/inspect/ax_tree_indexer_mac.h
@@ -5,7 +5,7 @@
#ifndef UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_TREE_INDEXER_MAC_H_
#define UI_ACCESSIBILITY_PLATFORM_INSPECT_AX_TREE_INDEXER_MAC_H_
-#include "ui/accessibility/platform/inspect/ax_inspect_utils_mac.h"
+#include "ui/accessibility/platform/inspect/ax_element_wrapper_mac.h"
#include "ui/accessibility/platform/inspect/ax_tree_indexer.h"
namespace ui {
@@ -15,12 +15,12 @@ namespace ui {
struct AXNodeComparator {
constexpr bool operator()(const gfx::NativeViewAccessible& lhs,
const gfx::NativeViewAccessible& rhs) const {
- if (IsAXUIElement(lhs)) {
- DCHECK(IsAXUIElement(rhs));
+ if (AXElementWrapper::IsAXUIElement(lhs)) {
+ DCHECK(AXElementWrapper::IsAXUIElement(rhs));
return CFHash(lhs) < CFHash(rhs);
}
- DCHECK(IsNSAccessibilityElement(lhs));
- DCHECK(IsNSAccessibilityElement(rhs));
+ DCHECK(AXElementWrapper::IsNSAccessibilityElement(lhs));
+ DCHECK(AXElementWrapper::IsNSAccessibilityElement(rhs));
return lhs < rhs;
}
};
@@ -28,9 +28,9 @@ struct AXNodeComparator {
//
// NSAccessibility tree indexer.
using AXTreeIndexerMac = AXTreeIndexer<const gfx::NativeViewAccessible,
- GetDOMId,
+ AXElementWrapper::DOMIdOf,
NSArray*,
- AXChildrenOf,
+ AXElementWrapper::ChildrenOf,
AXNodeComparator>;
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/test_ax_tree_update.cc b/chromium/ui/accessibility/platform/test_ax_tree_update.cc
new file mode 100644
index 00000000000..5a021738ea1
--- /dev/null
+++ b/chromium/ui/accessibility/platform/test_ax_tree_update.cc
@@ -0,0 +1,56 @@
+// Copyright 2022 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/platform/test_ax_tree_update.h"
+
+namespace ui {
+
+TestAXTreeUpdateNode::TestAXTreeUpdateNode(const TestAXTreeUpdateNode&) =
+ default;
+
+TestAXTreeUpdateNode::TestAXTreeUpdateNode(TestAXTreeUpdateNode&&) = default;
+
+TestAXTreeUpdateNode::~TestAXTreeUpdateNode() = default;
+
+TestAXTreeUpdateNode::TestAXTreeUpdateNode(
+ ax::mojom::Role role,
+ const std::vector<TestAXTreeUpdateNode>& children)
+ : children(children) {
+ DCHECK_NE(role, ax::mojom::Role::kUnknown);
+ data.role = role;
+}
+
+TestAXTreeUpdateNode::TestAXTreeUpdateNode(
+ ax::mojom::Role role,
+ ax::mojom::State state,
+ const std::vector<TestAXTreeUpdateNode>& children)
+ : children(children) {
+ DCHECK_NE(role, ax::mojom::Role::kUnknown);
+ DCHECK_NE(state, ax::mojom::State::kNone);
+ data.role = role;
+ data.AddState(state);
+}
+
+TestAXTreeUpdateNode::TestAXTreeUpdateNode(const std::string& text) {
+ data.role = ax::mojom::Role::kStaticText;
+ data.SetName(text);
+}
+
+TestAXTreeUpdate::TestAXTreeUpdate(const TestAXTreeUpdateNode& root) {
+ root_id = SetSubtree(root);
+}
+
+AXNodeID TestAXTreeUpdate::SetSubtree(const TestAXTreeUpdateNode& node) {
+ size_t node_index = nodes.size();
+ nodes.push_back(node.data);
+ nodes[node_index].id = node_index + 1;
+ std::vector<AXNodeID> child_ids;
+ for (const auto& child : node.children) {
+ child_ids.push_back(SetSubtree(child));
+ }
+ nodes[node_index].child_ids = child_ids;
+ return nodes[node_index].id;
+}
+
+} // namespace ui
diff --git a/chromium/ui/accessibility/platform/test_ax_tree_update.h b/chromium/ui/accessibility/platform/test_ax_tree_update.h
new file mode 100644
index 00000000000..41878fa0920
--- /dev/null
+++ b/chromium/ui/accessibility/platform/test_ax_tree_update.h
@@ -0,0 +1,54 @@
+// Copyright 2022 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.
+
+#ifndef UI_ACCESSIBILITY_PLATFORM_TEST_AX_TREE_UPDATE_H_
+#define UI_ACCESSIBILITY_PLATFORM_TEST_AX_TREE_UPDATE_H_
+
+#include "ui/accessibility/ax_tree_update.h"
+
+namespace ui {
+
+// These utility classes, help construct an AXTreeUpdate together with all of
+// the updated nodes more easily. Only for use in tests for constructing /
+// updating simple accessibility trees.
+
+// Used to construct AXTreeUpdate node.
+struct TestAXTreeUpdateNode {
+ TestAXTreeUpdateNode() = delete;
+ ~TestAXTreeUpdateNode();
+
+ TestAXTreeUpdateNode(const TestAXTreeUpdateNode&);
+ TestAXTreeUpdateNode(TestAXTreeUpdateNode&&);
+
+ TestAXTreeUpdateNode(ax::mojom::Role role,
+ const std::vector<TestAXTreeUpdateNode>& children);
+ TestAXTreeUpdateNode(ax::mojom::Role role,
+ ax::mojom::State state,
+ const std::vector<TestAXTreeUpdateNode>& children);
+ TestAXTreeUpdateNode(const std::string& text);
+
+ AXNodeData data;
+ std::vector<TestAXTreeUpdateNode> children;
+};
+
+// Used to construct an accessible tree from a hierarchical list of nodes
+// {<node_properties>, {<node_children>}}. For example,
+// {Role::kRootWebArea, {"text"}} will create the following tree:
+// kRootWebArea
+// ++kStaticText "text"
+class TestAXTreeUpdate final : public AXTreeUpdate {
+ public:
+ TestAXTreeUpdate(const TestAXTreeUpdateNode& root);
+
+ TestAXTreeUpdate(const TestAXTreeUpdate&) = delete;
+ TestAXTreeUpdate& operator=(const TestAXTreeUpdate&) = delete;
+
+ private:
+ // Recursively creates the tree update structure.
+ AXNodeID SetSubtree(const TestAXTreeUpdateNode& node);
+};
+
+} // namespace ui
+
+#endif // UI_ACCESSIBILITY_PLATFORM_TEST_AX_TREE_UPDATE_H_
diff --git a/chromium/ui/accessibility/test_ax_tree_manager.cc b/chromium/ui/accessibility/test_ax_tree_manager.cc
index 5ee1750a265..392ba9146ff 100644
--- a/chromium/ui/accessibility/test_ax_tree_manager.cc
+++ b/chromium/ui/accessibility/test_ax_tree_manager.cc
@@ -13,93 +13,66 @@ namespace ui {
TestAXTreeManager::TestAXTreeManager() = default;
TestAXTreeManager::TestAXTreeManager(std::unique_ptr<AXTree> tree)
- : tree_(std::move(tree)) {
- if (tree_)
- AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
-}
+ : AXTreeManager(std::move(tree)) {}
TestAXTreeManager::TestAXTreeManager(TestAXTreeManager&& manager)
- : tree_(std::move(manager.tree_)) {
- if (tree_) {
- AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
- AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+ : AXTreeManager(std::move(manager.ax_tree_)) {
+ if (ax_tree_) {
+ GetMap().RemoveTreeManager(GetTreeID());
+ GetMap().AddTreeManager(GetTreeID(), this);
}
}
TestAXTreeManager& TestAXTreeManager::operator=(TestAXTreeManager&& manager) {
if (this == &manager)
return *this;
- if (manager.tree_)
- AXTreeManagerMap::GetInstance().RemoveTreeManager(manager.GetTreeID());
+ if (manager.ax_tree_)
+ GetMap().RemoveTreeManager(manager.GetTreeID());
// std::move(nullptr) == nullptr, so no need to check if `manager.tree_` is
// assigned.
- SetTree(std::move(manager.tree_));
+ SetTree(std::move(manager.ax_tree_));
return *this;
}
-TestAXTreeManager::~TestAXTreeManager() {
- if (tree_)
- AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
-}
+TestAXTreeManager::~TestAXTreeManager() = default;
void TestAXTreeManager::DestroyTree() {
- if (!tree_)
+ if (!ax_tree_)
return;
- AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
- tree_.reset();
+ GetMap().RemoveTreeManager(GetTreeID());
+ ax_tree_.reset();
}
AXTree* TestAXTreeManager::GetTree() const {
- DCHECK(tree_) << "Did you forget to call SetTree?";
- return tree_.get();
+ DCHECK(ax_tree_) << "Did you forget to call SetTree?";
+ return ax_tree_.get();
}
void TestAXTreeManager::SetTree(std::unique_ptr<AXTree> tree) {
- if (tree_)
- AXTreeManagerMap::GetInstance().RemoveTreeManager(GetTreeID());
+ if (ax_tree_)
+ GetMap().RemoveTreeManager(GetTreeID());
- tree_ = std::move(tree);
- if (tree_)
- AXTreeManagerMap::GetInstance().AddTreeManager(GetTreeID(), this);
+ ax_tree_ = std::move(tree);
+ ax_tree_id_ = GetTreeID();
+ if (ax_tree_)
+ GetMap().AddTreeManager(GetTreeID(), this);
}
AXNode* TestAXTreeManager::GetNodeFromTree(const AXTreeID tree_id,
const AXNodeID node_id) const {
- return (tree_ && GetTreeID() == tree_id) ? tree_->GetFromId(node_id)
- : nullptr;
+ return (ax_tree_ && GetTreeID() == tree_id) ? ax_tree_->GetFromId(node_id)
+ : nullptr;
}
AXNode* TestAXTreeManager::GetNodeFromTree(const AXNodeID node_id) const {
- return tree_ ? tree_->GetFromId(node_id) : nullptr;
-}
-
-void TestAXTreeManager::AddObserver(AXTreeObserver* observer) {
- if (tree_)
- tree_->AddObserver(observer);
-}
-
-void TestAXTreeManager::RemoveObserver(AXTreeObserver* observer) {
- if (tree_)
- tree_->RemoveObserver(observer);
-}
-
-AXTreeID TestAXTreeManager::GetTreeID() const {
- return tree_ ? tree_->data().tree_id : AXTreeIDUnknown();
-}
-
-AXTreeID TestAXTreeManager::GetParentTreeID() const {
- return tree_ ? tree_->data().parent_tree_id : AXTreeIDUnknown();
-}
-
-AXNode* TestAXTreeManager::GetRootAsAXNode() const {
- return tree_ ? tree_->root() : nullptr;
+ return ax_tree_ ? ax_tree_->GetFromId(node_id) : nullptr;
}
AXNode* TestAXTreeManager::GetParentNodeFromParentTreeAsAXNode() const {
AXTreeID parent_tree_id = GetParentTreeID();
- TestAXTreeManager* parent_manager = static_cast<TestAXTreeManager*>(
- AXTreeManagerMap::GetInstance().GetManager(parent_tree_id));
+ TestAXTreeManager* parent_manager =
+ static_cast<TestAXTreeManager*>(AXTreeManager::FromID(parent_tree_id));
if (!parent_manager)
return nullptr;
@@ -116,8 +89,4 @@ AXNode* TestAXTreeManager::GetParentNodeFromParentTreeAsAXNode() const {
return nullptr;
}
-std::string TestAXTreeManager::ToString() const {
- return "<TestAXTreeManager>";
-}
-
} // namespace ui
diff --git a/chromium/ui/accessibility/test_ax_tree_manager.h b/chromium/ui/accessibility/test_ax_tree_manager.h
index dabc5650132..a3299a3ac3a 100644
--- a/chromium/ui/accessibility/test_ax_tree_manager.h
+++ b/chromium/ui/accessibility/test_ax_tree_manager.h
@@ -30,7 +30,7 @@ class AX_EXPORT TestAXTreeManager : public AXTreeManager {
// Takes ownership of |tree|.
explicit TestAXTreeManager(std::unique_ptr<AXTree> tree);
- virtual ~TestAXTreeManager();
+ ~TestAXTreeManager() override;
TestAXTreeManager(const TestAXTreeManager& manager) = delete;
TestAXTreeManager& operator=(const TestAXTreeManager& manager) = delete;
@@ -47,16 +47,7 @@ class AX_EXPORT TestAXTreeManager : public AXTreeManager {
AXNode* GetNodeFromTree(const AXTreeID tree_id,
const AXNodeID node_id) const override;
AXNode* GetNodeFromTree(const AXNodeID node_id) const override;
- void AddObserver(AXTreeObserver* observer) override;
- void RemoveObserver(AXTreeObserver* observer) override;
- AXTreeID GetTreeID() const override;
- AXTreeID GetParentTreeID() const override;
- AXNode* GetRootAsAXNode() const override;
AXNode* GetParentNodeFromParentTreeAsAXNode() const override;
- std::string ToString() const override;
-
- private:
- std::unique_ptr<AXTree> tree_;
};
} // namespace ui
diff --git a/chromium/ui/accessibility/test_ax_tree_update_json_reader.cc b/chromium/ui/accessibility/test_ax_tree_update_json_reader.cc
new file mode 100644
index 00000000000..11bc70b65f2
--- /dev/null
+++ b/chromium/ui/accessibility/test_ax_tree_update_json_reader.cc
@@ -0,0 +1,342 @@
+// Copyright 2022 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/test_ax_tree_update_json_reader.h"
+
+#include "base/containers/contains.h"
+#include "base/containers/flat_set.h"
+#include "base/numerics/clamped_math.h"
+#include "base/strings/string_split.h"
+#include "ui/accessibility/ax_enum_util.h"
+
+namespace {
+
+using RoleConversions = const std::map<std::string, ax::mojom::Role>;
+
+// The 3 lists below include all terms that are not parsed now if they are in a
+// JSON file. Since this class is only used for testing, we will only encounter
+// errors regarding that in the following two cases:
+// - We add a new JSON file (or modify one) for testing that includes different
+// unsupported.
+// - There is a new property added to AXNode that is not covered in the existing
+// JSON parsor and a test relies on it.
+// In both above cases, existing tests will catch the issue and warn about the
+// missing/changed property.
+const base::flat_set<std::string> kUnusedAxNodeProperties = {
+ "controls", "describedby", "details", "disabled", "editable",
+ "focused", "hidden", "hiddenRoot", "live", "multiline",
+ "readonly", "relevant", "required", "settable"};
+
+const base::flat_set<std::string> kUnusedAxNodeItems = {
+ "frameId", "ignoredReasons", "parentId"};
+
+const base::flat_set<std::string> kUnusedStyles = {
+ "background-image", "background-size", "clip", "font-style",
+ "margin-bottom", "margin-left", "margin-right", "margin-top",
+ "opacity", "padding-bottom", "padding-left", "padding-right",
+ "padding-top", "position", "text-align", "text-decoration",
+ "z-index"};
+
+int GetAsInt(const base::Value& value) {
+ if (value.is_int())
+ return value.GetInt();
+ if (value.is_string())
+ return atoi(value.GetString().c_str());
+
+ NOTREACHED() << "Unexpected: " << value;
+ return 0;
+}
+
+double GetAsDouble(const base::Value& value) {
+ if (value.is_double())
+ return value.GetDouble();
+ if (value.is_int())
+ return value.GetInt();
+ if (value.is_string())
+ return atof(value.GetString().c_str());
+
+ NOTREACHED() << "Unexpected: " << value;
+ return 0;
+}
+
+bool GetAsBoolean(const base::Value& value) {
+ if (value.is_bool())
+ return value.GetBool();
+ if (value.is_string()) {
+ if (value.GetString() == "false")
+ return false;
+ if (value.GetString() == "true")
+ return true;
+ }
+
+ NOTREACHED() << "Unexpected: " << value;
+ return false;
+}
+
+void GetTypeAndValue(const base::Value& node,
+ std::string& type,
+ std::string& value) {
+ type = node.GetDict().Find("type")->GetString();
+ value = node.GetDict().Find("value")->GetString();
+}
+
+ui::AXNodeID AddNode(ui::AXTreeUpdate& tree_update,
+ const base::Value& node,
+ RoleConversions* role_conversions);
+
+void ParseAxNodeChildIds(ui::AXNodeData& node_data,
+ const base::Value& child_ids) {
+ for (const auto& item : child_ids.GetList())
+ node_data.child_ids.push_back(GetAsInt(item));
+}
+
+void ParseAxNodeDescription(ui::AXNodeData& node_data,
+ const base::Value& description) {
+ std::string type, value;
+ GetTypeAndValue(description, type, value);
+ DCHECK_EQ(type, "computedString");
+ node_data.SetDescription(value);
+}
+
+void ParseAxNodeName(ui::AXNodeData& node_data, const base::Value& name) {
+ std::string type, value;
+ GetTypeAndValue(name, type, value);
+ DCHECK_EQ(type, "computedString");
+ node_data.SetName(value);
+}
+
+void ParseAxNodeProperties(ui::AXNodeData& node_data,
+ const base::Value& properties) {
+ if (properties.is_list()) {
+ for (const auto& item : properties.GetList())
+ ParseAxNodeProperties(node_data, item);
+ return;
+ }
+
+ const std::string prop_type = properties.GetDict().Find("name")->GetString();
+ const base::Value* prop_value =
+ properties.GetDict().Find("value")->GetDict().Find("value");
+
+ if (prop_type == "atomic") {
+ node_data.AddBoolAttribute(
+ ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot,
+ !GetAsBoolean(*prop_value));
+ } else if (prop_type == "focusable") {
+ if (GetAsBoolean(*prop_value))
+ node_data.AddState(ax::mojom::State::kFocusable);
+ } else if (prop_type == "expanded") {
+ if (GetAsBoolean(*prop_value))
+ node_data.AddState(ax::mojom::State::kExpanded);
+ } else if (prop_type == "hasPopup") {
+ node_data.SetHasPopup(
+ ui::ParseAXEnum<ax::mojom::HasPopup>(prop_value->GetString().c_str()));
+ } else if (prop_type == "invalid") {
+ node_data.SetInvalidState(GetAsBoolean(*prop_value)
+ ? ax::mojom::InvalidState::kTrue
+ : ax::mojom::InvalidState::kFalse);
+ } else if (prop_type == "level") {
+ node_data.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
+ GetAsInt(*prop_value));
+ } else {
+ DCHECK(base::Contains(kUnusedAxNodeProperties, prop_type)) << prop_type;
+ }
+}
+
+ax::mojom::Role RoleFromString(std::string role,
+ RoleConversions* role_conversions) {
+ const auto& item = role_conversions->find(role);
+ DCHECK(item != role_conversions->end()) << role;
+ return item->second;
+}
+
+void ParseAxNodeRole(ui::AXNodeData& node_data,
+ const base::Value& role,
+ RoleConversions* role_conversions) {
+ const std::string role_type = role.GetDict().Find("type")->GetString();
+ std::string role_value = role.GetDict().Find("value")->GetString();
+
+ DCHECK(role_type == "role" || role_type == "internalRole");
+
+ node_data.role = RoleFromString(role_value, role_conversions);
+}
+
+void ParseAxNode(ui::AXNodeData& node_data,
+ const base::Value& ax_node,
+ RoleConversions* role_conversions) {
+ // Store the name and set it at the end because |AXNodeData::SetName|
+ // expects a valid role to have already been set prior to calling it.
+ base::Value name_value;
+ for (const auto item : ax_node.GetDict()) {
+ if (item.first == "backendDOMNodeId") {
+ node_data.AddIntAttribute(ax::mojom::IntAttribute::kDOMNodeId,
+ GetAsInt(item.second));
+ } else if (item.first == "childIds") {
+ ParseAxNodeChildIds(node_data, item.second);
+ } else if (item.first == "description") {
+ ParseAxNodeDescription(node_data, item.second);
+ } else if (item.first == "ignored") {
+ DCHECK(item.second.is_bool());
+ if (item.second.GetBool())
+ node_data.AddState(ax::mojom::State::kIgnored);
+ } else if (item.first == "name") {
+ name_value = item.second.Clone();
+ } else if (item.first == "nodeId") {
+ node_data.id = GetAsInt(item.second);
+ } else if (item.first == "properties") {
+ ParseAxNodeProperties(node_data, item.second);
+ } else if (item.first == "role") {
+ ParseAxNodeRole(node_data, item.second, role_conversions);
+ } else {
+ DCHECK(base::Contains(kUnusedAxNodeItems, item.first)) << item.first;
+ }
+ }
+ if (!name_value.is_none())
+ ParseAxNodeName(node_data, name_value);
+}
+
+void ParseChildren(ui::AXTreeUpdate& tree_update,
+ const base::Value& children,
+ RoleConversions* role_conversions) {
+ for (const auto& child : children.GetList())
+ AddNode(tree_update, child, role_conversions);
+}
+
+// Converts "rgb(R,G,B)" or "rgba(R,G,B,A)" to one ARGB integer where R,G, and B
+// are integers and A is float < 1.
+uint32_t ConvertRgbaStringToArgbInt(const std::string& argb_string) {
+ std::vector<std::string> values = base::SplitString(
+ argb_string, ",()", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+ uint32_t a, r, g, b;
+
+ if (values.size() == 4 && values[0] == "rgb") {
+ a = 0;
+ } else if (values.size() == 5 && values[0] == "rgba") {
+ a = base::ClampRound(atof(values[4].c_str()) * 255);
+ } else {
+ NOTREACHED() << "Unexpected color value: " << argb_string;
+ return -1;
+ }
+
+ r = atoi(values[1].c_str());
+ g = atoi(values[2].c_str());
+ b = atoi(values[3].c_str());
+
+ return (a << 24) + (r << 16) + (g << 8) + b;
+}
+
+void ParseStyle(ui::AXNodeData& node_data, const base::Value& style) {
+ const std::string& name = style.GetDict().Find("name")->GetString();
+ const std::string& value = style.GetDict().Find("value")->GetString();
+
+ if (name == "color") {
+ node_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
+ ConvertRgbaStringToArgbInt(value));
+ } else if (name == "direction") {
+ node_data.AddIntAttribute(
+ ax::mojom::IntAttribute::kTextDirection,
+ static_cast<int>(
+ ui::ParseAXEnum<ax::mojom::WritingDirection>(value.c_str())));
+ } else if (name == "display") {
+ node_data.AddStringAttribute(ax::mojom::StringAttribute::kDisplay, value);
+ } else if (name == "font-size") {
+ // Drop the 'px' at the end of font size.
+ DCHECK(style.GetDict().Find("value")->is_string());
+ node_data.AddFloatAttribute(
+ ax::mojom::FloatAttribute::kFontSize,
+ atof(value.substr(0, value.length() - 2).c_str()));
+ } else if (name == "font-weight") {
+ DCHECK(style.GetDict().Find("value")->is_string());
+ node_data.AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize,
+ atof(value.c_str()));
+ } else if (name == "list-style-type") {
+ node_data.AddIntAttribute(
+ ax::mojom::IntAttribute::kListStyle,
+ static_cast<int>(ui::ParseAXEnum<ax::mojom::ListStyle>(value.c_str())));
+ } else if (name == "visibility") {
+ if (value == "hidden")
+ node_data.AddState(ax::mojom::State::kInvisible);
+ else
+ DCHECK_EQ(value, "visible");
+ } else {
+ DCHECK(base::Contains(kUnusedStyles, name)) << name;
+ }
+}
+
+void ParseExtras(ui::AXNodeData& node_data, const base::Value& extras) {
+ for (const auto extra : extras.GetDict()) {
+ const base::Value::List& items = extra.second.GetList();
+ if (extra.first == "bounds") {
+ node_data.relative_bounds.bounds.set_x(GetAsDouble(items[0]));
+ node_data.relative_bounds.bounds.set_y(GetAsDouble(items[1]));
+ node_data.relative_bounds.bounds.set_width(GetAsDouble(items[2]));
+ node_data.relative_bounds.bounds.set_height(GetAsDouble(items[3]));
+ } else if (extra.first == "styles") {
+ for (const auto& style : items)
+ ParseStyle(node_data, style);
+ } else {
+ NOTREACHED() << "Unexpected: " << extra.first;
+ }
+ }
+}
+
+// Adds a node and returns its id.
+ui::AXNodeID AddNode(ui::AXTreeUpdate& tree_update,
+ const base::Value& node,
+ RoleConversions* role_conversions) {
+ ui::AXNodeData node_data;
+
+ // Store the string and set it at the end because |AXNodeData::SetName|
+ // expects a valid role to have already been set prior to calling it.
+ std::string name_string;
+
+ for (const auto item : node.GetDict()) {
+ if (item.first == "axNode") {
+ ParseAxNode(node_data, item.second, role_conversions);
+ } else if (item.first == "backendDomId") {
+ node_data.AddIntAttribute(ax::mojom::IntAttribute::kDOMNodeId,
+ GetAsInt(item.second));
+ } else if (item.first == "children") {
+ ParseChildren(tree_update, item.second, role_conversions);
+ } else if (item.first == "description") {
+ node_data.SetDescription(item.second.GetString());
+ } else if (item.first == "extras") {
+ ParseExtras(node_data, item.second);
+ } else if (item.first == "interesting") {
+ // Not used yet, boolean.
+ } else if (item.first == "name") {
+ name_string = item.second.GetString();
+ } else if (item.first == "role") {
+ node_data.role =
+ RoleFromString(item.second.GetString(), role_conversions);
+ } else {
+ NOTREACHED() << "Unexpected: " << item.first;
+ }
+ }
+
+ node_data.SetName(name_string);
+
+ tree_update.nodes.push_back(node_data);
+
+ return node_data.id;
+}
+
+} // namespace
+
+namespace ui {
+
+AXTreeUpdate AXTreeUpdateFromJSON(const base::Value& json,
+ RoleConversions* role_conversions) {
+ AXTreeUpdate tree_update;
+
+ // Input should be a list with one item, which is the root node.
+ DCHECK(json.is_list() && json.GetList().size() == 1);
+
+ tree_update.root_id =
+ AddNode(tree_update, json.GetList().front(), role_conversions);
+
+ return tree_update;
+}
+
+} // namespace ui
diff --git a/chromium/ui/accessibility/test_ax_tree_update_json_reader.h b/chromium/ui/accessibility/test_ax_tree_update_json_reader.h
new file mode 100644
index 00000000000..2fd51c37115
--- /dev/null
+++ b/chromium/ui/accessibility/test_ax_tree_update_json_reader.h
@@ -0,0 +1,27 @@
+// Copyright 2022 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.
+
+#ifndef UI_ACCESSIBILITY_TEST_AX_TREE_UPDATE_JSON_READER_H_
+#define UI_ACCESSIBILITY_TEST_AX_TREE_UPDATE_JSON_READER_H_
+
+#include "base/values.h"
+#include "ui/accessibility/ax_tree.h"
+
+namespace ui {
+
+// This function assumes that the JSON input is properly formatted and any
+// error in parsing can result in a runtime error.
+// The JSON format is based on the output of
+// |InspectorAccessibilityAgent::WalkAXNodesToDepth| and should stay in sync
+// with that.
+// NOTE: This parser is not complete and only processes the required tags for
+// the existing tests.
+// |role_conversions| is a map of role strings in the JSON file to Chrome roles.
+// TODO(https://crbug.com/1278249): Drop |role_conversions| once Chrome roles
+// are added to the JSON file.
+AXTreeUpdate AXTreeUpdateFromJSON(
+ const base::Value& json,
+ const std::map<std::string, ax::mojom::Role>* role_conversions);
+} // namespace ui
+#endif // UI_ACCESSIBILITY_TEST_AX_TREE_UPDATE_JSON_READER_H_