summaryrefslogtreecommitdiff
path: root/chromium/ui/accessibility
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/ui/accessibility')
-rw-r--r--chromium/ui/accessibility/BUILD.gn207
-rw-r--r--chromium/ui/accessibility/accessibility_features.cc32
-rw-r--r--chromium/ui/accessibility/accessibility_features.h50
-rw-r--r--chromium/ui/accessibility/accessibility_switches.h37
-rw-r--r--chromium/ui/accessibility/ax_enum_util.cc8
-rw-r--r--chromium/ui/accessibility/ax_enums.mojom28
-rw-r--r--chromium/ui/accessibility/ax_event.cc2
-rw-r--r--chromium/ui/accessibility/ax_event_generator.cc21
-rw-r--r--chromium/ui/accessibility/ax_event_generator.h13
-rw-r--r--chromium/ui/accessibility/ax_mode.cc3
-rw-r--r--chromium/ui/accessibility/ax_mode.h7
-rw-r--r--chromium/ui/accessibility/ax_node.cc288
-rw-r--r--chromium/ui/accessibility/ax_node.h74
-rw-r--r--chromium/ui/accessibility/ax_node_data.cc49
-rw-r--r--chromium/ui/accessibility/ax_node_data.h7
-rw-r--r--chromium/ui/accessibility/ax_node_position.cc5
-rw-r--r--chromium/ui/accessibility/ax_node_position_unittest.cc129
-rw-r--r--chromium/ui/accessibility/ax_param_traits_macros.h1
-rw-r--r--chromium/ui/accessibility/ax_position.h27
-rw-r--r--chromium/ui/accessibility/ax_range_unittest.cc10
-rw-r--r--chromium/ui/accessibility/ax_role_properties.cc12
-rw-r--r--chromium/ui/accessibility/ax_role_properties.h8
-rw-r--r--chromium/ui/accessibility/ax_table_fuzzer.cc27
-rw-r--r--chromium/ui/accessibility/ax_tree.cc88
-rw-r--r--chromium/ui/accessibility/ax_tree.h31
-rw-r--r--chromium/ui/accessibility/ax_tree_id.cc12
-rw-r--r--chromium/ui/accessibility/ax_tree_unittest.cc313
-rw-r--r--chromium/ui/accessibility/ax_tree_update.h17
-rw-r--r--chromium/ui/accessibility/extensions/chromevoxclassic/BUILD.gn6
-rw-r--r--chromium/ui/accessibility/extensions/chromevoxclassic/testing/chromevox_unittest_base.js9
-rw-r--r--chromium/ui/accessibility/mojom/ax_tree_update.mojom2
-rw-r--r--chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc2
-rw-r--r--chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.h7
-rw-r--r--chromium/ui/accessibility/platform/BUILD.gn142
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win.cc66
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc104
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node.h2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc169
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.h3
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc19
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.cc108
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.h54
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate.h30
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc56
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h12
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_mac.h1
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_mac.mm21
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc18
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc43
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h10
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc27
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_unittest.cc8
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.cc173
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.h35
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc496
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h3
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_relation_win.cc6
-rw-r--r--chromium/ui/accessibility/platform/ichromeaccessible.idl64
-rw-r--r--chromium/ui/accessibility/platform/test_ax_node_wrapper.cc54
-rw-r--r--chromium/ui/accessibility/platform/test_ax_node_wrapper.h4
-rw-r--r--chromium/ui/accessibility/platform/uia_registrar_win.cc50
-rw-r--r--chromium/ui/accessibility/platform/uia_registrar_win.h45
-rw-r--r--chromium/ui/accessibility/test_ax_node_helper.cc204
-rw-r--r--chromium/ui/accessibility/test_ax_node_helper.h50
65 files changed, 2863 insertions, 748 deletions
diff --git a/chromium/ui/accessibility/BUILD.gn b/chromium/ui/accessibility/BUILD.gn
index fc74899db46..0297bff6f13 100644
--- a/chromium/ui/accessibility/BUILD.gn
+++ b/chromium/ui/accessibility/BUILD.gn
@@ -16,6 +16,10 @@ if (is_android) {
import("//build/config/android/rules.gni")
}
+if (is_win) {
+ import("//build/toolchain/win/midl.gni")
+}
+
# Reset sources_assignment_filter for the BUILD.gn file to prevent
# regression during the migration of Chromium away from the feature.
# See docs/no_sources_assignment_filter.md for more information.
@@ -41,6 +45,10 @@ jumbo_component("ax_base") {
defines = [ "AX_BASE_IMPLEMENTATION" ]
sources = [
+ "accessibility_features.cc",
+ "accessibility_features.h",
+ "accessibility_switches.cc",
+ "accessibility_switches.h",
"ax_base_export.h",
"ax_enum_util.cc",
"ax_enum_util.h",
@@ -70,14 +78,18 @@ jumbo_component("ax_base") {
]
}
+#if (is_win) {
+# midl("ichromeaccessible") {
+# sources = [
+# "platform/ichromeaccessible.idl",
+# ]
+# }
+#}
+
jumbo_component("accessibility") {
defines = [ "AX_IMPLEMENTATION" ]
sources = [
- "accessibility_features.cc",
- "accessibility_features.h",
- "accessibility_switches.cc",
- "accessibility_switches.h",
"ax_action_data.cc",
"ax_action_data.h",
"ax_action_handler.cc",
@@ -132,85 +144,21 @@ jumbo_component("accessibility") {
"ax_tree_update_forward.h",
"null_ax_action_target.cc",
"null_ax_action_target.h",
-
- # ax_android_constants* are used in ax_assistant_structure.cc.
- "platform/ax_android_constants.cc",
- "platform/ax_android_constants.h",
-
- # ax_platform_node* are used for enable/disable accessibility and
- # test_ax_node_wrapper.
- "platform/ax_platform_node.cc",
- "platform/ax_platform_node.h",
- "platform/ax_platform_node_base.cc",
- "platform/ax_platform_node_base.h",
- "platform/ax_platform_node_delegate.h",
- "platform/ax_platform_node_delegate_base.cc",
- "platform/ax_platform_node_delegate_base.h",
-
- # ax_platform_node_test_helper.{cc,h} are used in
- # browser_view_browsertest.cc
- "platform/ax_platform_node_test_helper.cc",
- "platform/ax_platform_node_test_helper.h",
-
- # ax_unique_id.{cc,h} are used in browser_accessibility.cc and
- # view_accessibility.cc
- "platform/ax_unique_id.cc",
- "platform/ax_unique_id.h",
-
- # compute_attributes.{cc,h} are used in
- # accessibility_tree_formatter_blink.cc
- "platform/compute_attributes.cc",
- "platform/compute_attributes.h",
]
- deps = [ "//third_party/cld_3/src/src:cld_3" ]
+ deps = [
+ "//base/util/values:values_util",
+ "//third_party/cld_3/src/src:cld_3",
+ ]
public_deps = [
":ax_base",
- "//ui/display",
+ "//ui/accessibility/platform",
]
- if (has_native_accessibility) {
- sources += [
- "platform/ax_platform_text_boundary.cc",
- "platform/ax_platform_text_boundary.h",
- ]
-
- if (use_atk) {
- # ax_platform_text_boundary.h includes atk.h, so ATK is needed as a public
- # config to ensure anything that includes this is able to find atk.h.
- public_configs = [ "//build/config/linux/atk" ]
- }
- }
-
- if (is_win) {
- sources += [
- "platform/ax_fragment_root_delegate_win.h",
- "platform/ax_fragment_root_win.cc",
- "platform/ax_fragment_root_win.h",
- "platform/ax_platform_node_delegate_utils_win.cc",
- "platform/ax_platform_node_delegate_utils_win.h",
- "platform/ax_platform_node_textchildprovider_win.cc",
- "platform/ax_platform_node_textchildprovider_win.h",
- "platform/ax_platform_node_textprovider_win.cc",
- "platform/ax_platform_node_textprovider_win.h",
- "platform/ax_platform_node_textrangeprovider_win.cc",
- "platform/ax_platform_node_textrangeprovider_win.h",
- "platform/ax_platform_node_win.cc",
- "platform/ax_platform_node_win.h",
- "platform/ax_platform_relation_win.cc",
- "platform/ax_platform_relation_win.h",
- "platform/ax_system_caret_win.cc",
- "platform/ax_system_caret_win.h",
- ]
-
- public_deps += [ "//third_party/iaccessible2" ]
-
- libs = [
- "oleacc.lib",
- "uiautomationcore.lib",
- ]
- }
+ # Allows the files from //ui/accessibility/platform includes headers
+ # from this directory.
+ allow_circular_includes_from = [ "//ui/accessibility/platform" ]
if (!is_ios) {
sources += [
@@ -225,40 +173,6 @@ jumbo_component("accessibility") {
]
}
- if (is_mac) {
- sources += [
- "platform/ax_platform_node_mac.h",
- "platform/ax_platform_node_mac.mm",
- ]
-
- libs = [
- "AppKit.framework",
- "Foundation.framework",
- ]
- }
-
- if (use_atk) {
- sources += [
- "platform/atk_util_auralinux.cc",
- "platform/atk_util_auralinux.h",
- "platform/atk_util_auralinux_gtk.cc",
- "platform/ax_platform_atk_hyperlink.cc",
- "platform/ax_platform_atk_hyperlink.h",
- "platform/ax_platform_node_auralinux.cc",
- "platform/ax_platform_node_auralinux.h",
- ]
-
- configs += [ "//build/config/linux/atk" ]
-
- if (use_glib) {
- configs += [ "//build/config/linux:glib" ]
- }
-
- if (use_x11) {
- public_deps += [ "//ui/gfx/x" ]
- }
- }
-
if (use_aura) {
sources += [
"aura/aura_window_properties.cc",
@@ -281,15 +195,21 @@ source_set("ax_assistant") {
static_library("test_support") {
testonly = true
sources = [
- # test_ax_node_wrapper.{cc,h} are used in ax_range_unittest.cc
- "platform/test_ax_node_wrapper.cc",
- "platform/test_ax_node_wrapper.h",
+ "test_ax_node_helper.cc",
+ "test_ax_node_helper.h",
"test_ax_tree_manager.cc",
"test_ax_tree_manager.h",
"tree_generator.cc",
"tree_generator.h",
]
+ if (has_native_accessibility) {
+ sources += [
+ "platform/test_ax_node_wrapper.cc",
+ "platform/test_ax_node_wrapper.h",
+ ]
+ }
+
deps = [ ":accessibility" ]
}
@@ -325,23 +245,6 @@ test("accessibility_unittests") {
"run_all_unittests.cc",
]
- if (is_win) {
- sources += [
- "platform/ax_fragment_root_win_unittest.cc",
- "platform/ax_platform_node_textchildprovider_win_unittest.cc",
- "platform/ax_platform_node_textprovider_win_unittest.cc",
- "platform/ax_platform_node_textrangeprovider_win_unittest.cc",
- "platform/ax_platform_node_win_unittest.cc",
- "platform/ax_platform_node_win_unittest.h",
- ]
- }
-
- if (has_native_accessibility) {
- # This test depends heavily on NativeViewAccessible, which is only
- # implemented on these platforms.
- sources += [ "platform/ax_platform_node_base_unittest.cc" ]
- }
-
deps = [
":accessibility",
":test_support",
@@ -357,22 +260,40 @@ test("accessibility_unittests") {
"//ui/gfx:test_support",
]
- if (is_win) {
- deps += [ "//third_party/iaccessible2" ]
+ if (has_native_accessibility) {
+ # This test depends heavily on NativeViewAccessible, which is only
+ # implemented on these platforms.
+ sources += [ "platform/ax_platform_node_base_unittest.cc" ]
- libs = [
- "oleacc.lib",
- "uiautomationcore.lib",
- ]
- }
+ if (is_win) {
+ sources += [
+ "platform/ax_fragment_root_win_unittest.cc",
+ "platform/ax_platform_node_textchildprovider_win_unittest.cc",
+ "platform/ax_platform_node_textprovider_win_unittest.cc",
+ "platform/ax_platform_node_textrangeprovider_win_unittest.cc",
+ "platform/ax_platform_node_win_unittest.cc",
+ "platform/ax_platform_node_win_unittest.h",
+ ]
+
+ deps += [
+ "//third_party/iaccessible2",
+ "//ui/accessibility/platform:ichromeaccessible",
+ ]
+
+ libs = [
+ "oleacc.lib",
+ "uiautomationcore.lib",
+ ]
+ }
- if (use_atk) {
- sources += [
- "platform/atk_util_auralinux_unittest.cc",
- "platform/ax_platform_node_auralinux_unittest.cc",
- ]
+ if (use_atk) {
+ sources += [
+ "platform/atk_util_auralinux_unittest.cc",
+ "platform/ax_platform_node_auralinux_unittest.cc",
+ ]
- configs += [ "//build/config/linux/atk" ]
+ configs += [ "//build/config/linux/atk" ]
+ }
}
}
diff --git a/chromium/ui/accessibility/accessibility_features.cc b/chromium/ui/accessibility/accessibility_features.cc
index 6b6f63f1b90..4a52188c1b6 100644
--- a/chromium/ui/accessibility/accessibility_features.cc
+++ b/chromium/ui/accessibility/accessibility_features.cc
@@ -47,4 +47,36 @@ bool IsAccessibilityTreeForViewsEnabled() {
::features::kEnableAccessibilityTreeForViews);
}
+const base::Feature kAccessibilityFocusHighlight{
+ "AccessibilityFocusHighlight", base::FEATURE_DISABLED_BY_DEFAULT};
+
+bool IsAccessibilityFocusHighlightEnabled() {
+ return base::FeatureList::IsEnabled(::features::kAccessibilityFocusHighlight);
+}
+
+#if defined(OS_WIN)
+const base::Feature kIChromeAccessible{"IChromeAccessible",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
+bool IsIChromeAccessibleEnabled() {
+ return base::FeatureList::IsEnabled(::features::kIChromeAccessible);
+}
+#endif // defined(OS_WIN)
+
+#if defined(OS_CHROMEOS)
+const base::Feature kAccessibilityCursorColor{
+ "AccessibilityCursorColor", base::FEATURE_DISABLED_BY_DEFAULT};
+
+bool IsAccessibilityCursorColorEnabled() {
+ return base::FeatureList::IsEnabled(::features::kAccessibilityCursorColor);
+}
+#endif // defined(OS_CHROMEOS)
+
+const base::Feature kAugmentExistingImageLabels{
+ "AugmentExistingImageLabels", base::FEATURE_DISABLED_BY_DEFAULT};
+
+bool IsAugmentExistingImageLabelsEnabled() {
+ return base::FeatureList::IsEnabled(::features::kAugmentExistingImageLabels);
+}
+
} // namespace features
diff --git a/chromium/ui/accessibility/accessibility_features.h b/chromium/ui/accessibility/accessibility_features.h
index b7fa71ec600..743a32c5bc7 100644
--- a/chromium/ui/accessibility/accessibility_features.h
+++ b/chromium/ui/accessibility/accessibility_features.h
@@ -8,35 +8,67 @@
#include "base/feature_list.h"
#include "build/build_config.h"
-#include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/ax_base_export.h"
namespace features {
-AX_EXPORT extern const base::Feature kEnableAccessibilityExposeARIAAnnotations;
+AX_BASE_EXPORT extern const base::Feature
+ kEnableAccessibilityExposeARIAAnnotations;
// Returns true if ARIA annotations should be exposed to the browser AX Tree.
-AX_EXPORT bool IsAccessibilityExposeARIAAnnotationsEnabled();
+AX_BASE_EXPORT bool IsAccessibilityExposeARIAAnnotationsEnabled();
-AX_EXPORT extern const base::Feature kEnableAccessibilityExposeDisplayNone;
+AX_BASE_EXPORT extern const base::Feature kEnableAccessibilityExposeDisplayNone;
// Returns true if "display: none" nodes should be exposed to the
// browser process AXTree.
-AX_EXPORT bool IsAccessibilityExposeDisplayNoneEnabled();
+AX_BASE_EXPORT bool IsAccessibilityExposeDisplayNoneEnabled();
-AX_EXPORT extern const base::Feature kEnableAccessibilityExposeHTMLElement;
+AX_BASE_EXPORT extern const base::Feature kEnableAccessibilityExposeHTMLElement;
// Returns true if the <html> element should be exposed to the
// browser process AXTree (as an ignored node).
-AX_EXPORT bool IsAccessibilityExposeHTMLElementEnabled();
+AX_BASE_EXPORT bool IsAccessibilityExposeHTMLElementEnabled();
// Serializes accessibility information from the Views tree and deserializes it
// into an AXTree in the browser process.
-AX_EXPORT extern const base::Feature kEnableAccessibilityTreeForViews;
+AX_BASE_EXPORT extern const base::Feature kEnableAccessibilityTreeForViews;
// Returns true if the Views tree is exposed using an AXTree in the browser
// process. Returns false if the Views tree is exposed to accessibility
// directly.
-AX_EXPORT bool IsAccessibilityTreeForViewsEnabled();
+AX_BASE_EXPORT bool IsAccessibilityTreeForViewsEnabled();
+
+AX_BASE_EXPORT extern const base::Feature kAccessibilityFocusHighlight;
+
+// Returns true if the accessibility focus highlight feature is enabled,
+// which draws a visual highlight around the focused element on the page
+// briefly whenever focus changes.
+AX_BASE_EXPORT bool IsAccessibilityFocusHighlightEnabled();
+
+#if defined(OS_WIN)
+// Enables an experimental Chrome-specific accessibility COM API
+AX_BASE_EXPORT extern const base::Feature kIChromeAccessible;
+
+// Returns true if the IChromeAccessible COM API is enabled.
+AX_BASE_EXPORT bool IsIChromeAccessibleEnabled();
+
+#endif // defined(OS_WIN)
+
+#if defined(OS_CHROMEOS)
+AX_BASE_EXPORT extern const base::Feature kAccessibilityCursorColor;
+
+// Returns true if the accessibility cursor color feature is enabled, letting
+// users pick a custom cursor color.
+AX_BASE_EXPORT bool IsAccessibilityCursorColorEnabled();
+#endif // defined(OS_CHROMEOS)
+
+// Enables Get Image Descriptions to augment existing images labels,
+// rather than only provide descriptions for completely unlabeled images.
+AX_BASE_EXPORT extern const base::Feature kAugmentExistingImageLabels;
+
+// Returns true if augmenting existing image labels is enabled.
+AX_BASE_EXPORT bool IsAugmentExistingImageLabelsEnabled();
} // namespace features
diff --git a/chromium/ui/accessibility/accessibility_switches.h b/chromium/ui/accessibility/accessibility_switches.h
index 3cc38c37f10..2d3b9f2761b 100644
--- a/chromium/ui/accessibility/accessibility_switches.h
+++ b/chromium/ui/accessibility/accessibility_switches.h
@@ -7,41 +7,46 @@
#define UI_ACCESSIBILITY_ACCESSIBILITY_SWITCHES_H_
#include "build/build_config.h"
-#include "ui/accessibility/ax_export.h"
+#include "ui/accessibility/ax_base_export.h"
namespace switches {
-AX_EXPORT extern const char kEnableExperimentalAccessibilityAutoclick[];
-AX_EXPORT extern const char kEnableExperimentalAccessibilityLabelsDebugging[];
-AX_EXPORT extern const char kEnableExperimentalAccessibilityLanguageDetection[];
-AX_EXPORT extern const char
+AX_BASE_EXPORT extern const char kEnableExperimentalAccessibilityAutoclick[];
+AX_BASE_EXPORT extern const char
+ kEnableExperimentalAccessibilityLabelsDebugging[];
+AX_BASE_EXPORT extern const char
+ kEnableExperimentalAccessibilityLanguageDetection[];
+AX_BASE_EXPORT extern const char
kEnableExperimentalAccessibilityLanguageDetectionDynamic[];
-AX_EXPORT extern const char kEnableExperimentalAccessibilitySwitchAccess[];
-AX_EXPORT extern const char kEnableExperimentalAccessibilitySwitchAccessText[];
-AX_EXPORT extern const char
+AX_BASE_EXPORT extern const char kEnableExperimentalAccessibilitySwitchAccess[];
+AX_BASE_EXPORT extern const char
+ kEnableExperimentalAccessibilitySwitchAccessText[];
+AX_BASE_EXPORT extern const char
kEnableExperimentalAccessibilityChromeVoxAnnotations[];
-AX_EXPORT extern const char
+AX_BASE_EXPORT extern const char
kDisableExperimentalAccessibilityChromeVoxLanguageSwitching[];
-AX_EXPORT extern const char
+AX_BASE_EXPORT extern const char
kDisableExperimentalAccessibilityChromeVoxSearchMenus[];
-AX_EXPORT extern const char kEnableExperimentalAccessibilityChromeVoxTutorial[];
+AX_BASE_EXPORT extern const char
+ kEnableExperimentalAccessibilityChromeVoxTutorial[];
// Returns true if experimental accessibility language detection is enabled.
-AX_EXPORT bool IsExperimentalAccessibilityLanguageDetectionEnabled();
+AX_BASE_EXPORT bool IsExperimentalAccessibilityLanguageDetectionEnabled();
// Returns true if experimental accessibility language detection support for
// dynamic content is enabled.
-AX_EXPORT bool IsExperimentalAccessibilityLanguageDetectionDynamicEnabled();
+AX_BASE_EXPORT bool
+IsExperimentalAccessibilityLanguageDetectionDynamicEnabled();
// Returns true if experimental accessibility Switch Access text is enabled.
-AX_EXPORT bool IsExperimentalAccessibilitySwitchAccessTextEnabled();
+AX_BASE_EXPORT bool IsExperimentalAccessibilitySwitchAccessTextEnabled();
#if defined(OS_WIN)
-AX_EXPORT extern const char kEnableExperimentalUIAutomation[];
+AX_BASE_EXPORT extern const char kEnableExperimentalUIAutomation[];
#endif
// Returns true if experimental support for UIAutomation is enabled.
-AX_EXPORT bool IsExperimentalAccessibilityPlatformUIAEnabled();
+AX_BASE_EXPORT bool IsExperimentalAccessibilityPlatformUIAEnabled();
} // namespace switches
diff --git a/chromium/ui/accessibility/ax_enum_util.cc b/chromium/ui/accessibility/ax_enum_util.cc
index 35bbef5afc6..6d592227fc2 100644
--- a/chromium/ui/accessibility/ax_enum_util.cc
+++ b/chromium/ui/accessibility/ax_enum_util.cc
@@ -81,8 +81,6 @@ const char* ToString(ax::mojom::Event event) {
return "menuListValueChanged";
case ax::mojom::Event::kMenuPopupEnd:
return "menuPopupEnd";
- case ax::mojom::Event::kMenuPopupHide:
- return "menuPopupHide";
case ax::mojom::Event::kMenuPopupStart:
return "menuPopupStart";
case ax::mojom::Event::kMenuStart:
@@ -211,8 +209,6 @@ ax::mojom::Event ParseEvent(const char* event) {
return ax::mojom::Event::kMenuListValueChanged;
if (0 == strcmp(event, "menuPopupEnd"))
return ax::mojom::Event::kMenuPopupEnd;
- if (0 == strcmp(event, "menuPopupHide"))
- return ax::mojom::Event::kMenuPopupHide;
if (0 == strcmp(event, "menuPopupStart"))
return ax::mojom::Event::kMenuPopupStart;
if (0 == strcmp(event, "menuStart"))
@@ -1884,6 +1880,8 @@ const char* ToString(ax::mojom::BoolAttribute bool_attribute) {
return "clipsChildren";
case ax::mojom::BoolAttribute::kSelected:
return "selected";
+ case ax::mojom::BoolAttribute::kSelectedFromFocus:
+ return "selectedFromFocus";
case ax::mojom::BoolAttribute::kSupportsTextLocation:
return "supportsTextLocation";
case ax::mojom::BoolAttribute::kIsLineBreakingObject:
@@ -1926,6 +1924,8 @@ ax::mojom::BoolAttribute ParseBoolAttribute(const char* bool_attribute) {
return ax::mojom::BoolAttribute::kClipsChildren;
if (0 == strcmp(bool_attribute, "selected"))
return ax::mojom::BoolAttribute::kSelected;
+ if (0 == strcmp(bool_attribute, "selectedFromFocus"))
+ return ax::mojom::BoolAttribute::kSelectedFromFocus;
if (0 == strcmp(bool_attribute, "supportsTextLocation"))
return ax::mojom::BoolAttribute::kSupportsTextLocation;
if (0 == strcmp(bool_attribute, "isLineBreakingObject"))
diff --git a/chromium/ui/accessibility/ax_enums.mojom b/chromium/ui/accessibility/ax_enums.mojom
index a5c0fc5884f..b1523555d3b 100644
--- a/chromium/ui/accessibility/ax_enums.mojom
+++ b/chromium/ui/accessibility/ax_enums.mojom
@@ -27,7 +27,7 @@ module ax.mojom;
enum Event {
kNone,
- kActiveDescendantChanged, // Web
+ kActiveDescendantChanged,
kAlert,
kAriaAttributeChanged, // Implicit
kAutocorrectionOccured, // Unknown: http://crbug.com/392498
@@ -56,13 +56,12 @@ enum Event {
kLocationChanged, // Web
kMediaStartedPlaying, // Native / Automation
kMediaStoppedPlaying, // Native / Automation
- kMenuEnd, // Native / Win
+ kMenuEnd, // Native / web: menu interaction has ended.
kMenuListItemSelected, // Web
kMenuListValueChanged, // Web
- kMenuPopupEnd, // Native
- kMenuPopupHide, // Native / AuraLinux
- kMenuPopupStart, // Native
- kMenuStart, // Native / Win
+ kMenuPopupEnd, // Native / web: a menu/submenu is hidden/closed.
+ kMenuPopupStart, // Native / web: a menu/submenu is shown/opened.
+ kMenuStart, // Native / web: menu interaction has begun.
kMouseCanceled,
kMouseDragged,
kMouseMoved,
@@ -77,7 +76,7 @@ enum Event {
kSelection, // Native
kSelectionAdd, // Native
kSelectionRemove, // Native
- kShow, // Remove: http://crbug.com/392502
+ kShow, // Native / Automation
kStateChanged, // Native / Automation
kTextChanged,
kWindowActivated, // Native
@@ -734,6 +733,9 @@ enum BoolAttribute {
// Indicates whether this node is selected or unselected.
kSelected,
+ // Indicates whether this node is selected due to selection follows focus.
+ kSelectedFromFocus,
+
// Indicates whether this node supports text location.
kSupportsTextLocation,
@@ -984,14 +986,14 @@ enum SortDirection {
enum NameFrom {
kNone,
kUninitialized,
- kAttribute,
+ kAttribute, // E.g. aria-label.
kAttributeExplicitlyEmpty,
- kCaption,
+ kCaption, // E.g. in the case of a table, from a caption element.
kContents,
- kPlaceholder,
- kRelatedElement,
- kTitle,
- kValue,
+ kPlaceholder, // E.g. from an HTML placeholder attribute on a text field.
+ kRelatedElement, // E.g. from a figcaption Element in a figure.
+ kTitle, // E.g. <input type="text" title="title">.
+ kValue, // E.g. <input type="button" value="Button's name">.
};
enum DescriptionFrom {
diff --git a/chromium/ui/accessibility/ax_event.cc b/chromium/ui/accessibility/ax_event.cc
index 34327f466f1..ecb80d908e4 100644
--- a/chromium/ui/accessibility/ax_event.cc
+++ b/chromium/ui/accessibility/ax_event.cc
@@ -29,7 +29,7 @@ AXEvent::AXEvent(const AXEvent& event) = default;
AXEvent& AXEvent::operator=(const AXEvent& event) = default;
std::string AXEvent::ToString() const {
- std::string result = "AXEvent";
+ std::string result = "AXEvent ";
result += ui::ToString(event_type);
result += " on node id=" + base::NumberToString(id);
diff --git a/chromium/ui/accessibility/ax_event_generator.cc b/chromium/ui/accessibility/ax_event_generator.cc
index 472dd5b1579..7561e537be6 100644
--- a/chromium/ui/accessibility/ax_event_generator.cc
+++ b/chromium/ui/accessibility/ax_event_generator.cc
@@ -49,9 +49,13 @@ void RemoveEvent(std::set<AXEventGenerator::EventParams>* node_events,
} // namespace
-AXEventGenerator::EventParams::EventParams(Event event,
- ax::mojom::EventFrom event_from)
- : event(event), event_from(event_from) {}
+AXEventGenerator::EventParams::EventParams(
+ Event event,
+ ax::mojom::EventFrom event_from,
+ const std::vector<AXEventIntent>& event_intents)
+ : event(event), event_from(event_from), event_intents(event_intents) {}
+
+AXEventGenerator::EventParams::~EventParams() = default;
AXEventGenerator::TargetedEvent::TargetedEvent(AXNode* node,
const EventParams& event_params)
@@ -138,7 +142,8 @@ void AXEventGenerator::AddEvent(AXNode* node, AXEventGenerator::Event event) {
return;
std::set<EventParams>& node_events = tree_events_[node];
- node_events.emplace(event, ax::mojom::EventFrom::kNone);
+ node_events.emplace(event, ax::mojom::EventFrom::kNone,
+ tree_->event_intents());
}
void AXEventGenerator::OnNodeDataChanged(AXTree* tree,
@@ -154,7 +159,8 @@ void AXEventGenerator::OnNodeDataChanged(AXTree* tree,
new_node_data.role != ax::mojom::Role::kStaticText) {
AXNode* node = tree_->GetFromId(new_node_data.id);
tree_events_[node].emplace(Event::CHILDREN_CHANGED,
- ax::mojom::EventFrom::kNone);
+ ax::mojom::EventFrom::kNone,
+ tree_->event_intents());
}
}
@@ -543,7 +549,7 @@ void AXEventGenerator::FireLiveRegionEvents(AXNode* node) {
.GetStringAttribute(ax::mojom::StringAttribute::kName)
.empty())
AddEvent(node, Event::LIVE_REGION_NODE_CHANGED);
- // Fire LIVE_REGION_CHANGED on the root of the live region.
+ // Fire LIVE_REGION_NODE_CHANGED on the root of the live region.
AddEvent(live_root, Event::LIVE_REGION_CHANGED);
}
}
@@ -610,6 +616,9 @@ 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;
+
const AXNodeData& data = node->data();
return data.relative_bounds.bounds.width() ||
data.relative_bounds.bounds.height();
diff --git a/chromium/ui/accessibility/ax_event_generator.h b/chromium/ui/accessibility/ax_event_generator.h
index 61d860b1e64..47c64ef7b80 100644
--- a/chromium/ui/accessibility/ax_event_generator.h
+++ b/chromium/ui/accessibility/ax_event_generator.h
@@ -11,6 +11,7 @@
#include <vector>
#include "base/scoped_observer.h"
+#include "ui/accessibility/ax_event_intent.h"
#include "ui/accessibility/ax_export.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_observer.h"
@@ -90,9 +91,13 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
};
struct EventParams {
- EventParams(Event event, ax::mojom::EventFrom event_from);
+ EventParams(Event event,
+ ax::mojom::EventFrom event_from,
+ const std::vector<AXEventIntent>& event_intents);
+ ~EventParams();
Event event;
ax::mojom::EventFrom event_from;
+ std::vector<AXEventIntent> event_intents;
bool operator==(const EventParams& rhs);
bool operator<(const EventParams& rhs) const;
@@ -165,6 +170,10 @@ 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;
+ }
+
protected:
// AXTreeObserver overrides.
void OnNodeDataChanged(AXTree* tree,
@@ -236,6 +245,8 @@ class AX_EXPORT AXEventGenerator : public AXTreeObserver {
// OnAtomicUpdateFinished. List of nodes whose active descendant changed.
std::vector<AXNode*> active_descendant_changed_;
+ bool always_fire_load_complete_ = false;
+
// Please make sure that this ScopedObserver is always declared last in order
// to prevent any use-after-free.
ScopedObserver<AXTree, AXTreeObserver> tree_event_observer_{this};
diff --git a/chromium/ui/accessibility/ax_mode.cc b/chromium/ui/accessibility/ax_mode.cc
index 55d0fc130db..7919464eb0a 100644
--- a/chromium/ui/accessibility/ax_mode.cc
+++ b/chromium/ui/accessibility/ax_mode.cc
@@ -41,6 +41,9 @@ std::string AXMode::ToString() const {
case AXMode::kLabelImages:
flag_name = "kLabelImages";
break;
+ case AXMode::kPDF:
+ flag_name = "kPDF";
+ break;
}
DCHECK(flag_name);
diff --git a/chromium/ui/accessibility/ax_mode.h b/chromium/ui/accessibility/ax_mode.h
index 8e0b2dc5fc0..2536ad56236 100644
--- a/chromium/ui/accessibility/ax_mode.h
+++ b/chromium/ui/accessibility/ax_mode.h
@@ -10,7 +10,6 @@
#include <ostream>
#include <string>
-#include "base/logging.h"
#include "ui/accessibility/ax_export.h"
namespace ui {
@@ -60,10 +59,14 @@ class AX_EXPORT AXMode {
// The accessibility tree will contain automatic image annotations.
static constexpr uint32_t kLabelImages = 1 << 5;
+ // The accessibility tree will contain enough information to export
+ // an accessible PDF.
+ static constexpr uint32_t kPDF = 1 << 6;
+
// 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.
- static constexpr uint32_t kLastModeFlag = 1 << 5;
+ static constexpr uint32_t kLastModeFlag = 1 << 6;
constexpr AXMode() : flags_(0) {}
constexpr AXMode(uint32_t flags) : flags_(flags) {}
diff --git a/chromium/ui/accessibility/ax_node.cc b/chromium/ui/accessibility/ax_node.cc
index b81f1491c31..3c7b06ecd91 100644
--- a/chromium/ui/accessibility/ax_node.cc
+++ b/chromium/ui/accessibility/ax_node.cc
@@ -10,6 +10,7 @@
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_language_detection.h"
#include "ui/accessibility/ax_role_properties.h"
@@ -36,6 +37,7 @@ AXNode::AXNode(AXNode::OwnerTree* tree,
AXNode::~AXNode() = default;
size_t AXNode::GetUnignoredChildCount() const {
+ // TODO(nektar): Should DCHECK if the node is not ignored.
DCHECK(!tree_->GetTreeUpdateInProgressState());
return unignored_child_count_;
}
@@ -320,7 +322,7 @@ AXNode::UnignoredChildIterator AXNode::UnignoredChildrenEnd() const {
// The first (direct) child, ignored or unignored.
AXNode* AXNode::GetFirstChild() const {
- if (children().size() == 0)
+ if (children().empty())
return nullptr;
return children()[0];
}
@@ -354,9 +356,13 @@ AXNode* AXNode::GetNextSibling() const {
}
bool AXNode::IsText() const {
- return data().role == ax::mojom::Role::kStaticText ||
- data().role == ax::mojom::Role::kLineBreak ||
- data().role == ax::mojom::Role::kInlineTextBox;
+ // In Legacy Layout, a list marker has no children and is thus represented on
+ // all platforms as a leaf node that exposes the marker itself, i.e., it forms
+ // part of the AX tree's text representation. In contrast, in Layout NG, a
+ // list marker has a static text child.
+ if (data().role == ax::mojom::Role::kListMarker)
+ return !children().size();
+ return ui::IsText(data().role);
}
bool AXNode::IsLineBreak() const {
@@ -477,6 +483,61 @@ void AXNode::ClearLanguageInfo() {
language_info_.reset();
}
+std::string AXNode::GetInnerText() const {
+ // If a text field has no descendants, then we compute its inner text from its
+ // value or its placeholder. Otherwise we prefer to look at its descendant
+ // text nodes because Blink doesn't always add all trailing white space to the
+ // value attribute.
+ if (data().IsTextField() && children().empty()) {
+ std::string value =
+ data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
+ // If the value is empty, then there might be some placeholder text in the
+ // text field, or any other name that is derived from visible contents, even
+ // if the text field has no children.
+ if (!value.empty())
+ return value;
+ }
+
+ // Ordinarily, plain text fields are leaves. We need to exclude them from the
+ // set of leaf nodes when they expose any descendants if we want to compute
+ // their inner text from their descendant text nodes.
+ if (IsLeaf() && !(data().IsTextField() && !children().empty())) {
+ switch (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.
+ case ax::mojom::NameFrom::kAttribute:
+ // The node's accessible name is explicitly empty.
+ case ax::mojom::NameFrom::kAttributeExplicitlyEmpty:
+ // The accessible name does not represent the entirety of the node's inner
+ // text, e.g. a table's caption or a figure's figcaption.
+ case ax::mojom::NameFrom::kCaption:
+ case ax::mojom::NameFrom::kRelatedElement:
+ // The accessible name is not displayed directly inside the node but is
+ // visible via e.g. a tooltip.
+ case ax::mojom::NameFrom::kTitle:
+ return std::string();
+
+ case ax::mojom::NameFrom::kContents:
+ // The placeholder text is initially displayed inside the text field and
+ // takes the place of its value.
+ case ax::mojom::NameFrom::kPlaceholder:
+ // The value attribute takes the place of the node's inner text, e.g. the
+ // value of a submit button is displayed inside the button itself.
+ case ax::mojom::NameFrom::kValue:
+ return data().GetStringAttribute(ax::mojom::StringAttribute::kName);
+ }
+ }
+
+ std::string inner_text;
+ for (auto it = UnignoredChildrenBegin(); it != UnignoredChildrenEnd(); ++it) {
+ inner_text += it->GetInnerText();
+ }
+ return inner_text;
+}
+
std::string AXNode::GetLanguage() const {
// Walk up tree considering both detected and author declared languages.
for (const AXNode* cur = this; cur; cur = cur->parent()) {
@@ -584,44 +645,52 @@ AXNode* AXNode::GetTableCellFromCoords(int row_index, int col_index) const {
table_info->cell_ids[size_t{row_index}][size_t{col_index}]);
}
-void AXNode::GetTableColHeaderNodeIds(
- int col_index,
- std::vector<int32_t>* col_header_ids) const {
- DCHECK(col_header_ids);
+std::vector<AXNode::AXID> AXNode::GetTableColHeaderNodeIds() const {
+ const AXTableInfo* table_info = GetAncestorTableInfo();
+ if (!table_info)
+ return std::vector<AXNode::AXID>();
+
+ std::vector<AXNode::AXID> col_header_ids;
+ // Flatten and add column header ids of each column to |col_header_ids|.
+ for (std::vector<AXNode::AXID> col_headers_at_index :
+ table_info->col_headers) {
+ col_header_ids.insert(col_header_ids.end(), col_headers_at_index.begin(),
+ col_headers_at_index.end());
+ }
+
+ return col_header_ids;
+}
+
+std::vector<AXNode::AXID> AXNode::GetTableColHeaderNodeIds(
+ int col_index) const {
const AXTableInfo* table_info = GetAncestorTableInfo();
if (!table_info)
- return;
+ return std::vector<AXNode::AXID>();
if (col_index < 0 || size_t{col_index} >= table_info->col_count)
- return;
+ return std::vector<AXNode::AXID>();
- for (size_t i = 0; i < table_info->col_headers[size_t{col_index}].size(); i++)
- col_header_ids->push_back(table_info->col_headers[size_t{col_index}][i]);
+ return std::vector<AXNode::AXID>(table_info->col_headers[size_t{col_index}]);
}
-void AXNode::GetTableRowHeaderNodeIds(
- int row_index,
- std::vector<int32_t>* row_header_ids) const {
- DCHECK(row_header_ids);
+std::vector<AXNode::AXID> AXNode::GetTableRowHeaderNodeIds(
+ int row_index) const {
const AXTableInfo* table_info = GetAncestorTableInfo();
if (!table_info)
- return;
+ return std::vector<AXNode::AXID>();
if (row_index < 0 || size_t{row_index} >= table_info->row_count)
- return;
+ return std::vector<AXNode::AXID>();
- for (size_t i = 0; i < table_info->row_headers[size_t{row_index}].size(); i++)
- row_header_ids->push_back(table_info->row_headers[size_t{row_index}][i]);
+ return std::vector<AXNode::AXID>(table_info->row_headers[size_t{row_index}]);
}
-void AXNode::GetTableUniqueCellIds(std::vector<int32_t>* cell_ids) const {
- DCHECK(cell_ids);
+std::vector<AXNode::AXID> AXNode::GetTableUniqueCellIds() const {
const AXTableInfo* table_info = GetAncestorTableInfo();
if (!table_info)
- return;
+ return std::vector<AXNode::AXID>();
- cell_ids->assign(table_info->unique_cell_ids.begin(),
- table_info->unique_cell_ids.end());
+ return std::vector<AXNode::AXID>(table_info->unique_cell_ids);
}
const std::vector<AXNode*>* AXNode::GetExtraMacNodes() const {
@@ -792,47 +861,39 @@ base::Optional<int> AXNode::GetTableCellAriaRowIndex() const {
return int{table_info->cell_data_vector[*index].aria_row_index};
}
-void AXNode::GetTableCellColHeaderNodeIds(
- std::vector<int32_t>* col_header_ids) const {
- DCHECK(col_header_ids);
+std::vector<AXNode::AXID> AXNode::GetTableCellColHeaderNodeIds() const {
const AXTableInfo* table_info = GetAncestorTableInfo();
if (!table_info || table_info->col_count <= 0)
- return;
+ return std::vector<AXNode::AXID>();
// If this node is not a cell, then return the headers for the first column.
int col_index = GetTableCellColIndex().value_or(0);
- const auto& col = table_info->col_headers[col_index];
- for (int header : col)
- col_header_ids->push_back(header);
+
+ return std::vector<AXNode::AXID>(table_info->col_headers[col_index]);
}
void AXNode::GetTableCellColHeaders(std::vector<AXNode*>* col_headers) const {
DCHECK(col_headers);
- std::vector<int32_t> col_header_ids;
- GetTableCellColHeaderNodeIds(&col_header_ids);
+ std::vector<int32_t> col_header_ids = GetTableCellColHeaderNodeIds();
IdVectorToNodeVector(col_header_ids, col_headers);
}
-void AXNode::GetTableCellRowHeaderNodeIds(
- std::vector<int32_t>* row_header_ids) const {
- DCHECK(row_header_ids);
+std::vector<AXNode::AXID> AXNode::GetTableCellRowHeaderNodeIds() const {
const AXTableInfo* table_info = GetAncestorTableInfo();
if (!table_info || table_info->row_count <= 0)
- return;
+ return std::vector<AXNode::AXID>();
// If this node is not a cell, then return the headers for the first row.
int row_index = GetTableCellRowIndex().value_or(0);
- const auto& row = table_info->row_headers[row_index];
- for (int header : row)
- row_header_ids->push_back(header);
+
+ return std::vector<AXNode::AXID>(table_info->row_headers[row_index]);
}
void AXNode::GetTableCellRowHeaders(std::vector<AXNode*>* row_headers) const {
DCHECK(row_headers);
- std::vector<int32_t> row_header_ids;
- GetTableCellRowHeaderNodeIds(&row_header_ids);
+ std::vector<int32_t> row_header_ids = GetTableCellRowHeaderNodeIds();
IdVectorToNodeVector(row_header_ids, row_headers);
}
@@ -902,51 +963,14 @@ bool AXNode::IsOrderedSet() const {
return ui::IsSetLike(data().role);
}
-// pos_in_set and set_size related functions.
-// Uses AXTree's cache to calculate node's pos_in_set.
+// Uses AXTree's cache to calculate node's PosInSet.
base::Optional<int> AXNode::GetPosInSet() {
- // Only allow this to be called on nodes that can hold pos_in_set values,
- // which are defined in the ARIA spec.
- if (!IsOrderedSetItem() || IsIgnored())
- return base::nullopt;
-
- const AXNode* ordered_set = GetOrderedSet();
- if (!ordered_set) {
- return base::nullopt;
- }
-
- // If tree is being updated, return no value.
- if (tree()->GetTreeUpdateInProgressState())
- return base::nullopt;
-
- // See AXTree::GetPosInSet
- return tree_->GetPosInSet(*this, ordered_set);
+ return tree_->GetPosInSet(*this);
}
-// Uses AXTree's cache to calculate node's set_size.
+// Uses AXTree's cache to calculate node's SetSize.
base::Optional<int> AXNode::GetSetSize() {
- // Only allow this to be called on nodes that can hold set_size values, which
- // are defined in the ARIA spec.
- if ((!IsOrderedSetItem() && !IsOrderedSet()) || IsIgnored())
- return base::nullopt;
-
- // If node is item-like, find its outerlying ordered set. Otherwise,
- // this node is the ordered set.
- const AXNode* ordered_set = this;
- if (IsItemLike(data().role))
- ordered_set = GetOrderedSet();
- if (!ordered_set)
- return base::nullopt;
-
- // If tree is being updated, return no value.
- if (tree()->GetTreeUpdateInProgressState())
- return base::nullopt;
-
- // See AXTree::GetSetSize
- int32_t set_size = tree_->GetSetSize(*this, ordered_set);
- if (set_size < 0)
- return base::nullopt;
- return set_size;
+ return tree_->GetSetSize(*this);
}
// Returns true if the role of ordered set matches the role of item.
@@ -1001,7 +1025,8 @@ bool AXNode::SetRoleMatchesItemRole(const AXNode* ordered_set) const {
}
bool AXNode::IsIgnoredContainerForOrderedSet() const {
- return IsIgnored() || data().role == ax::mojom::Role::kListItem ||
+ return IsIgnored() || IsEmbeddedGroup() ||
+ data().role == ax::mojom::Role::kListItem ||
data().role == ax::mojom::Role::kGenericContainer ||
data().role == ax::mojom::Role::kUnknown;
}
@@ -1020,17 +1045,16 @@ int AXNode::UpdateUnignoredCachedValuesRecursive(int startIndex) {
return count;
}
-// Finds ordered set that immediately contains node.
+// Finds ordered set that contains node.
// Is not required for set's role to match node's role.
AXNode* AXNode::GetOrderedSet() const {
AXNode* result = parent();
// Continue walking up while parent is invalid, ignored, a generic container,
- // or unknown.
- while (result && (result->IsIgnored() ||
- result->data().role == ax::mojom::Role::kGenericContainer ||
- result->data().role == ax::mojom::Role::kUnknown)) {
+ // unknown, or embedded group.
+ while (result && result->IsIgnoredContainerForOrderedSet()) {
result = result->parent();
}
+
return result;
}
@@ -1069,6 +1093,70 @@ bool AXNode::IsIgnored() const {
return data().IsIgnored();
}
+bool AXNode::IsChildOfLeaf() const {
+ const AXNode* ancestor = GetUnignoredParent();
+ while (ancestor) {
+ if (ancestor->IsLeaf())
+ return true;
+ ancestor = ancestor->GetUnignoredParent();
+ }
+ return false;
+}
+
+bool AXNode::IsLeaf() const {
+ return !GetUnignoredChildCount() || IsLeafIncludingIgnored();
+}
+
+bool AXNode::IsLeafIncludingIgnored() const {
+ if (children().empty())
+ return true;
+
+#if defined(OS_WIN)
+ // On Windows, we want to hide the subtree of a collapsed <select> element.
+ // Otherwise, ATs are always going to announce its options whether it's
+ // collapsed or expanded. In the AXTree, this element corresponds to a node
+ // with role ax::mojom::Role::kPopUpButton that is the parent of a node with
+ // role ax::mojom::Role::kMenuListPopup.
+ if (IsCollapsedMenuListPopUpButton())
+ return true;
+#endif // defined(OS_WIN)
+
+ // These types of objects may have children that we use as internal
+ // implementation details, but we want to expose them as leaves to platform
+ // accessibility APIs because screen readers might be confused if they find
+ // any children.
+ if (data().IsPlainTextField() || IsText())
+ return true;
+
+ // Roles whose children are only presentational according to the ARIA and
+ // HTML5 Specs should be hidden from screen readers.
+ switch (data().role) {
+ // According to the ARIA and Core-AAM specs:
+ // https://w3c.github.io/aria/#button,
+ // https://www.w3.org/TR/core-aam-1.1/#exclude_elements
+ // buttons' children are presentational only and should be hidden from
+ // screen readers. However, we cannot enforce the leafiness of buttons
+ // because they may contain many rich, interactive descendants such as a day
+ // in a calendar, and screen readers will need to interact with these
+ // contents. See https://crbug.com/689204.
+ // So we decided to not enforce the leafiness of buttons and expose all
+ // children.
+ case ax::mojom::Role::kButton:
+ return false;
+ case ax::mojom::Role::kDocCover:
+ case ax::mojom::Role::kGraphicsSymbol:
+ case ax::mojom::Role::kImage:
+ case ax::mojom::Role::kMeter:
+ case ax::mojom::Role::kScrollBar:
+ case ax::mojom::Role::kSlider:
+ case ax::mojom::Role::kSplitter:
+ case ax::mojom::Role::kProgressIndicator:
+ return true;
+ default:
+ return false;
+ }
+}
+
bool AXNode::IsInListMarker() const {
if (data().role == ax::mojom::Role::kListMarker)
return true;
@@ -1125,4 +1213,24 @@ AXNode* AXNode::GetCollapsedMenuListPopUpButtonAncestor() const {
return node->IsCollapsedMenuListPopUpButton() ? node : nullptr;
}
+bool AXNode::IsEmbeddedGroup() const {
+ if (data().role != ax::mojom::Role::kGroup || !parent())
+ return false;
+
+ return ui::IsSetLike(parent()->data().role);
+}
+
+AXNode* AXNode::GetTextFieldAncestor() const {
+ AXNode* parent = GetUnignoredParent();
+
+ while (parent && parent->data().HasState(ax::mojom::State::kEditable)) {
+ if (parent->data().IsPlainTextField() || parent->data().IsRichTextField())
+ return parent;
+
+ parent = parent->GetUnignoredParent();
+ }
+
+ return nullptr;
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/ax_node.h b/chromium/ui/accessibility/ax_node.h
index e19d1bc43b7..b9fb97ff04e 100644
--- a/chromium/ui/accessibility/ax_node.h
+++ b/chromium/ui/accessibility/ax_node.h
@@ -56,10 +56,9 @@ class AX_EXPORT AXNode final {
// See AXTree::GetFromId.
virtual AXNode* GetFromId(int32_t id) const = 0;
- virtual int32_t GetPosInSet(const AXNode& node,
- const AXNode* ordered_set) = 0;
- virtual int32_t GetSetSize(const AXNode& node,
- const AXNode* ordered_set) = 0;
+ virtual base::Optional<int> GetPosInSet(const AXNode& node) = 0;
+ virtual base::Optional<int> GetSetSize(const AXNode& node) = 0;
+
virtual Selection GetUnignoredSelection() const = 0;
virtual bool GetTreeUpdateInProgressState() const = 0;
virtual bool HasPaginationSupport() const = 0;
@@ -101,7 +100,7 @@ class AX_EXPORT AXNode final {
// Accessors.
OwnerTree* tree() const { return tree_; }
- int32_t id() const { return data_.id; }
+ AXID id() const { return data_.id; }
AXNode* parent() const { return parent_; }
const AXNodeData& data() const { return data_; }
const std::vector<AXNode*>& children() const { return children_; }
@@ -296,6 +295,14 @@ class AX_EXPORT AXNode final {
base::string16 GetInheritedString16Attribute(
ax::mojom::StringAttribute attribute) const;
+ // Returns the text of this node and all descendant nodes; including text
+ // found in embedded objects.
+ //
+ // Only text displayed on screen is included. Text from ARIA and HTML
+ // attributes that is either not displayed on screen, or outside this node, is
+ // not returned.
+ std::string GetInnerText() const;
+
// Return a string representing the language code.
//
// This will consider the language declared in the DOM, and may eventually
@@ -335,11 +342,13 @@ class AX_EXPORT AXNode final {
AXNode* GetTableCaption() const;
AXNode* GetTableCellFromIndex(int index) const;
AXNode* GetTableCellFromCoords(int row_index, int col_index) const;
- void GetTableColHeaderNodeIds(int col_index,
- std::vector<int32_t>* col_header_ids) const;
- void GetTableRowHeaderNodeIds(int row_index,
- std::vector<int32_t>* row_header_ids) const;
- void GetTableUniqueCellIds(std::vector<int32_t>* row_header_ids) const;
+ // Get all the column header node ids of the table.
+ std::vector<AXNode::AXID> GetTableColHeaderNodeIds() const;
+ // Get the column header node ids associated with |col_index|.
+ std::vector<AXNode::AXID> GetTableColHeaderNodeIds(int col_index) const;
+ // Get the row header node ids associated with |row_index|.
+ std::vector<AXNode::AXID> GetTableRowHeaderNodeIds(int row_index) const;
+ std::vector<AXNode::AXID> GetTableUniqueCellIds() const;
// Extra computed nodes for the accessibility tree for macOS:
// one column node for each table column, followed by one
// table header container node, or nullptr if not applicable.
@@ -366,8 +375,8 @@ class AX_EXPORT AXNode final {
base::Optional<int> GetTableCellRowSpan() const;
base::Optional<int> GetTableCellAriaColIndex() const;
base::Optional<int> GetTableCellAriaRowIndex() const;
- void GetTableCellColHeaderNodeIds(std::vector<int32_t>* col_header_ids) const;
- void GetTableCellRowHeaderNodeIds(std::vector<int32_t>* row_header_ids) const;
+ std::vector<AXNode::AXID> GetTableCellColHeaderNodeIds() const;
+ std::vector<AXNode::AXID> GetTableCellRowHeaderNodeIds() const;
void GetTableCellColHeaders(std::vector<AXNode*>* col_headers) const;
void GetTableCellRowHeaders(std::vector<AXNode*>* row_headers) const;
@@ -390,10 +399,39 @@ class AX_EXPORT AXNode final {
// Destroy the language info for this node.
void ClearLanguageInfo();
+ // Returns true if node is a group and is a direct descendant of a set-like
+ // element.
+ bool IsEmbeddedGroup() const;
+
// Returns true if node has ignored state or ignored role.
bool IsIgnored() const;
- // Returns true if this current node is a list marker or if it's a descendant
+ // Returns true if an ancestor of this node (not including itself) is a
+ // leaf node, meaning that this node is not actually exposed to any
+ // platform's accessibility layer.
+ bool IsChildOfLeaf() const;
+
+ // Returns true if this is a leaf node, meaning all its
+ // children should not be exposed to any platform's native accessibility
+ // layer.
+ //
+ // The definition of a leaf includes nodes with children that are exclusively
+ // an internal renderer implementation, such as the children of an HTML native
+ // text field, as well as nodes with presentational children according to the
+ // ARIA and HTML5 Specs.
+ //
+ // A leaf node should never have children that are focusable or
+ // that might send notifications.
+ bool IsLeaf() const;
+
+ // Returns true if this is a leaf node, (see "IsLeaf"), or if all of the
+ // node's children are ignored.
+ //
+ // TODO(nektar): There are no performance advantages in keeping this method
+ // since unignored child count is cached. Please remove.
+ bool IsLeafIncludingIgnored() const;
+
+ // Returns true if this node is a list marker or if it's a descendant
// of a list marker node. Returns false otherwise.
bool IsInListMarker() const;
@@ -406,6 +444,12 @@ class AX_EXPORT AXNode final {
// collapsed.
AXNode* GetCollapsedMenuListPopUpButtonAncestor() const;
+ // Returns the text field ancestor of this current node if any.
+ AXNode* GetTextFieldAncestor() const;
+
+ // Finds and returns a pointer to ordered set containing node.
+ AXNode* GetOrderedSet() const;
+
private:
// Computes the text offset where each line starts by traversing all child
// leaf nodes.
@@ -419,9 +463,6 @@ class AX_EXPORT AXNode final {
AXNode* ComputeLastUnignoredChildRecursive() const;
AXNode* ComputeFirstUnignoredChildRecursive() const;
- // Finds and returns a pointer to ordered set containing node.
- AXNode* GetOrderedSet() const;
-
OwnerTree* const tree_; // Owns this.
size_t index_in_parent_;
size_t unignored_index_in_parent_;
@@ -430,6 +471,7 @@ class AX_EXPORT AXNode final {
std::vector<AXNode*> children_;
AXNodeData data_;
+ // Stores the detected language computed from the node's text.
std::unique_ptr<AXLanguageInfo> language_info_;
};
diff --git a/chromium/ui/accessibility/ax_node_data.cc b/chromium/ui/accessibility/ax_node_data.cc
index 1780711a1ae..789e9e01feb 100644
--- a/chromium/ui/accessibility/ax_node_data.cc
+++ b/chromium/ui/accessibility/ax_node_data.cc
@@ -570,17 +570,42 @@ AXNodeTextStyles AXNodeData::GetTextStyles() 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.";
+
auto iter = std::find_if(string_attributes.begin(), string_attributes.end(),
[](const auto& string_attribute) {
return string_attribute.first ==
ax::mojom::StringAttribute::kName;
});
+
if (iter == string_attributes.end()) {
string_attributes.push_back(
std::make_pair(ax::mojom::StringAttribute::kName, name));
} else {
iter->second = name;
}
+
+ if (HasIntAttribute(ax::mojom::IntAttribute::kNameFrom))
+ return;
+ // Since this method is mostly used by tests which don't always set the
+ // "NameFrom" attribute, we need to set it here to the most likely value if
+ // not set, otherwise code that tries to calculate the node's inner text, its
+ // hypertext, or even its value, might not know whether to include the name in
+ // the result or not.
+ //
+ // For example, if there is a text field, but it is empty, i.e. it has no
+ // value, its value could be its name if "NameFrom" is set to "kPlaceholder"
+ // or to "kContents" but not if it's set to "kAttribute". Similarly, if there
+ // is a button without any unignored children, it's name can only be
+ // equivalent to its inner text if "NameFrom" is set to "kContents" or to
+ // "kValue", but not if it is set to "kAttribute".
+ if (IsText(role)) {
+ SetNameFrom(ax::mojom::NameFrom::kContents);
+ } else {
+ SetNameFrom(ax::mojom::NameFrom::kAttribute);
+ }
}
void AXNodeData::SetName(const base::string16& name) {
@@ -729,7 +754,7 @@ ax::mojom::CheckedState AXNodeData::GetCheckedState() const {
}
void AXNodeData::SetCheckedState(ax::mojom::CheckedState checked_state) {
- if (HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
+ if (HasCheckedState())
RemoveIntAttribute(ax::mojom::IntAttribute::kCheckedState);
if (checked_state != ax::mojom::CheckedState::kNone) {
AddIntAttribute(ax::mojom::IntAttribute::kCheckedState,
@@ -737,6 +762,10 @@ void AXNodeData::SetCheckedState(ax::mojom::CheckedState checked_state) {
}
}
+bool AXNodeData::HasCheckedState() const {
+ return HasIntAttribute(ax::mojom::IntAttribute::kCheckedState);
+}
+
ax::mojom::DefaultActionVerb AXNodeData::GetDefaultActionVerb() const {
return static_cast<ax::mojom::DefaultActionVerb>(
GetIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb));
@@ -1001,21 +1030,6 @@ bool AXNodeData::SupportsExpandCollapse() const {
return ui::SupportsExpandCollapse(role);
}
-bool AXNodeData::IsContainedInActiveLiveRegion() const {
- if (!HasStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus))
- return false;
-
- if (base::CompareCaseInsensitiveASCII(
- GetStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus),
- "off") == 0)
- return false;
-
- if (GetBoolAttribute(ax::mojom::BoolAttribute::kContainerLiveBusy))
- return false;
-
- return true;
-}
-
std::string AXNodeData::ToString() const {
std::string result;
@@ -1539,6 +1553,9 @@ std::string AXNodeData::ToString() const {
case ax::mojom::BoolAttribute::kSelected:
result += " selected=" + value;
break;
+ case ax::mojom::BoolAttribute::kSelectedFromFocus:
+ result += " selected_from_focus=" + value;
+ break;
case ax::mojom::BoolAttribute::kSupportsTextLocation:
result += " supports_text_location=" + value;
break;
diff --git a/chromium/ui/accessibility/ax_node_data.h b/chromium/ui/accessibility/ax_node_data.h
index e03e836b912..c2ac3b5ad75 100644
--- a/chromium/ui/accessibility/ax_node_data.h
+++ b/chromium/ui/accessibility/ax_node_data.h
@@ -141,7 +141,8 @@ struct AX_BASE_EXPORT AXNodeData {
// Convenience functions.
//
- // Adds the name attribute or replaces it if already present.
+ // Adds the name attribute or replaces it if already present. Also sets the
+ // NameFrom attribute if not already set.
void SetName(const std::string& name);
void SetName(const base::string16& name);
@@ -177,6 +178,7 @@ struct AX_BASE_EXPORT AXNodeData {
// Please keep in alphabetic order.
ax::mojom::CheckedState GetCheckedState() const;
void SetCheckedState(ax::mojom::CheckedState checked_state);
+ bool HasCheckedState() const;
ax::mojom::DefaultActionVerb GetDefaultActionVerb() const;
void SetDefaultActionVerb(ax::mojom::DefaultActionVerb default_action_verb);
ax::mojom::HasPopup GetHasPopup() const;
@@ -258,9 +260,6 @@ struct AX_BASE_EXPORT AXNodeData {
// expand/collapse.
bool SupportsExpandCollapse() const;
- // Helper to determine if the node is in an active live region.
- bool IsContainedInActiveLiveRegion() const;
-
// Return a string representation of this data, for debugging.
virtual std::string ToString() const;
diff --git a/chromium/ui/accessibility/ax_node_position.cc b/chromium/ui/accessibility/ax_node_position.cc
index ffce6af1700..ef23c509c7e 100644
--- a/chromium/ui/accessibility/ax_node_position.cc
+++ b/chromium/ui/accessibility/ax_node_position.cc
@@ -18,7 +18,7 @@ AXEmbeddedObjectBehavior g_ax_embedded_object_behavior =
AXEmbeddedObjectBehavior::kExposeCharacter;
#else
AXEmbeddedObjectBehavior::kSuppressCharacter;
-#endif
+#endif // defined(OS_WIN)
// static
AXNodePosition::AXPositionInstance AXNodePosition::CreatePosition(
@@ -29,9 +29,10 @@ AXNodePosition::AXPositionInstance AXNodePosition::CreatePosition(
return CreateNullPosition();
AXTreeID tree_id = node.tree()->GetAXTreeID();
- if (node.IsText())
+ if (node.IsText()) {
return CreateTextPosition(tree_id, node.id(), child_index_or_text_offset,
affinity);
+ }
return CreateTreePosition(tree_id, node.id(), child_index_or_text_offset);
}
diff --git a/chromium/ui/accessibility/ax_node_position_unittest.cc b/chromium/ui/accessibility/ax_node_position_unittest.cc
index d7e7b25695e..170054bbb71 100644
--- a/chromium/ui/accessibility/ax_node_position_unittest.cc
+++ b/chromium/ui/accessibility/ax_node_position_unittest.cc
@@ -1370,6 +1370,135 @@ TEST_F(AXPositionTest, AtEndOfBlankLine) {
EXPECT_TRUE(text_position->AtEndOfLine());
}
+TEST_F(AXPositionTest, AtStartAndEndOfLineWhenAtEndOfTextSpan) {
+ // This test ensures that the "AtStartOfLine" and the "AtEndOfLine" methods
+ // return false and true respectively when we are at the end of a text span.
+ //
+ // A text span is defined by a series of inline text boxes that make up a
+ // single static text object. Lines always end at the end of static text
+ // objects, so there would never arise a situation when a position at the end
+ // of a text span would be at start of line. It should always be at end of
+ // line. On the contrary, if a position is at the end of an inline text box
+ // and the equivalent parent position is in the middle of a static text
+ // object, then the position would sometimes be at start of line, i.e., when
+ // the inline text box contains only white space that is used to separate
+ // lines in the case of lines being wrapped by a soft line break.
+ //
+ // Example accessibility tree:
+ // 0:kRootWebArea
+ // ++1:kStaticText "Hello testing "
+ // ++++2:kInlineTextBox "Hello" kNextOnLine=2
+ // ++++3:kInlineTextBox " " kPreviousOnLine=2
+ // ++++4:kInlineTextBox "testing" kNextOnLine=5
+ // ++++5:kInlineTextBox " " kPreviousOnLine=4
+ // ++6:kStaticText "here."
+ // ++++7:kInlineTextBox "here."
+ //
+ // Resulting text representation:
+ // "Hello<soft_line_break>testing <hard_line_break>here."
+ // Notice the extra space after the word "testing". This is not a line break.
+ // The hard line break is caused by the presence of the second static text
+ // object.
+ //
+ // A position at the end of inline text box 3 should be at start of line,
+ // whilst a position at the end of inline text box 5 should not.
+
+ AXNodeData root_data;
+ root_data.id = 1;
+ root_data.role = ax::mojom::Role::kRootWebArea;
+ // "kIsLineBreakingObject" is not strictly necessary but is added for
+ // completeness.
+ root_data.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
+ true);
+
+ AXNodeData static_text_data_1;
+ static_text_data_1.id = 2;
+ static_text_data_1.role = ax::mojom::Role::kStaticText;
+ static_text_data_1.SetName("Hello testing ");
+
+ AXNodeData inline_box_data_1;
+ inline_box_data_1.id = 3;
+ inline_box_data_1.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_data_1.SetName("hello");
+
+ AXNodeData inline_box_data_2;
+ inline_box_data_2.id = 4;
+ inline_box_data_2.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_data_1.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
+ inline_box_data_2.id);
+ inline_box_data_2.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
+ inline_box_data_1.id);
+ // The name is a space character that we assume it turns into a soft line
+ // break by the layout engine.
+ inline_box_data_2.SetName(" ");
+
+ AXNodeData inline_box_data_3;
+ inline_box_data_3.id = 5;
+ inline_box_data_3.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_data_3.SetName("testing");
+
+ AXNodeData inline_box_data_4;
+ inline_box_data_4.id = 6;
+ inline_box_data_4.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_data_3.AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
+ inline_box_data_4.id);
+ inline_box_data_4.AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
+ inline_box_data_3.id);
+ inline_box_data_4.SetName(" "); // Just a space character - not a line break.
+
+ AXNodeData static_text_data_2;
+ static_text_data_2.id = 7;
+ static_text_data_2.role = ax::mojom::Role::kStaticText;
+ static_text_data_2.SetName("here.");
+
+ AXNodeData inline_box_data_5;
+ inline_box_data_5.id = 8;
+ inline_box_data_5.role = ax::mojom::Role::kInlineTextBox;
+ inline_box_data_5.SetName("here.");
+
+ static_text_data_1.child_ids = {inline_box_data_1.id, inline_box_data_2.id,
+ inline_box_data_3.id, inline_box_data_4.id};
+ static_text_data_2.child_ids = {inline_box_data_5.id};
+ root_data.child_ids = {static_text_data_1.id, static_text_data_2.id};
+
+ SetTree(CreateAXTree({root_data, static_text_data_1, inline_box_data_1,
+ inline_box_data_2, inline_box_data_3, inline_box_data_4,
+ static_text_data_2, inline_box_data_5}));
+
+ // An "after text" tree position - after the soft line break.
+ TestPositionType tree_position = AXNodePosition::CreateTreePosition(
+ GetTreeID(), inline_box_data_2.id, 0 /* child_index */);
+ ASSERT_NE(nullptr, tree_position);
+ ASSERT_TRUE(tree_position->IsTreePosition());
+ EXPECT_TRUE(tree_position->AtStartOfLine());
+ EXPECT_FALSE(tree_position->AtEndOfLine());
+
+ // An "after text" tree position - after the space character and before the
+ // hard line break caused by the second static text object.
+ tree_position = AXNodePosition::CreateTreePosition(
+ GetTreeID(), inline_box_data_4.id, 0 /* child_index */);
+ ASSERT_NE(nullptr, tree_position);
+ ASSERT_TRUE(tree_position->IsTreePosition());
+ EXPECT_FALSE(tree_position->AtStartOfLine());
+ EXPECT_TRUE(tree_position->AtEndOfLine());
+
+ TestPositionType text_position = AXNodePosition::CreateTextPosition(
+ GetTreeID(), inline_box_data_2.id, 1 /* text_offset */,
+ ax::mojom::TextAffinity::kDownstream);
+ ASSERT_NE(nullptr, text_position);
+ ASSERT_TRUE(text_position->IsTextPosition());
+ EXPECT_TRUE(text_position->AtStartOfLine());
+ EXPECT_FALSE(text_position->AtEndOfLine());
+
+ text_position = AXNodePosition::CreateTextPosition(
+ GetTreeID(), inline_box_data_4.id, 1 /* text_offset */,
+ ax::mojom::TextAffinity::kDownstream);
+ ASSERT_NE(nullptr, text_position);
+ ASSERT_TRUE(text_position->IsTextPosition());
+ EXPECT_FALSE(text_position->AtStartOfLine());
+ EXPECT_TRUE(text_position->AtEndOfLine());
+}
+
TEST_F(AXPositionTest, AtStartAndEndOfLineInsideTextField) {
// This test ensures that "AtStart/EndOfLine" methods work properly when at
// the start or end of a text field.
diff --git a/chromium/ui/accessibility/ax_param_traits_macros.h b/chromium/ui/accessibility/ax_param_traits_macros.h
index d33912c774e..b545df34ba2 100644
--- a/chromium/ui/accessibility/ax_param_traits_macros.h
+++ b/chromium/ui/accessibility/ax_param_traits_macros.h
@@ -106,6 +106,7 @@ IPC_STRUCT_TRAITS_BEGIN(ui::AXTreeUpdate)
IPC_STRUCT_TRAITS_MEMBER(root_id)
IPC_STRUCT_TRAITS_MEMBER(nodes)
IPC_STRUCT_TRAITS_MEMBER(event_from)
+ IPC_STRUCT_TRAITS_MEMBER(event_intents)
IPC_STRUCT_TRAITS_END()
#undef IPC_MESSAGE_EXPORT
diff --git a/chromium/ui/accessibility/ax_position.h b/chromium/ui/accessibility/ax_position.h
index 70f9501c195..dfeed6edfde 100644
--- a/chromium/ui/accessibility/ax_position.h
+++ b/chromium/ui/accessibility/ax_position.h
@@ -23,7 +23,6 @@
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
-#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node.h"
@@ -518,9 +517,12 @@ class AXPosition {
// We assume that white space, including but not limited to hard line
// breaks, might be used to separate lines. For example, an inline text
// box with just a single space character inside it can be used to
- // represent a soft line break. if an inline text box containing white
+ // represent a soft line break. If an inline text box containing white
// space separates two lines, it should always be connected to the first
- // line via "kPreviousOnLineId". This is guaranteed by the renderer.
+ // line via "kPreviousOnLineId". This is guaranteed by the renderer. If
+ // there are multiple line breaks separating the two lines, then only
+ // the first line break is connected to the first line via
+ // "kPreviousOnLineId".
//
// Sometimes there might be an inline text box with a single space in it
// at the end of a text field. We should not mark positions that are at
@@ -530,11 +532,11 @@ class AXPosition {
// all cases, the parent of an inline text box is a static text object,
// whose end signifies the end of the text span. One exception is line
// breaks.
- if (!text_position->AtEndOfTextSpan() &&
+ if (text_position->AtEndOfAnchor() &&
+ !text_position->AtEndOfTextSpan() &&
text_position->IsInWhiteSpace() &&
GetNextOnLineID(text_position->anchor_id_) ==
- AXNode::kInvalidAXID &&
- text_position->AtEndOfAnchor()) {
+ AXNode::kInvalidAXID) {
return true;
}
@@ -573,10 +575,12 @@ class AXPosition {
// In other cases, we assume that white space, including but not limited
// to hard line breaks, might be used to separate lines. For example, an
// inline text box with just a single space character inside it can be
- // used to represent a soft line break. if an inline text box containing
+ // used to represent a soft line break. If an inline text box containing
// white space separates two lines, it should always be connected to the
// first line via "kPreviousOnLineId". This is guaranteed by the
- // renderer.
+ // renderer. If there are multiple line breaks separating the two lines,
+ // then only the first line break is connected to the first line via
+ // "kPreviousOnLineId".
//
// We don't treat a position that is at the start of white space that is
// on a line by itself as being at the end of the line. This is in order
@@ -641,9 +645,10 @@ class AXPosition {
// 2. The current position is not whitespace only, unless it is also
// the first leaf text position within the document.
- if (text_position->IsInWhiteSpace())
+ if (text_position->IsInWhiteSpace()) {
return text_position->CreatePreviousLeafTextPosition()
->IsNullPosition();
+ }
// 3. Either (a) the current leaf text position is the first leaf text
// position in the document, or (b) there are no line breaking
@@ -3036,7 +3041,7 @@ class AXPosition {
if (AnchorUnignoredChildCount())
return false;
- // All unignored leaf nodes in the AXTree except the document and the text
+ // All unignored leaf nodes in the AXTree except document and text
// nodes should be replaced by the embedded object character. Also, nodes
// that only have ignored children (e.g., a button that contains only an
// empty div) need to be treated as leaf nodes.
@@ -3083,14 +3088,12 @@ class AXPosition {
// The first unignored ancestor is necessarily the empty object if this node
// is the descendant of an empty object.
AXNodeType* ancestor_node = GetLowestUnignoredAncestor();
-
if (!ancestor_node)
return nullptr;
AXPositionInstance position = CreateTextPosition(
tree_id_, GetAnchorID(ancestor_node), 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
-
if (position && position->IsEmptyObjectReplacedByCharacter())
return ancestor_node;
diff --git a/chromium/ui/accessibility/ax_range_unittest.cc b/chromium/ui/accessibility/ax_range_unittest.cc
index 92910f94a57..b27df725cb6 100644
--- a/chromium/ui/accessibility/ax_range_unittest.cc
+++ b/chromium/ui/accessibility/ax_range_unittest.cc
@@ -17,7 +17,7 @@
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_update.h"
-#include "ui/accessibility/platform/test_ax_node_wrapper.h"
+#include "ui/accessibility/test_ax_node_helper.h"
#include "ui/accessibility/test_ax_tree_manager.h"
namespace ui {
@@ -68,8 +68,8 @@ class TestAXRangeScreenRectDelegate : public AXRangeRectDelegate {
if (!node)
return gfx::Rect();
- TestAXNodeWrapper* wrapper =
- TestAXNodeWrapper::GetOrCreate(tree_manager_->GetTree(), node);
+ TestAXNodeHelper* wrapper =
+ TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
return wrapper->GetInnerTextRangeBoundsRect(
start_offset, end_offset, AXCoordinateSystem::kScreenDIPs,
AXClippingBehavior::kClipped, offscreen_result);
@@ -85,8 +85,8 @@ class TestAXRangeScreenRectDelegate : public AXRangeRectDelegate {
if (!node)
return gfx::Rect();
- TestAXNodeWrapper* wrapper =
- TestAXNodeWrapper::GetOrCreate(tree_manager_->GetTree(), node);
+ TestAXNodeHelper* wrapper =
+ TestAXNodeHelper::GetOrCreate(tree_manager_->GetTree(), node);
return wrapper->GetBoundsRect(AXCoordinateSystem::kScreenDIPs,
AXClippingBehavior::kClipped,
offscreen_result);
diff --git a/chromium/ui/accessibility/ax_role_properties.cc b/chromium/ui/accessibility/ax_role_properties.cc
index 587b4e81e19..60115f31327 100644
--- a/chromium/ui/accessibility/ax_role_properties.cc
+++ b/chromium/ui/accessibility/ax_role_properties.cc
@@ -564,10 +564,10 @@ bool IsSetLike(const ax::mojom::Role role) {
case ax::mojom::Role::kMenu:
case ax::mojom::Role::kMenuBar:
case ax::mojom::Role::kMenuListPopup:
+ case ax::mojom::Role::kPopUpButton:
case ax::mojom::Role::kRadioGroup:
case ax::mojom::Role::kTabList:
case ax::mojom::Role::kTree:
- case ax::mojom::Role::kPopUpButton:
return true;
default:
return false;
@@ -675,16 +675,6 @@ bool IsTableRow(ax::mojom::Role role) {
}
}
-bool IsTextOrLineBreak(ax::mojom::Role role) {
- switch (role) {
- case ax::mojom::Role::kLineBreak:
- case ax::mojom::Role::kStaticText:
- return true;
- default:
- return false;
- }
-}
-
bool IsText(ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kInlineTextBox:
diff --git a/chromium/ui/accessibility/ax_role_properties.h b/chromium/ui/accessibility/ax_role_properties.h
index 536d2758440..60e8e570e62 100644
--- a/chromium/ui/accessibility/ax_role_properties.h
+++ b/chromium/ui/accessibility/ax_role_properties.h
@@ -161,14 +161,10 @@ AX_BASE_EXPORT bool IsTableLike(const ax::mojom::Role role);
// table is not used for layout purposes.
AX_BASE_EXPORT bool IsTableRow(ax::mojom::Role role);
-// Returns true if it's a text-related node e.g. static text, line break, or
-// inline text box node.
+// Returns true if the provided role is text-related, e.g., static text, line
+// break, or inline text box.
AX_BASE_EXPORT bool IsText(ax::mojom::Role role);
-// Returns true if it's a text-related node e.g. a static text or line break
-// node.
-AX_BASE_EXPORT bool IsTextOrLineBreak(ax::mojom::Role role);
-
// Returns true if the role supports expand/collapse.
AX_BASE_EXPORT bool SupportsExpandCollapse(const ax::mojom::Role role);
diff --git a/chromium/ui/accessibility/ax_table_fuzzer.cc b/chromium/ui/accessibility/ax_table_fuzzer.cc
index 9925f9bf79f..1ee955ee38f 100644
--- a/chromium/ui/accessibility/ax_table_fuzzer.cc
+++ b/chromium/ui/accessibility/ax_table_fuzzer.cc
@@ -98,12 +98,21 @@ void TestTableAPIs(const ui::AXNode* node) {
// crash. Normally |ids| is an out argument only, but
// there's no reason we shouldn't be able to pass a vector
// that was previously used by another call.
- std::vector<int32_t> ids;
+ std::vector<ui::AXNode::AXID> ids;
for (int i = 0; i < 3; i++) {
- node->GetTableColHeaderNodeIds(i, &ids);
- node->GetTableRowHeaderNodeIds(i, &ids);
+ std::vector<ui::AXNode::AXID> col_header_node_ids =
+ node->GetTableColHeaderNodeIds(i);
+ ids.insert(ids.end(), col_header_node_ids.begin(),
+ col_header_node_ids.end());
+
+ std::vector<ui::AXNode::AXID> row_header_node_ids =
+ node->GetTableRowHeaderNodeIds(i);
+ ids.insert(ids.end(), row_header_node_ids.begin(),
+ row_header_node_ids.end());
}
- node->GetTableUniqueCellIds(&ids);
+ std::vector<ui::AXNode::AXID> unique_cell_ids = node->GetTableUniqueCellIds();
+ ids.insert(ids.end(), unique_cell_ids.begin(), unique_cell_ids.end());
+
ignore_result(node->IsTableRow());
ignore_result(node->GetTableRowRowIndex());
#if defined(OS_MACOSX)
@@ -118,8 +127,14 @@ void TestTableAPIs(const ui::AXNode* node) {
ignore_result(node->GetTableCellRowSpan());
ignore_result(node->GetTableCellAriaColIndex());
ignore_result(node->GetTableCellAriaRowIndex());
- node->GetTableCellColHeaderNodeIds(&ids);
- node->GetTableCellRowHeaderNodeIds(&ids);
+ std::vector<ui::AXNode::AXID> cell_col_header_node_ids =
+ node->GetTableCellColHeaderNodeIds();
+ ids.insert(ids.end(), cell_col_header_node_ids.begin(),
+ cell_col_header_node_ids.end());
+ std::vector<ui::AXNode::AXID> cell_row_header_node_ids =
+ node->GetTableCellRowHeaderNodeIds();
+ ids.insert(ids.end(), cell_row_header_node_ids.begin(),
+ cell_row_header_node_ids.end());
std::vector<ui::AXNode*> headers;
node->GetTableCellColHeaders(&headers);
node->GetTableCellRowHeaders(&headers);
diff --git a/chromium/ui/accessibility/ax_tree.cc b/chromium/ui/accessibility/ax_tree.cc
index 5a2b447abbc..003f2770723 100644
--- a/chromium/ui/accessibility/ax_tree.cc
+++ b/chromium/ui/accessibility/ax_tree.cc
@@ -925,6 +925,11 @@ const std::set<AXTreeID> AXTree::GetAllChildTreeIds() const {
}
bool AXTree::Unserialize(const AXTreeUpdate& update) {
+ event_intents_ = update.event_intents;
+ base::ScopedClosureRunner clear_event_intents(base::BindOnce(
+ [](std::vector<AXEventIntent>* event_intents) { event_intents->clear(); },
+ &event_intents_));
+
AXTreeUpdateState update_state(*this);
const AXNode::AXID old_root_id = root_ ? root_->id() : AXNode::kInvalidAXID;
@@ -2202,30 +2207,69 @@ void AXTree::ComputeSetSizePosInSetAndCacheHelper(
} // End of iterating over each item in |ordered_set_content|.
}
-// Returns the pos_in_set of item. Looks in |node_set_size_pos_in_set_info_map_|
-// for cached value. Calculates pos_in_set and set_size for item (and all other
-// items in the same ordered set) if no value is present in the cache. This
-// function is guaranteed to be only called on nodes that can hold pos_in_set
-// values, minimizing the size of the cache.
-int32_t AXTree::GetPosInSet(const AXNode& node, const AXNode* ordered_set) {
- // If item's id is not in the cache, compute it.
- if (node_set_size_pos_in_set_info_map_.find(node.id()) ==
- node_set_size_pos_in_set_info_map_.end())
- ComputeSetSizePosInSetAndCache(node, ordered_set);
- return node_set_size_pos_in_set_info_map_[node.id()].pos_in_set;
+base::Optional<int> AXTree::GetPosInSet(const AXNode& node) {
+ if (node_set_size_pos_in_set_info_map_.find(node.id()) !=
+ node_set_size_pos_in_set_info_map_.end()) {
+ // If item's id is in the cache, return stored PosInSet value.
+ return node_set_size_pos_in_set_info_map_[node.id()].pos_in_set;
+ }
+
+ if (GetTreeUpdateInProgressState())
+ return base::nullopt;
+
+ // Only allow this to be called on nodes that can hold PosInSet values,
+ // which are defined in the ARIA spec.
+ if (!node.IsOrderedSetItem() || node.IsIgnored())
+ return base::nullopt;
+
+ const AXNode* ordered_set = node.GetOrderedSet();
+ if (!ordered_set)
+ return base::nullopt;
+
+ // Compute, cache, then return.
+ ComputeSetSizePosInSetAndCache(node, ordered_set);
+ base::Optional<int> pos_in_set =
+ node_set_size_pos_in_set_info_map_[node.id()].pos_in_set;
+ if (pos_in_set.has_value() && pos_in_set.value() < 1)
+ return base::nullopt;
+
+ return pos_in_set;
}
-// Returns the set_size of node. node could be an ordered set or an item.
-// Looks in |node_set_size_pos_in_set_info_map_| for cached value. Calculates
-// pos_in_set and set_size for all nodes in same ordered set if no value is
-// present in the cache. This function is guaranteed to be only called on nodes
-// that can hold set_size values, minimizing the size of the cache.
-int32_t AXTree::GetSetSize(const AXNode& node, const AXNode* ordered_set) {
- // If node's id is not in the cache, compute it.
- if (node_set_size_pos_in_set_info_map_.find(node.id()) ==
- node_set_size_pos_in_set_info_map_.end())
- ComputeSetSizePosInSetAndCache(node, ordered_set);
- return node_set_size_pos_in_set_info_map_[node.id()].set_size;
+base::Optional<int> AXTree::GetSetSize(const AXNode& node) {
+ if (node_set_size_pos_in_set_info_map_.find(node.id()) !=
+ node_set_size_pos_in_set_info_map_.end()) {
+ // If item's id is in the cache, return stored SetSize value.
+ return node_set_size_pos_in_set_info_map_[node.id()].set_size;
+ }
+
+ if (GetTreeUpdateInProgressState())
+ return base::nullopt;
+
+ // Only allow this to be called on nodes that can hold SetSize values, which
+ // are defined in the ARIA spec. However, we allow set-like items to receive
+ // SetSize values for internal purposes.
+ if ((!node.IsOrderedSetItem() && !node.IsOrderedSet()) || node.IsIgnored() ||
+ node.IsEmbeddedGroup()) {
+ return base::nullopt;
+ }
+
+ // If |node| is item-like, find its outerlying ordered set. Otherwise,
+ // |node| is the ordered set.
+ const AXNode* ordered_set = &node;
+ if (IsItemLike(node.data().role))
+ ordered_set = node.GetOrderedSet();
+ if (!ordered_set)
+ return base::nullopt;
+
+ // Compute, cache, then return.
+ ComputeSetSizePosInSetAndCache(node, ordered_set);
+ base::Optional<int> set_size =
+ node_set_size_pos_in_set_info_map_[node.id()].set_size;
+ if (set_size.has_value() && set_size.value() < 0)
+ return base::nullopt;
+
+ return set_size;
}
AXTree::Selection AXTree::GetUnignoredSelection() const {
diff --git a/chromium/ui/accessibility/ax_tree.h b/chromium/ui/accessibility/ax_tree.h
index 8c1c57517ac..c47d1c20234 100644
--- a/chromium/ui/accessibility/ax_tree.h
+++ b/chromium/ui/accessibility/ax_tree.h
@@ -147,18 +147,14 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// conflict with positive-numbered node IDs from tree sources.
int32_t GetNextNegativeInternalNodeId();
- // Returns the pos_in_set of node. Looks in node_set_size_pos_in_set_info_map_
- // for cached value. Calculates pos_in_set and set_size for node (and all
- // other nodes in the same ordered set) if no value is present in the cache.
- // This function is guaranteed to be only called on nodes that can hold
- // pos_in_set values, minimizing the size of the cache.
- int32_t GetPosInSet(const AXNode& node, const AXNode* ordered_set) override;
- // Returns the set_size of node. Looks in node_set_size_pos_in_set_info_map_
- // for cached value. Calculates pos_inset_set and set_size for node (and all
- // other nodes in the same ordered set) if no value is present in the cache.
- // This function is guaranteed to be only called on nodes that can hold
- // set_size values, minimizing the size of the cache.
- int32_t GetSetSize(const AXNode& node, const AXNode* ordered_set) override;
+ // Returns the PosInSet of |node|. Looks in node_set_size_pos_in_set_info_map_
+ // for cached value. Calls |ComputeSetSizePosInSetAndCache|if no value is
+ // present in the cache.
+ base::Optional<int> GetPosInSet(const AXNode& node) override;
+ // Returns the SetSize of |node|. Looks in node_set_size_pos_in_set_info_map_
+ // for cached value. Calls |ComputeSetSizePosInSetAndCache|if no value is
+ // present in the cache.
+ base::Optional<int> GetSetSize(const AXNode& node) override;
Selection GetUnignoredSelection() const override;
@@ -174,6 +170,11 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// When should we initialize this?
std::unique_ptr<AXLanguageDetectionManager> language_detection_manager;
+ // A list of intents active during a tree update/unserialization.
+ const std::vector<AXEventIntent>& event_intents() const {
+ return event_intents_;
+ }
+
private:
friend class AXTableInfoTest;
@@ -332,8 +333,8 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
NodeSetSizePosInSetInfo();
~NodeSetSizePosInSetInfo();
- int32_t pos_in_set = 0;
- int32_t set_size = 0;
+ base::Optional<int> pos_in_set;
+ base::Optional<int> set_size;
base::Optional<int> lowest_hierarchical_level;
};
@@ -388,6 +389,8 @@ class AX_EXPORT AXTree : public AXNode::OwnerTree {
// Indicates if the tree represents a paginated document
bool has_pagination_support_ = false;
+
+ std::vector<AXEventIntent> event_intents_;
};
} // namespace ui
diff --git a/chromium/ui/accessibility/ax_tree_id.cc b/chromium/ui/accessibility/ax_tree_id.cc
index 86347de4255..3cb2335c798 100644
--- a/chromium/ui/accessibility/ax_tree_id.cc
+++ b/chromium/ui/accessibility/ax_tree_id.cc
@@ -10,7 +10,7 @@
#include "base/check.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
-#include "base/value_conversions.h"
+#include "base/util/values/values_util.h"
#include "base/values.h"
#include "ui/accessibility/ax_enums.mojom.h"
@@ -30,10 +30,10 @@ AXTreeID::AXTreeID(const std::string& string) {
type_ = ax::mojom::AXTreeIDType::kUnknown;
} else {
type_ = ax::mojom::AXTreeIDType::kToken;
- base::Value string_value(string);
- base::UnguessableToken token;
- CHECK(base::GetValueAsUnguessableToken(string_value, &token));
- token_ = token;
+ base::Optional<base::UnguessableToken> token =
+ util::ValueToUnguessableToken(base::Value(string));
+ CHECK(token);
+ token_ = *token;
}
}
@@ -54,7 +54,7 @@ std::string AXTreeID::ToString() const {
case ax::mojom::AXTreeIDType::kUnknown:
return "";
case ax::mojom::AXTreeIDType::kToken:
- return base::CreateUnguessableTokenValue(*token_).GetString();
+ return util::UnguessableTokenToValue(*token_).GetString();
}
NOTREACHED();
diff --git a/chromium/ui/accessibility/ax_tree_unittest.cc b/chromium/ui/accessibility/ax_tree_unittest.cc
index 05c962720ea..8f2c3ce8541 100644
--- a/chromium/ui/accessibility/ax_tree_unittest.cc
+++ b/chromium/ui/accessibility/ax_tree_unittest.cc
@@ -3138,7 +3138,7 @@ TEST(AXTreeTest, ChildTreeIds) {
}
// Tests GetPosInSet and GetSetSize return the assigned int attribute values.
-TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
+TEST(AXTreeTest, SetSizePosInSetAssigned) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3170,8 +3170,8 @@ TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
EXPECT_OPTIONAL_EQ(12, item3->GetSetSize());
}
-// Tests that pos_in_set and set_size can be calculated if not assigned.
-TEST(AXTreeTest, TestSetSizePosInSetUnassigned) {
+// Tests that PosInSet and SetSize can be calculated if not assigned.
+TEST(AXTreeTest, SetSizePosInSetUnassigned) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3197,9 +3197,9 @@ TEST(AXTreeTest, TestSetSizePosInSetUnassigned) {
EXPECT_OPTIONAL_EQ(3, item3->GetSetSize());
}
-// Tests pos_in_set can be calculated if unassigned, and set_size can be
+// Tests PosInSet can be calculated if unassigned, and SetSize can be
// assigned on the outerlying ordered set.
-TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
+TEST(AXTreeTest, SetSizeAssignedOnContainer) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3215,18 +3215,21 @@ TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
tree_update.nodes[3].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
- // Items should inherit set_size from ordered set if not specified.
+ // Items should inherit SetSize from ordered set if not specified.
AXNode* item1 = tree.GetFromId(2);
EXPECT_OPTIONAL_EQ(7, item1->GetSetSize());
+ EXPECT_OPTIONAL_EQ(1, item1->GetPosInSet());
AXNode* item2 = tree.GetFromId(3);
EXPECT_OPTIONAL_EQ(7, item2->GetSetSize());
+ EXPECT_OPTIONAL_EQ(2, item2->GetPosInSet());
AXNode* item3 = tree.GetFromId(4);
EXPECT_OPTIONAL_EQ(7, item3->GetSetSize());
+ EXPECT_OPTIONAL_EQ(3, item3->GetPosInSet());
}
// Tests GetPosInSet and GetSetSize on a list containing various roles.
// Roles for items and associated ordered set should match up.
-TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
+TEST(AXTreeTest, SetSizePosInSetDiverseList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(6);
@@ -3261,12 +3264,12 @@ TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
EXPECT_OPTIONAL_EQ(4, item3->GetPosInSet());
EXPECT_OPTIONAL_EQ(4, item3->GetSetSize());
AXNode* tab = tree.GetFromId(6);
- EXPECT_OPTIONAL_EQ(0, tab->GetPosInSet());
- EXPECT_OPTIONAL_EQ(0, tab->GetSetSize());
+ EXPECT_FALSE(tab->GetPosInSet());
+ EXPECT_FALSE(tab->GetSetSize());
}
// Tests GetPosInSet and GetSetSize on a nested list.
-TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
+TEST(AXTreeTest, SetSizePosInSetNestedList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(7);
@@ -3307,9 +3310,9 @@ TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
EXPECT_OPTIONAL_EQ(3, outer_item3->GetSetSize());
}
-// Tests pos_in_set can be calculated if one item specifies pos_in_set, but
+// Tests PosInSet can be calculated if one item specifies PosInSet, but
// other assignments are missing.
-TEST(AXTreeTest, TestPosInSetMissing) {
+TEST(AXTreeTest, PosInSetMissing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3339,8 +3342,8 @@ TEST(AXTreeTest, TestPosInSetMissing) {
EXPECT_OPTIONAL_EQ(20, item3->GetSetSize());
}
-// A more difficult test that involves missing pos_in_set and set_size values.
-TEST(AXTreeTest, TestSetSizePosInSetMissingDifficult) {
+// A more difficult test that involves missing PosInSet and SetSize values.
+TEST(AXTreeTest, SetSizePosInSetMissingDifficult) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(6);
@@ -3380,9 +3383,9 @@ TEST(AXTreeTest, TestSetSizePosInSetMissingDifficult) {
EXPECT_OPTIONAL_EQ(11, item5->GetSetSize());
}
-// Tests that code overwrites decreasing set_size assignments to largest of
+// Tests that code overwrites decreasing SetSize assignments to largest of
// assigned values.
-TEST(AXTreeTest, TestSetSizeDecreasing) {
+TEST(AXTreeTest, SetSizeDecreasing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3410,8 +3413,8 @@ TEST(AXTreeTest, TestSetSizeDecreasing) {
EXPECT_OPTIONAL_EQ(5, item3->GetSetSize());
}
-// Tests that code overwrites decreasing pos_in_set values.
-TEST(AXTreeTest, TestPosInSetDecreasing) {
+// Tests that code overwrites decreasing PosInSet values.
+TEST(AXTreeTest, PosInSetDecreasing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3439,10 +3442,10 @@ TEST(AXTreeTest, TestPosInSetDecreasing) {
EXPECT_OPTIONAL_EQ(8, item3->GetSetSize());
}
-// Tests that code overwrites duplicate pos_in_set values. Note this case is
+// Tests that code overwrites duplicate PosInSet values. Note this case is
// tricky; an update to the second element causes an update to the third
// element.
-TEST(AXTreeTest, TestPosInSetDuplicates) {
+TEST(AXTreeTest, PosInSetDuplicates) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3473,7 +3476,7 @@ TEST(AXTreeTest, TestPosInSetDuplicates) {
// Tests GetPosInSet and GetSetSize when some list items are nested in a generic
// container.
-TEST(AXTreeTest, TestSetSizePosInSetNestedContainer) {
+TEST(AXTreeTest, SetSizePosInSetNestedContainer) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(7);
@@ -3517,10 +3520,8 @@ TEST(AXTreeTest, TestSetSizePosInSetNestedContainer) {
}
// Tests GetSetSize and GetPosInSet are correct, even when list items change.
-// This test is directed at the caching functionality of pos_in_set and
-// set_size. Tests that previously calculated values are not used after
-// tree is updated.
-TEST(AXTreeTest, TestSetSizePosInSetDeleteItem) {
+// Tests that previously calculated values are not used after tree is updated.
+TEST(AXTreeTest, SetSizePosInSetDeleteItem) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(4);
@@ -3561,9 +3562,9 @@ TEST(AXTreeTest, TestSetSizePosInSetDeleteItem) {
// Tests GetSetSize and GetPosInSet are correct, even when list items change.
// This test adds an item to the front of a list, which invalidates previously
-// calculated pos_in_set and set_size values. Tests that old values are not
+// calculated PosInSet and SetSize values. Tests that old values are not
// used after tree is updated.
-TEST(AXTreeTest, TestSetSizePosInSetAddItem) {
+TEST(AXTreeTest, SetSizePosInSetAddItem) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(4);
@@ -3611,22 +3612,22 @@ TEST(AXTreeTest, TestSetSizePosInSetAddItem) {
EXPECT_OPTIONAL_EQ(4, new_item4->GetSetSize());
}
-// Tests that the outerlying ordered set reports a set_size. Ordered sets
-// should not report a pos_in_set value other than 0, since they are not
+// Tests that the outerlying ordered set reports a SetSize. Ordered sets
+// should not report a PosInSet value other than 0, since they are not
// considered to be items within a set (even when nested).
-TEST(AXTreeTest, TestOrderedSetReportsSetSize) {
+TEST(AXTreeTest, OrderedSetReportsSetSize) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(12);
tree_update.nodes[0].id = 1;
- tree_update.nodes[0].role = ax::mojom::Role::kList; // set_size = 3
+ tree_update.nodes[0].role = ax::mojom::Role::kList; // SetSize = 3
tree_update.nodes[0].child_ids = {2, 3, 4, 7, 8, 9, 12};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 3
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem; // 2 of 3
tree_update.nodes[3].id = 4;
- tree_update.nodes[3].role = ax::mojom::Role::kList; // set_size = 2
+ tree_update.nodes[3].role = ax::mojom::Role::kList; // SetSize = 2
tree_update.nodes[3].child_ids = {5, 6};
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kListItem; // 1 of 2
@@ -3635,10 +3636,10 @@ TEST(AXTreeTest, TestOrderedSetReportsSetSize) {
tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kListItem; // 3 of 3
tree_update.nodes[7].id = 8;
- tree_update.nodes[7].role = ax::mojom::Role::kList; // set_size = 0
+ tree_update.nodes[7].role = ax::mojom::Role::kList; // SetSize = 0
tree_update.nodes[8].id = 9;
tree_update.nodes[8].role =
- ax::mojom::Role::kList; // set_size = 1 because only 1 item whose role
+ ax::mojom::Role::kList; // SetSize = 1 because only 1 item whose role
// matches
tree_update.nodes[8].child_ids = {10, 11};
tree_update.nodes[9].id = 10;
@@ -3682,8 +3683,8 @@ TEST(AXTreeTest, TestOrderedSetReportsSetSize) {
// Only 1 item whose role matches.
EXPECT_OPTIONAL_EQ(1, inner_list3->GetSetSize());
AXNode* inner_list3_article1 = tree.GetFromId(10);
- EXPECT_OPTIONAL_EQ(0, inner_list3_article1->GetPosInSet());
- EXPECT_OPTIONAL_EQ(0, inner_list3_article1->GetSetSize());
+ EXPECT_FALSE(inner_list3_article1->GetPosInSet());
+ EXPECT_FALSE(inner_list3_article1->GetSetSize());
AXNode* inner_list3_item1 = tree.GetFromId(11);
EXPECT_OPTIONAL_EQ(1, inner_list3_item1->GetPosInSet());
EXPECT_OPTIONAL_EQ(1, inner_list3_item1->GetSetSize());
@@ -3696,7 +3697,7 @@ TEST(AXTreeTest, TestOrderedSetReportsSetSize) {
}
// Tests GetPosInSet and GetSetSize code on invalid input.
-TEST(AXTreeTest, TestSetSizePosInSetInvalid) {
+TEST(AXTreeTest, SetSizePosInSetInvalid) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(3);
@@ -3715,17 +3716,17 @@ TEST(AXTreeTest, TestSetSizePosInSetInvalid) {
EXPECT_FALSE(item1->GetPosInSet());
EXPECT_FALSE(item1->GetSetSize());
AXNode* item2 = tree.GetFromId(2);
- EXPECT_OPTIONAL_EQ(0, item2->GetPosInSet());
- EXPECT_OPTIONAL_EQ(0, item2->GetSetSize());
+ EXPECT_FALSE(item2->GetPosInSet());
+ EXPECT_FALSE(item2->GetSetSize());
AXNode* item3 = tree.GetFromId(3);
- EXPECT_OPTIONAL_EQ(0, item3->GetPosInSet());
- EXPECT_OPTIONAL_EQ(0, item3->GetSetSize());
+ EXPECT_FALSE(item3->GetPosInSet());
+ EXPECT_FALSE(item3->GetSetSize());
}
// Tests GetPosInSet and GetSetSize code on kRadioButtons. Radio buttons
// behave differently than other item-like elements; most notably, they do not
// need to be contained within an ordered set to report a PosInSet or SetSize.
-TEST(AXTreeTest, TestSetSizePosInSetRadioButtons) {
+TEST(AXTreeTest, SetSizePosInSetRadioButtons) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(13);
@@ -3837,13 +3838,13 @@ TEST(AXTreeTest, TestSetSizePosInSetRadioButtons) {
// Tests GetPosInSet and GetSetSize on a list that includes radio buttons.
// Note that radio buttons do not contribute to the SetSize of the outerlying
// list.
-TEST(AXTreeTest, TestSetSizePosInSetRadioButtonsInList) {
+TEST(AXTreeTest, SetSizePosInSetRadioButtonsInList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(6);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role =
- ax::mojom::Role::kList; // set_size = 2, since only contains 2 ListItems
+ ax::mojom::Role::kList; // SetSize = 2, since only contains 2 ListItems
tree_update.nodes[0].child_ids = {2, 3, 4, 5, 6};
tree_update.nodes[1].id = 2;
@@ -3888,7 +3889,7 @@ TEST(AXTreeTest, TestSetSizePosInSetRadioButtonsInList) {
// to the tree representation, the three elements are siblings. However,
// due to the presence of the kHierarchicalLevel attribute, they all belong
// to different sets.
-TEST(AXTreeTest, TestSetSizePosInSetFlatTree) {
+TEST(AXTreeTest, SetSizePosInSetFlatTree) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
@@ -3922,7 +3923,7 @@ TEST(AXTreeTest, TestSetSizePosInSetFlatTree) {
// Tests GetPosInSet and GetSetSize on a flat tree representation, where only
// the level is specified.
-TEST(AXTreeTest, TestSetSizePosInSetFlatTreeLevelsOnly) {
+TEST(AXTreeTest, SetSizePosInSetFlatTreeLevelsOnly) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(9);
@@ -3994,7 +3995,7 @@ TEST(AXTreeTest, TestSetSizePosInSetFlatTreeLevelsOnly) {
// Tests that GetPosInSet and GetSetSize work while a tree is being
// unserialized.
-TEST(AXTreeTest, TestSetSizePosInSetSubtreeDeleted) {
+TEST(AXTreeTest, SetSizePosInSetSubtreeDeleted) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(3);
@@ -4035,7 +4036,7 @@ TEST(AXTreeTest, TestSetSizePosInSetSubtreeDeleted) {
}
// Tests that GetPosInSet and GetSetSize work when there are ignored nodes.
-TEST(AXTreeTest, TestSetSizePosInSetIgnoredItem) {
+TEST(AXTreeTest, SetSizePosInSetIgnoredItem) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(3);
@@ -4083,7 +4084,7 @@ TEST(AXTreeTest, TestSetSizePosInSetIgnoredItem) {
// Tests that kPopUpButtons are assigned the SetSize of the wrapped
// kMenuListPopup, if one is present.
-TEST(AXTreeTest, TestSetSizePosInSetPopUpButton) {
+TEST(AXTreeTest, SetSizePosInSetPopUpButton) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(6);
@@ -4114,7 +4115,7 @@ TEST(AXTreeTest, TestSetSizePosInSetPopUpButton) {
// Tests that PosInSet and SetSize are still correctly calculated when there
// are nodes with role of kUnknown layered between items and ordered set.
-TEST(AXTreeTest, TestSetSizePosInSetUnkown) {
+TEST(AXTreeTest, SetSizePosInSetUnkown) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(5);
@@ -4143,7 +4144,7 @@ TEST(AXTreeTest, TestSetSizePosInSetUnkown) {
EXPECT_OPTIONAL_EQ(2, item2->GetSetSize());
}
-TEST(AXTreeTest, TestSetSizePosInSetMenuItemValidChildOfMenuListPopup) {
+TEST(AXTreeTest, SetSizePosInSetMenuItemValidChildOfMenuListPopup) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(3);
@@ -4166,7 +4167,7 @@ TEST(AXTreeTest, TestSetSizePosInSetMenuItemValidChildOfMenuListPopup) {
EXPECT_OPTIONAL_EQ(2, item2->GetSetSize());
}
-TEST(AXTreeTest, TestSetSizePostInSetListBoxOptionWithGroup) {
+TEST(AXTreeTest, SetSizePostInSetListBoxOptionWithGroup) {
AXTreeUpdate initial_state;
initial_state.root_id = 1;
initial_state.nodes.resize(7);
@@ -4191,16 +4192,212 @@ TEST(AXTreeTest, TestSetSizePostInSetListBoxOptionWithGroup) {
AXNode* listbox_option1 = tree.GetFromId(4);
EXPECT_OPTIONAL_EQ(1, listbox_option1->GetPosInSet());
- EXPECT_OPTIONAL_EQ(2, listbox_option1->GetSetSize());
+ EXPECT_OPTIONAL_EQ(4, listbox_option1->GetSetSize());
AXNode* listbox_option2 = tree.GetFromId(5);
EXPECT_OPTIONAL_EQ(2, listbox_option2->GetPosInSet());
- EXPECT_OPTIONAL_EQ(2, listbox_option2->GetSetSize());
+ EXPECT_OPTIONAL_EQ(4, listbox_option2->GetSetSize());
AXNode* listbox_option3 = tree.GetFromId(6);
- EXPECT_OPTIONAL_EQ(1, listbox_option3->GetPosInSet());
- EXPECT_OPTIONAL_EQ(2, listbox_option3->GetSetSize());
+ EXPECT_OPTIONAL_EQ(3, listbox_option3->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, listbox_option3->GetSetSize());
AXNode* listbox_option4 = tree.GetFromId(7);
- EXPECT_OPTIONAL_EQ(2, listbox_option4->GetPosInSet());
- EXPECT_OPTIONAL_EQ(2, listbox_option4->GetSetSize());
+ EXPECT_OPTIONAL_EQ(4, listbox_option4->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, listbox_option4->GetSetSize());
+}
+
+TEST(AXTreeTest, SetSizePosInSetGroup) {
+ // The behavior of a group changes depending on the context it appears in
+ // i.e. if it appears alone vs. if it is contained within another set-like
+ // element. The below example shows a group standing alone:
+ //
+ // <ul role="group"> <!-- SetSize = 3 -->
+ // <li role="menuitemradio" aria-checked="true">Small</li>
+ // <li role="menuitemradio" aria-checked="false">Medium</li>
+ // <li role="menuitemradio" aria-checked="false">Large</li>
+ // </ul>
+ //
+ // However, when it is contained within another set-like element, like a
+ // listbox, it should simply act like a generic container:
+ //
+ // <div role="listbox"> <!-- SetSize = 3 -->
+ // <div role="option">Red</div> <!-- 1 of 3 -->
+ // <div role="option">Yellow</div> <!-- 2 of 3 -->
+ // <div role="group"> <!-- SetSize = 0 -->
+ // <div role="option">Blue</div> <!-- 3 of 3 -->
+ // </div>
+ // </div>
+ //
+ // Please note: the GetPosInSet and GetSetSize functions take slightly
+ // different code paths when initially run on items vs. the container.
+ // Exercise both code paths in this test.
+
+ AXTreeUpdate tree_update;
+ tree_update.root_id = 1;
+ tree_update.nodes.resize(6);
+ tree_update.nodes[0].id = 1;
+ tree_update.nodes[0].role = ax::mojom::Role::kMenu; // SetSize = 4
+ tree_update.nodes[0].child_ids = {2, 6};
+ tree_update.nodes[1].id = 2;
+ tree_update.nodes[1].role = ax::mojom::Role::kGroup; // SetSize = 0
+ tree_update.nodes[1].child_ids = {3, 4, 5};
+ tree_update.nodes[2].id = 3;
+ tree_update.nodes[2].role = ax::mojom::Role::kMenuItemRadio; // 1 of 4
+ tree_update.nodes[3].id = 4;
+ tree_update.nodes[3].role = ax::mojom::Role::kMenuItemRadio; // 2 of 4
+ tree_update.nodes[4].id = 5;
+ tree_update.nodes[4].role = ax::mojom::Role::kMenuItemRadio; // 3 of 4
+ tree_update.nodes[5].id = 6;
+ tree_update.nodes[5].role = ax::mojom::Role::kMenuItemRadio; // 4 of 4
+ AXTree tree(tree_update);
+
+ // Get data on kMenu first.
+ AXNode* menu = tree.GetFromId(1);
+ EXPECT_OPTIONAL_EQ(4, menu->GetSetSize());
+ AXNode* group = tree.GetFromId(2);
+ EXPECT_FALSE(group->GetSetSize());
+ // The below values should have already been computed and cached.
+ AXNode* item1 = tree.GetFromId(3);
+ EXPECT_OPTIONAL_EQ(1, item1->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, item1->GetSetSize());
+ AXNode* item4 = tree.GetFromId(6);
+ EXPECT_OPTIONAL_EQ(4, item4->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, item4->GetSetSize());
+
+ AXTreeUpdate next_tree_update;
+ next_tree_update.root_id = 1;
+ next_tree_update.nodes.resize(6);
+ next_tree_update.nodes[0].id = 1;
+ next_tree_update.nodes[0].role = ax::mojom::Role::kListBox; // SetSize = 4
+ next_tree_update.nodes[0].child_ids = {2, 6};
+ next_tree_update.nodes[1].id = 2;
+ next_tree_update.nodes[1].role = ax::mojom::Role::kGroup; // SetSize = 0
+ next_tree_update.nodes[1].child_ids = {3, 4, 5};
+ next_tree_update.nodes[2].id = 3;
+ next_tree_update.nodes[2].role = ax::mojom::Role::kListBoxOption; // 1 of 4
+ next_tree_update.nodes[3].id = 4;
+ next_tree_update.nodes[3].role = ax::mojom::Role::kListBoxOption; // 2 of 4
+ next_tree_update.nodes[4].id = 5;
+ next_tree_update.nodes[4].role = ax::mojom::Role::kListBoxOption; // 3 of 4
+ next_tree_update.nodes[5].id = 6;
+ next_tree_update.nodes[5].role = ax::mojom::Role::kListBoxOption; // 4 of 4
+ AXTree next_tree(next_tree_update);
+
+ // Get data on kListBoxOption first.
+ AXNode* option1 = next_tree.GetFromId(3);
+ EXPECT_OPTIONAL_EQ(1, option1->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option1->GetSetSize());
+ AXNode* option2 = next_tree.GetFromId(4);
+ EXPECT_OPTIONAL_EQ(2, option2->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option2->GetSetSize());
+ AXNode* option3 = next_tree.GetFromId(5);
+ EXPECT_OPTIONAL_EQ(3, option3->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option3->GetSetSize());
+ AXNode* option4 = next_tree.GetFromId(6);
+ EXPECT_OPTIONAL_EQ(4, option4->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option4->GetSetSize());
+ AXNode* next_group = next_tree.GetFromId(2);
+ EXPECT_FALSE(next_group->GetSetSize());
+ // The below value should have already been computed and cached.
+ AXNode* listbox = next_tree.GetFromId(1);
+ EXPECT_OPTIONAL_EQ(4, listbox->GetSetSize());
+
+ // Standalone groups are allowed.
+ AXTreeUpdate third_tree_update;
+ third_tree_update.root_id = 1;
+ third_tree_update.nodes.resize(3);
+ third_tree_update.nodes[0].id = 1;
+ third_tree_update.nodes[0].role = ax::mojom::Role::kGroup;
+ third_tree_update.nodes[0].child_ids = {2, 3};
+ third_tree_update.nodes[1].id = 2;
+ third_tree_update.nodes[1].role = ax::mojom::Role::kListItem;
+ third_tree_update.nodes[2].id = 3;
+ third_tree_update.nodes[2].role = ax::mojom::Role::kListItem;
+ AXTree third_tree(third_tree_update);
+
+ // Ensure that groups can't also stand alone.
+ AXNode* last_group = third_tree.GetFromId(1);
+ EXPECT_OPTIONAL_EQ(2, last_group->GetSetSize());
+ AXNode* list_item1 = third_tree.GetFromId(2);
+ EXPECT_OPTIONAL_EQ(1, list_item1->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(2, list_item1->GetSetSize());
+ AXNode* list_item2 = third_tree.GetFromId(3);
+ EXPECT_OPTIONAL_EQ(2, list_item2->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(2, list_item2->GetSetSize());
+
+ // Test nested groups.
+ AXTreeUpdate last_tree_update;
+ last_tree_update.root_id = 1;
+ last_tree_update.nodes.resize(6);
+ last_tree_update.nodes[0].id = 1;
+ last_tree_update.nodes[0].role = ax::mojom::Role::kMenuBar;
+ last_tree_update.nodes[0].child_ids = {2};
+ last_tree_update.nodes[1].id = 2;
+ last_tree_update.nodes[1].role = ax::mojom::Role::kGroup;
+ last_tree_update.nodes[1].child_ids = {3, 4};
+ last_tree_update.nodes[2].id = 3;
+ last_tree_update.nodes[2].role = ax::mojom::Role::kMenuItemCheckBox;
+ last_tree_update.nodes[3].id = 4;
+ last_tree_update.nodes[3].role = ax::mojom::Role::kGroup;
+ last_tree_update.nodes[3].child_ids = {5, 6};
+ last_tree_update.nodes[4].id = 5;
+ last_tree_update.nodes[4].role = ax::mojom::Role::kMenuItemCheckBox;
+ last_tree_update.nodes[5].id = 6;
+ last_tree_update.nodes[5].role = ax::mojom::Role::kMenuItemCheckBox;
+ AXTree last_tree(last_tree_update);
+
+ AXNode* checkbox1 = last_tree.GetFromId(3);
+ EXPECT_OPTIONAL_EQ(1, checkbox1->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(3, checkbox1->GetSetSize());
+ AXNode* checkbox2 = last_tree.GetFromId(5);
+ EXPECT_OPTIONAL_EQ(2, checkbox2->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(3, checkbox2->GetSetSize());
+ AXNode* checkbox3 = last_tree.GetFromId(6);
+ EXPECT_OPTIONAL_EQ(3, checkbox3->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(3, checkbox3->GetSetSize());
+ AXNode* menu_bar = last_tree.GetFromId(1);
+ EXPECT_OPTIONAL_EQ(3, menu_bar->GetSetSize());
+ AXNode* outer_group = last_tree.GetFromId(2);
+ EXPECT_FALSE(outer_group->GetSetSize());
+ AXNode* inner_group = last_tree.GetFromId(4);
+ EXPECT_FALSE(inner_group->GetSetSize());
+}
+
+TEST(AXTreeTest, SetSizePosInSetHidden) {
+ AXTreeUpdate tree_update;
+ tree_update.root_id = 1;
+ tree_update.nodes.resize(6);
+ tree_update.nodes[0].id = 1;
+ tree_update.nodes[0].role = ax::mojom::Role::kListBox; // SetSize = 4
+ tree_update.nodes[0].child_ids = {2, 3, 4, 5, 6};
+ tree_update.nodes[1].id = 2;
+ tree_update.nodes[1].role = ax::mojom::Role::kListBoxOption; // 1 of 4
+ tree_update.nodes[2].id = 3;
+ tree_update.nodes[2].role = ax::mojom::Role::kListBoxOption; // 2 of 4
+ tree_update.nodes[3].id = 4;
+ tree_update.nodes[3].role = ax::mojom::Role::kListBoxOption; // Hidden
+ tree_update.nodes[3].AddState(ax::mojom::State::kInvisible);
+ tree_update.nodes[4].id = 5;
+ tree_update.nodes[4].role = ax::mojom::Role::kListBoxOption; // 3 of 4
+ tree_update.nodes[5].id = 6;
+ tree_update.nodes[5].role = ax::mojom::Role::kListBoxOption; // 4 of 4
+ AXTree tree(tree_update);
+
+ AXNode* list_box = tree.GetFromId(1);
+ EXPECT_OPTIONAL_EQ(4, list_box->GetSetSize());
+ AXNode* option1 = tree.GetFromId(2);
+ EXPECT_OPTIONAL_EQ(1, option1->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option1->GetSetSize());
+ AXNode* option2 = tree.GetFromId(3);
+ EXPECT_OPTIONAL_EQ(2, option2->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option2->GetSetSize());
+ AXNode* option_hidden = tree.GetFromId(4);
+ EXPECT_FALSE(option_hidden->GetPosInSet());
+ EXPECT_FALSE(option_hidden->GetSetSize());
+ AXNode* option3 = tree.GetFromId(5);
+ EXPECT_OPTIONAL_EQ(3, option3->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option3->GetSetSize());
+ AXNode* option4 = tree.GetFromId(6);
+ EXPECT_OPTIONAL_EQ(4, option4->GetPosInSet());
+ EXPECT_OPTIONAL_EQ(4, option4->GetSetSize());
}
TEST(AXTreeTest, OnNodeWillBeDeletedHasValidUnignoredParent) {
diff --git a/chromium/ui/accessibility/ax_tree_update.h b/chromium/ui/accessibility/ax_tree_update.h
index 5225cdefb87..a27fc3b2377 100644
--- a/chromium/ui/accessibility/ax_tree_update.h
+++ b/chromium/ui/accessibility/ax_tree_update.h
@@ -13,7 +13,9 @@
#include <vector>
#include "base/strings/string_number_conversions.h"
+#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_enums.mojom.h"
+#include "ui/accessibility/ax_event_intent.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_data.h"
@@ -72,9 +74,12 @@ template<typename AXNodeData, typename AXTreeData> struct AXTreeUpdateBase {
// A vector of nodes to update, according to the rules above.
std::vector<AXNodeData> nodes;
- // The source of the event.
+ // The source of the event which generated this tree update.
ax::mojom::EventFrom event_from = ax::mojom::EventFrom::kNone;
+ // The event intents associated with this tree update.
+ std::vector<AXEventIntent> event_intents;
+
// Return a multi-line indented string representation, for logging.
std::string ToString() const;
@@ -100,6 +105,16 @@ std::string AXTreeUpdateBase<AXNodeData, AXTreeData>::ToString() const {
result += "AXTreeUpdate: root id " + base::NumberToString(root_id) + "\n";
}
+ if (event_from != ax::mojom::EventFrom::kNone)
+ result += "event_from=" + std::string(ui::ToString(event_from)) + "\n";
+
+ if (!event_intents.empty()) {
+ result += "event_intents=[\n";
+ for (const auto& event_intent : event_intents)
+ result += " " + event_intent.ToString() + "\n";
+ result += "]\n";
+ }
+
// The challenge here is that we want to indent the nodes being updated
// so that parent/child relationships are clear, but we don't have access
// to the rest of the tree for context, so we have to try to show the
diff --git a/chromium/ui/accessibility/extensions/chromevoxclassic/BUILD.gn b/chromium/ui/accessibility/extensions/chromevoxclassic/BUILD.gn
index a2f3cec09e6..dfecb666555 100644
--- a/chromium/ui/accessibility/extensions/chromevoxclassic/BUILD.gn
+++ b/chromium/ui/accessibility/extensions/chromevoxclassic/BUILD.gn
@@ -4,7 +4,7 @@
import("//build/config/features.gni")
import(
- "//chrome/browser/resources/chromeos/accessibility/chromevox/run_jsbundler.gni")
+ "//chrome/browser/resources/chromeos/accessibility/common/run_jsbundler.gni")
import(
"//chrome/browser/resources/chromeos/accessibility/strings/accessibility_strings.gni")
import("//chrome/common/features.gni")
@@ -586,8 +586,8 @@ js2gtest("chromevox_unitjs_tests") {
"walkers/word_walker_test.unitjs",
]
gen_include_files = [
- "//chrome/browser/resources/chromeos/accessibility/chromevox/testing/assert_additions.js",
- "//chrome/browser/resources/chromeos/accessibility/chromevox/testing/callback_helper.js",
+ "//chrome/browser/resources/chromeos/accessibility/common/testing/assert_additions.js",
+ "//chrome/browser/resources/chromeos/accessibility/common/testing/callback_helper.js",
"//ui/accessibility/extensions/chromevoxclassic/testing/chromevox_unittest_base.js",
]
diff --git a/chromium/ui/accessibility/extensions/chromevoxclassic/testing/chromevox_unittest_base.js b/chromium/ui/accessibility/extensions/chromevoxclassic/testing/chromevox_unittest_base.js
index 29e01eb3824..01fcc5b6bdb 100644
--- a/chromium/ui/accessibility/extensions/chromevoxclassic/testing/chromevox_unittest_base.js
+++ b/chromium/ui/accessibility/extensions/chromevoxclassic/testing/chromevox_unittest_base.js
@@ -3,11 +3,14 @@
// found in the LICENSE file.
GEN_INCLUDE([
- '//chrome/browser/resources/chromeos/accessibility/chromevox/testing/assert_additions.js'
+ '//chrome/browser/resources/chromeos/accessibility/common/testing/' +
+ 'assert_additions.js'
]);
GEN_INCLUDE([
- '//chrome/browser/resources/chromeos/accessibility/chromevox/testing/common.js',
- '//chrome/browser/resources/chromeos/accessibility/chromevox/testing/callback_helper.js'
+ '//chrome/browser/resources/chromeos/accessibility/chromevox/testing/' +
+ 'common.js',
+ '//chrome/browser/resources/chromeos/accessibility/common/testing/' +
+ 'callback_helper.js'
]);
/**
diff --git a/chromium/ui/accessibility/mojom/ax_tree_update.mojom b/chromium/ui/accessibility/mojom/ax_tree_update.mojom
index 495f88158ea..957edd556c0 100644
--- a/chromium/ui/accessibility/mojom/ax_tree_update.mojom
+++ b/chromium/ui/accessibility/mojom/ax_tree_update.mojom
@@ -5,6 +5,7 @@
module ax.mojom;
import "ui/accessibility/ax_enums.mojom";
+import "ui/accessibility/mojom/ax_event_intent.mojom";
import "ui/accessibility/mojom/ax_node_data.mojom";
import "ui/accessibility/mojom/ax_tree_data.mojom";
@@ -16,4 +17,5 @@ struct AXTreeUpdate {
int32 root_id;
array<AXNodeData> nodes;
ax.mojom.EventFrom event_from;
+ array<EventIntent> event_intents;
};
diff --git a/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc b/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc
index 159aa36b138..e429ce978d3 100644
--- a/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc
+++ b/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.cc
@@ -18,7 +18,7 @@ bool StructTraits<ax::mojom::AXTreeUpdateDataView, ui::AXTreeUpdate>::Read(
if (!data.ReadNodes(&out->nodes))
return false;
out->event_from = data.event_from();
- return true;
+ return data.ReadEventIntents(&out->event_intents);
}
} // namespace mojo
diff --git a/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.h b/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.h
index 18a8bb8af1e..480c07aca8f 100644
--- a/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.h
+++ b/chromium/ui/accessibility/mojom/ax_tree_update_mojom_traits.h
@@ -5,7 +5,10 @@
#ifndef UI_ACCESSIBILITY_MOJOM_AX_TREE_UPDATE_MOJOM_TRAITS_H_
#define UI_ACCESSIBILITY_MOJOM_AX_TREE_UPDATE_MOJOM_TRAITS_H_
+#include "ui/accessibility/ax_event_intent.h"
#include "ui/accessibility/ax_tree_update.h"
+#include "ui/accessibility/mojom/ax_event_intent.mojom.h"
+#include "ui/accessibility/mojom/ax_event_intent_mojom_traits.h"
#include "ui/accessibility/mojom/ax_node_data_mojom_traits.h"
#include "ui/accessibility/mojom/ax_tree_data_mojom_traits.h"
#include "ui/accessibility/mojom/ax_tree_update.mojom-shared.h"
@@ -30,6 +33,10 @@ struct StructTraits<ax::mojom::AXTreeUpdateDataView, ui::AXTreeUpdate> {
static ax::mojom::EventFrom event_from(const ui::AXTreeUpdate& p) {
return p.event_from;
}
+ static std::vector<ui::AXEventIntent> event_intents(
+ const ui::AXTreeUpdate& p) {
+ return p.event_intents;
+ }
static bool Read(ax::mojom::AXTreeUpdateDataView data, ui::AXTreeUpdate* out);
};
diff --git a/chromium/ui/accessibility/platform/BUILD.gn b/chromium/ui/accessibility/platform/BUILD.gn
new file mode 100644
index 00000000000..691a701457d
--- /dev/null
+++ b/chromium/ui/accessibility/platform/BUILD.gn
@@ -0,0 +1,142 @@
+# Copyright 2020 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.
+
+import("//build/config/features.gni")
+import("//build/config/jumbo.gni")
+import("//build/config/linux/pkg_config.gni")
+import("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+import("//testing/libfuzzer/fuzzer_test.gni")
+import("//testing/test.gni")
+import("//tools/json_schema_compiler/json_schema_api.gni")
+import("//ui/base/ui_features.gni")
+
+if (is_win) {
+ import("//build/toolchain/win/midl.gni")
+}
+
+if (is_win) {
+ midl("ichromeaccessible") {
+ sources = [ "ichromeaccessible.idl" ]
+ }
+}
+
+if (is_android) {
+ import("//build/config/android/rules.gni")
+}
+
+source_set("platform") {
+ defines = [ "AX_IMPLEMENTATION" ]
+
+ visibility = [ "//ui/accessibility" ]
+
+ sources = [
+ # Used by by browser_accessibility_state_impl.cc.
+ "ax_platform_node.cc",
+ "ax_platform_node.h",
+ "ax_platform_node_delegate.h",
+
+ # Used by browser_accessibility.cc.
+ "ax_unique_id.cc",
+ "ax_unique_id.h",
+
+ # Used by accessibility_tree_formatter_blink.cc.
+ "compute_attributes.cc",
+ "compute_attributes.h",
+
+ # Used by //ui/accessibility:ax_assistant.
+ "ax_android_constants.cc",
+ "ax_android_constants.h",
+
+ # Used by //ui/views/views/ax_virtual_view.h.
+ "ax_platform_node_base.cc",
+ "ax_platform_node_base.h",
+ "ax_platform_node_delegate_base.cc",
+ "ax_platform_node_delegate_base.h",
+
+ # Used by //chrome/test/browser_tests/browser_view_browsertest.cc
+ "ax_platform_node_test_helper.cc",
+ "ax_platform_node_test_helper.h",
+ ]
+
+ public_deps = [
+ "//ui/accessibility:ax_base",
+ "//ui/display",
+ ]
+
+ if (has_native_accessibility) {
+ sources += [
+ "ax_fragment_root_delegate_win.h",
+ "ax_fragment_root_win.cc",
+ "ax_fragment_root_win.h",
+ "ax_platform_node_delegate_utils_win.cc",
+ "ax_platform_node_delegate_utils_win.h",
+ "ax_platform_node_textchildprovider_win.cc",
+ "ax_platform_node_textchildprovider_win.h",
+ "ax_platform_node_textprovider_win.cc",
+ "ax_platform_node_textprovider_win.h",
+ "ax_platform_node_textrangeprovider_win.cc",
+ "ax_platform_node_textrangeprovider_win.h",
+ "ax_platform_node_win.cc",
+ "ax_platform_node_win.h",
+ "ax_platform_relation_win.cc",
+ "ax_platform_relation_win.h",
+ "ax_platform_text_boundary.cc",
+ "ax_platform_text_boundary.h",
+ "ax_system_caret_win.cc",
+ "ax_system_caret_win.h",
+ "uia_registrar_win.cc",
+ "uia_registrar_win.h",
+ ]
+
+ if (is_win) {
+ public_deps += [
+ "//third_party/iaccessible2",
+ "//ui/accessibility/platform:ichromeaccessible",
+ ]
+
+ libs = [
+ "oleacc.lib",
+ "uiautomationcore.lib",
+ ]
+ }
+
+ if (is_mac) {
+ sources += [
+ "ax_platform_node_mac.h",
+ "ax_platform_node_mac.mm",
+ ]
+
+ libs = [
+ "AppKit.framework",
+ "Foundation.framework",
+ ]
+ }
+
+ if (use_atk) {
+ sources += [
+ "atk_util_auralinux.cc",
+ "atk_util_auralinux.h",
+ "atk_util_auralinux_gtk.cc",
+ "ax_platform_atk_hyperlink.cc",
+ "ax_platform_atk_hyperlink.h",
+ "ax_platform_node_auralinux.cc",
+ "ax_platform_node_auralinux.h",
+ ]
+
+ # ax_platform_text_boundary.h includes atk.h, so ATK is needed
+ # as a public config to ensure anything that includes this is
+ # able to find atk.h.
+ public_configs = [ "//build/config/linux/atk" ]
+
+ if (use_glib) {
+ configs += [ "//build/config/linux:glib" ]
+ }
+
+ if (use_x11) {
+ public_deps += [ "//ui/gfx/x" ]
+ }
+ }
+ }
+}
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
index f1925832840..163fb8446dd 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -7,17 +7,21 @@
#include <unordered_map>
#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/base/win/atl_module.h"
namespace ui {
class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
+ public IItemContainerProvider,
public IRawElementProviderFragmentRoot,
public IRawElementProviderAdviseEvents {
public:
BEGIN_COM_MAP(AXFragmentRootPlatformNodeWin)
+ COM_INTERFACE_ENTRY(IItemContainerProvider)
COM_INTERFACE_ENTRY(IRawElementProviderFragmentRoot)
COM_INTERFACE_ENTRY(IRawElementProviderAdviseEvents)
COM_INTERFACE_ENTRY_CHAIN(AXPlatformNodeWin)
@@ -38,19 +42,76 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
}
//
+ // IItemContainerProvider methods.
+ //
+ IFACEMETHODIMP FindItemByProperty(
+ IRawElementProviderSimple* start_after_element,
+ PROPERTYID property_id,
+ VARIANT value,
+ IRawElementProviderSimple** result) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ITEMCONTAINER_FINDITEMBYPROPERTY);
+ UIA_VALIDATE_CALL_1_ARG(result);
+ *result = nullptr;
+
+ // We currently only support the custom UIA property ID for unique id and we
+ // ignore |start_after_element|.
+ if (property_id ==
+ UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId() &&
+ value.vt == VT_BSTR) {
+ // TODO: We should support the case when |start_after_element| isn't
+ // nullptr for unique id (https://crbug.com/1098160).
+ if (start_after_element)
+ return E_INVALIDARG;
+
+ int32_t ax_unique_id;
+ if (!base::StringToInt(value.bstrVal, &ax_unique_id))
+ return S_OK;
+
+ // In the Windows accessibility platform implementation, id 0 represents
+ // self; a positive id represents the immediate descendants; and a
+ // negative id represents a unique id that can be mapped to any node.
+ if (AXPlatformNodeWin* node_win =
+ static_cast<AXPlatformNodeWin*>(GetFromUniqueId(-ax_unique_id))) {
+ node_win->QueryInterface(IID_PPV_ARGS(result));
+ }
+
+ return S_OK;
+ }
+
+ return E_INVALIDARG;
+ }
+
+ //
// IRawElementProviderSimple methods.
//
IFACEMETHODIMP get_HostRawElementProvider(
IRawElementProviderSimple** host_element_provider) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_HOST_RAW_ELEMENT_PROVIDER);
UIA_VALIDATE_CALL_1_ARG(host_element_provider);
HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
return UiaHostProviderFromHwnd(hwnd, host_element_provider);
}
+ IFACEMETHODIMP GetPatternProvider(PATTERNID pattern_id,
+ IUnknown** result) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PATTERN_PROVIDER);
+ UIA_VALIDATE_CALL_1_ARG(result);
+ *result = nullptr;
+
+ if (pattern_id == UIA_ItemContainerPatternId) {
+ AddRef();
+ *result = static_cast<IItemContainerProvider*>(this);
+ return S_OK;
+ }
+
+ return AXPlatformNodeWin::GetPatternProviderImpl(pattern_id, result);
+ }
+
IFACEMETHODIMP GetPropertyValue(PROPERTYID property_id,
VARIANT* result) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PROPERTY_VALUE);
UIA_VALIDATE_CALL_1_ARG(result);
switch (property_id) {
@@ -84,6 +145,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
IFACEMETHODIMP get_FragmentRoot(
IRawElementProviderFragmentRoot** fragment_root) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FRAGMENTROOT);
UIA_VALIDATE_CALL_1_ARG(fragment_root);
QueryInterface(IID_PPV_ARGS(fragment_root));
@@ -97,6 +159,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
double screen_physical_pixel_x,
double screen_physical_pixel_y,
IRawElementProviderFragment** element_provider) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ELEMENT_PROVIDER_FROM_POINT);
UIA_VALIDATE_CALL_1_ARG(element_provider);
*element_provider = nullptr;
@@ -124,6 +187,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
}
IFACEMETHODIMP GetFocus(IRawElementProviderFragment** focus) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FOCUS);
UIA_VALIDATE_CALL_1_ARG(focus);
*focus = nullptr;
@@ -157,6 +221,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
//
IFACEMETHODIMP AdviseEventAdded(EVENTID event_id,
SAFEARRAY* property_ids) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_ADDED);
if (event_id == UIA_LiveRegionChangedEventId) {
live_region_change_listeners_++;
@@ -179,6 +244,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
IFACEMETHODIMP AdviseEventRemoved(EVENTID event_id,
SAFEARRAY* property_ids) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_REMOVED);
if (event_id == UIA_LiveRegionChangedEventId) {
DCHECK(live_region_change_listeners_ > 0);
live_region_change_listeners_--;
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc b/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
index 7691d12cece..e26ad0446c1 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "ui/accessibility/platform/ax_fragment_root_win.h"
+#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/accessibility/platform/ax_platform_node_win_unittest.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h"
@@ -14,11 +15,26 @@
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
+using base::win::ScopedVariant;
using Microsoft::WRL::ComPtr;
namespace ui {
+#define EXPECT_UIA_BSTR_EQ(node, property_id, expected) \
+ { \
+ ScopedVariant expectedVariant(expected); \
+ ASSERT_EQ(VT_BSTR, expectedVariant.type()); \
+ ASSERT_NE(nullptr, expectedVariant.ptr()->bstrVal); \
+ ScopedVariant actual; \
+ ASSERT_HRESULT_SUCCEEDED( \
+ node->GetPropertyValue(property_id, actual.Receive())); \
+ ASSERT_EQ(VT_BSTR, actual.type()); \
+ ASSERT_NE(nullptr, actual.ptr()->bstrVal); \
+ EXPECT_STREQ(expectedVariant.ptr()->bstrVal, actual.ptr()->bstrVal); \
+ }
+
class AXFragmentRootTest : public AXPlatformNodeWinTest {
public:
AXFragmentRootTest() = default;
@@ -27,6 +43,92 @@ class AXFragmentRootTest : public AXPlatformNodeWinTest {
AXFragmentRootTest& operator=(const AXFragmentRootTest&) = delete;
};
+TEST_F(AXFragmentRootTest, UIAFindItemByProperty) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("root");
+ root.child_ids = {2, 3};
+
+ AXNodeData text1;
+ text1.id = 2;
+ text1.role = ax::mojom::Role::kStaticText;
+ text1.SetName("text1");
+
+ AXNodeData button;
+ button.id = 3;
+ button.role = ax::mojom::Role::kButton;
+ button.SetName("button");
+ button.child_ids = {4};
+
+ AXNodeData text2;
+ text2.id = 4;
+ text2.role = ax::mojom::Role::kStaticText;
+ text2.SetName("text2");
+
+ Init(root, text1, button, text2);
+ InitFragmentRoot();
+
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple;
+ ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
+ IID_PPV_ARGS(&raw_element_provider_simple));
+
+ ComPtr<IItemContainerProvider> item_container_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_ItemContainerPatternId, &item_container_provider));
+ ASSERT_NE(nullptr, item_container_provider.Get());
+
+ // Fetch the AxUniqueId of "root", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ ScopedVariant unique_id_variant;
+ int32_t unique_id = AXPlatformNodeFromNode(GetRootAsAXNode())->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ ComPtr<IRawElementProviderSimple> result;
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"root");
+ result.Reset();
+ unique_id_variant.Release();
+
+ // Fetch the AxUniqueId of "text1", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ unique_id =
+ AXPlatformNodeFromNode(GetRootAsAXNode()->children()[0])->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"text1");
+ result.Reset();
+ unique_id_variant.Release();
+
+ // Fetch the AxUniqueId of "button", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ AXNode* button_node = GetRootAsAXNode()->children()[1];
+ unique_id = AXPlatformNodeFromNode(button_node)->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"button");
+ result.Reset();
+ unique_id_variant.Release();
+
+ // Fetch the AxUniqueId of "text2", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ unique_id = AXPlatformNodeFromNode(button_node->children()[0])->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"text2");
+}
+
TEST_F(AXFragmentRootTest, TestUIAGetFragmentRoot) {
AXNodeData root;
Init(root);
@@ -227,7 +329,7 @@ TEST_F(AXFragmentRootTest, TestGetPropertyValue) {
// IsControlElement and IsContentElement should follow the setting on the
// fragment root delegate.
test_fragment_root_delegate_->is_control_element_ = true;
- base::win::ScopedVariant result;
+ ScopedVariant result;
EXPECT_HRESULT_SUCCEEDED(root_provider->GetPropertyValue(
UIA_IsControlElementPropertyId, result.Receive()));
EXPECT_EQ(result.type(), VT_BOOL);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node.h b/chromium/ui/accessibility/platform/ax_platform_node.h
index c5198fc792e..9621fcb9a5c 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node.h
@@ -92,7 +92,7 @@ class AX_EXPORT AXPlatformNode {
// Return true if this object is equal to or a descendant of |ancestor|.
virtual bool IsDescendantOf(AXPlatformNode* ancestor) const = 0;
- // Return the unique ID
+ // Return the unique ID.
int32_t GetUniqueId() const;
// Creates a string representation of this node's data.
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 89309020096..a4554aaac25 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -17,6 +17,7 @@
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/debug/leak_annotations.h"
+#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/numerics/ranges.h"
#include "base/optional.h"
@@ -73,6 +74,29 @@ namespace ui {
namespace {
+// IMPORTANT!
+// These values are written to logs. Do not renumber or delete
+// existing items; add new entries to the end of the list.
+enum class UmaAtkApi {
+ kGetName = 0,
+ kGetDescription = 1,
+ kGetNChildren = 2,
+ kRefChild = 3,
+ kGetIndexInParent = 4,
+ kGetParent = 5,
+ kRefRelationSet = 6,
+ kGetAttributes = 7,
+ kGetRole = 8,
+ kRefStateSet = 9,
+ // This must always be the last enum. It's okay for its value to
+ // increase, but none of the other enum values may change.
+ kMaxValue = kRefStateSet,
+};
+
+void RecordAccessibilityAtkApi(UmaAtkApi enum_value) {
+ UMA_HISTOGRAM_ENUMERATION("Accessibility.ATK-APIs", enum_value);
+}
+
// When accepting input from clients calling the API, an ATK character offset
// of -1 can often represent the length of the string.
static const int kStringLengthOffset = -1;
@@ -186,13 +210,17 @@ void SetIntPointerValueIfNotNull(int* pointer, int value) {
*pointer = value;
}
+#if defined(ATK_230)
bool SupportsAtkComponentScrollingInterface() {
return dlsym(RTLD_DEFAULT, "atk_component_scroll_to_point");
}
+#endif
+#if defined(ATK_232)
bool SupportsAtkTextScrollingInterface() {
return dlsym(RTLD_DEFAULT, "atk_text_scroll_substring_to_point");
}
+#endif
AtkObject* FindAtkObjectParentFrame(AtkObject* atk_object) {
AXPlatformNodeAuraLinux* node =
@@ -1246,6 +1274,7 @@ char* GetStringAtOffset(AtkText* atk_text,
}
#endif
+#if defined(ATK_230)
gfx::Rect GetUnclippedParentHypertextRangeBoundsRect(
AXPlatformNodeDelegate* ax_platform_node_delegate,
const int start_offset,
@@ -1269,6 +1298,7 @@ gfx::Rect GetUnclippedParentHypertextRangeBoundsRect(
AXClippingBehavior::kClipped)
.OffsetFromOrigin();
}
+#endif
void GetCharacterExtents(AtkText* atk_text,
int offset,
@@ -2028,6 +2058,7 @@ const gchar* GetName(AtkObject* atk_object) {
}
const gchar* AtkGetName(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetName);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetName(atk_object);
}
@@ -2045,6 +2076,7 @@ const gchar* GetDescription(AtkObject* atk_object) {
}
const gchar* AtkGetDescription(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetDescription);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetDescription(atk_object);
}
@@ -2061,6 +2093,7 @@ gint GetNChildren(AtkObject* atk_object) {
}
gint AtkGetNChildren(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetNChildren);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetNChildren(atk_object);
}
@@ -2083,6 +2116,7 @@ AtkObject* RefChild(AtkObject* atk_object, gint index) {
}
AtkObject* AtkRefChild(AtkObject* atk_object, gint index) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kRefChild);
AXPlatformNodeAuraLinux::EnableAXMode();
return RefChild(atk_object, index);
}
@@ -2099,6 +2133,7 @@ gint GetIndexInParent(AtkObject* atk_object) {
}
gint AtkGetIndexInParent(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetIndexInParent);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetIndexInParent(atk_object);
}
@@ -2115,6 +2150,7 @@ AtkObject* GetParent(AtkObject* atk_object) {
}
AtkObject* AtkGetParent(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetParent);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetParent(atk_object);
}
@@ -2130,6 +2166,7 @@ AtkRelationSet* RefRelationSet(AtkObject* atk_object) {
}
AtkRelationSet* AtkRefRelationSet(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kRefRelationSet);
AXPlatformNodeAuraLinux::EnableAXMode();
return RefRelationSet(atk_object);
}
@@ -2146,6 +2183,7 @@ AtkAttributeSet* GetAttributes(AtkObject* atk_object) {
}
AtkAttributeSet* AtkGetAttributes(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetAttributes);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetAttributes(atk_object);
}
@@ -2161,6 +2199,7 @@ AtkRole GetRole(AtkObject* atk_object) {
}
AtkRole AtkGetRole(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetRole);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetRole(atk_object);
}
@@ -2183,6 +2222,7 @@ AtkStateSet* RefStateSet(AtkObject* atk_object) {
}
AtkStateSet* AtkRefStateSet(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kRefStateSet);
AXPlatformNodeAuraLinux::EnableAXMode();
return RefStateSet(atk_object);
}
@@ -2468,6 +2508,8 @@ AtkObject* AXPlatformNodeAuraLinux::CreateAtkObject() {
if (GetData().role != ax::mojom::Role::kApplication &&
!GetAccessibilityMode().has_mode(AXMode::kNativeAPIs))
return nullptr;
+ if (GetDelegate()->IsChildOfLeaf())
+ return nullptr;
EnsureGTypeInit();
interface_mask_ = GetGTypeInterfaceMask(GetData());
GType type = GetAccessibilityGType();
@@ -3000,10 +3042,8 @@ void AXPlatformNodeAuraLinux::GetAtkState(AtkStateSet* atk_state_set) {
static_cast<int32_t>(ax::mojom::InvalidState::kFalse))
atk_state_set_add_state(atk_state_set, ATK_STATE_INVALID_ENTRY);
#if defined(ATK_216)
- if (data.HasIntAttribute(ax::mojom::IntAttribute::kCheckedState) &&
- data.role != ax::mojom::Role::kToggleButton) {
+ if (IsPlatformCheckable())
atk_state_set_add_state(atk_state_set, ATK_STATE_CHECKABLE);
- }
if (data.HasIntAttribute(ax::mojom::IntAttribute::kHasPopup))
atk_state_set_add_state(atk_state_set, ATK_STATE_HAS_POPUP);
#endif
@@ -3217,6 +3257,13 @@ void AXPlatformNodeAuraLinux::Init(AXPlatformNodeDelegate* delegate) {
GetOrCreateAtkObject();
}
+bool AXPlatformNodeAuraLinux::IsPlatformCheckable() const {
+ if (GetData().role == ax::mojom::Role::kToggleButton)
+ return false;
+
+ return AXPlatformNodeBase::IsPlatformCheckable();
+}
+
void AXPlatformNodeAuraLinux::EnsureAtkObjectIsValid() {
if (atk_object_) {
// If the object's role changes and that causes its
@@ -3343,7 +3390,7 @@ void AXPlatformNodeAuraLinux::OnMenuPopupStart() {
atk_object_notify_state_change(parent_frame, ATK_STATE_ACTIVE, TRUE);
}
-void AXPlatformNodeAuraLinux::OnMenuPopupHide() {
+void AXPlatformNodeAuraLinux::OnMenuPopupEnd() {
AtkObject* atk_object = GetOrCreateAtkObject();
AtkObject* parent_frame = FindAtkObjectParentFrame(atk_object);
if (!parent_frame)
@@ -3354,35 +3401,24 @@ void AXPlatformNodeAuraLinux::OnMenuPopupHide() {
// kMenuPopupHide may be called multiple times for the same menu, so only
// remove it if our parent frame matches the most recently opened menu.
std::vector<AtkObject*>& active_menus = GetActiveMenus();
- if (active_menus.empty())
- return;
-
- // When multiple levels of menu are closed at once, they may be hidden out
- // of order. When this happens, we just remove the open menu from the stack.
- if (active_menus.back() != atk_object) {
- auto it = std::find(active_menus.rbegin(), active_menus.rend(), atk_object);
- if (it != active_menus.rend()) {
- // We used a reverse iterator, so we need to convert it into a normal
- // iterator to use it for std::vector::erase(...).
- auto to_remove = --(it.base());
- active_menus.erase(to_remove);
- }
- return;
- }
+ DCHECK(!active_menus.empty())
+ << "Asymmetrical menupopupend events -- too many";
active_menus.pop_back();
-
- // We exit early if the newly activated menu has the same AtkWindow as the
- // previous one.
AtkObject* new_active_item = ComputeActiveTopLevelFrame();
- if (new_active_item == parent_frame)
- return;
- g_signal_emit_by_name(parent_frame, "deactivate");
- atk_object_notify_state_change(parent_frame, ATK_STATE_ACTIVE, FALSE);
- if (new_active_item) {
- g_signal_emit_by_name(new_active_item, "activate");
- atk_object_notify_state_change(new_active_item, ATK_STATE_ACTIVE, TRUE);
+ if (new_active_item != parent_frame) {
+ // Newly activated menu has the different AtkWindow as the previous one.
+ g_signal_emit_by_name(parent_frame, "deactivate");
+ atk_object_notify_state_change(parent_frame, ATK_STATE_ACTIVE, FALSE);
+ if (new_active_item) {
+ g_signal_emit_by_name(new_active_item, "activate");
+ atk_object_notify_state_change(new_active_item, ATK_STATE_ACTIVE, TRUE);
+ }
}
+
+ // All menus are closed.
+ if (active_menus.empty())
+ OnAllMenusEnded();
}
void AXPlatformNodeAuraLinux::ResendFocusSignalsForCurrentlyFocusedNode() {
@@ -3398,7 +3434,8 @@ void AXPlatformNodeAuraLinux::ResendFocusSignalsForCurrentlyFocusedNode() {
atk_object_notify_state_change(focused_node, ATK_STATE_FOCUSED, true);
}
-void AXPlatformNodeAuraLinux::OnMenuPopupEnd() {
+// All menus have closed.
+void AXPlatformNodeAuraLinux::OnAllMenusEnded() {
if (!GetActiveMenus().empty() && g_active_top_level_frame &&
ComputeActiveTopLevelFrame() != g_active_top_level_frame) {
g_signal_emit_by_name(g_active_top_level_frame, "activate");
@@ -3406,8 +3443,8 @@ void AXPlatformNodeAuraLinux::OnMenuPopupEnd() {
TRUE);
}
- ResendFocusSignalsForCurrentlyFocusedNode();
GetActiveMenus().clear();
+ ResendFocusSignalsForCurrentlyFocusedNode();
}
void AXPlatformNodeAuraLinux::OnWindowActivated() {
@@ -3512,16 +3549,25 @@ void AXPlatformNodeAuraLinux::OnFocused() {
SetActiveViewsDialog();
- if (g_current_focused) {
- g_signal_emit_by_name(g_current_focused, "focus-event", false);
- atk_object_notify_state_change(ATK_OBJECT(g_current_focused),
+ AtkObject* old_effective_focus = g_current_active_descendant
+ ? g_current_active_descendant
+ : g_current_focused;
+ if (old_effective_focus) {
+ g_signal_emit_by_name(old_effective_focus, "focus-event", false);
+ atk_object_notify_state_change(ATK_OBJECT(old_effective_focus),
ATK_STATE_FOCUSED, false);
}
SetWeakGPtrToAtkObject(&g_current_focused, atk_object);
- g_signal_emit_by_name(atk_object, "focus-event", true);
- atk_object_notify_state_change(ATK_OBJECT(atk_object), ATK_STATE_FOCUSED,
- true);
+ AtkObject* descendant = GetActiveDescendantOfCurrentFocused();
+ SetWeakGPtrToAtkObject(&g_current_active_descendant, descendant);
+
+ AtkObject* new_effective_focus = g_current_active_descendant
+ ? g_current_active_descendant
+ : g_current_focused;
+ g_signal_emit_by_name(new_effective_focus, "focus-event", true);
+ atk_object_notify_state_change(ATK_OBJECT(new_effective_focus),
+ ATK_STATE_FOCUSED, true);
}
void AXPlatformNodeAuraLinux::OnSelected() {
@@ -3874,21 +3920,15 @@ void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent(
return;
AXPlatformNodeBase::NotifyAccessibilityEvent(event_type);
switch (event_type) {
- // There are three types of messages that we receive for popup menus. Each
- // time a popup menu is shown, we get a kMenuPopupStart message. This
- // includes if the menu is hidden and then re-shown. When a menu is hidden
- // we receive the kMenuPopupHide message. Finally, when the entire menu is
- // closed we receive the kMenuPopupEnd message for the parent menu and all
- // of the submenus that were opened when navigating through the menu.
- case ax::mojom::Event::kMenuPopupEnd:
- OnMenuPopupEnd();
- break;
- case ax::mojom::Event::kMenuPopupHide:
- OnMenuPopupHide();
- break;
+ // kMenuStart/kMenuEnd: the menu system has started / stopped.
+ // kMenuPopupStart/kMenuPopupEnd: an individual menu/submenu has
+ // opened/closed.
case ax::mojom::Event::kMenuPopupStart:
OnMenuPopupStart();
break;
+ case ax::mojom::Event::kMenuPopupEnd:
+ OnMenuPopupEnd();
+ break;
case ax::mojom::Event::kActiveDescendantChanged:
OnActiveDescendantChanged();
break;
@@ -4176,7 +4216,30 @@ gfx::NativeViewAccessible
AXPlatformNodeAuraLinux::HitTestSync(gint x, gint y, AtkCoordType coord_type) {
gfx::Point scroll_to(x, y);
scroll_to = ConvertPointToScreenCoordinates(scroll_to, coord_type);
- return delegate_->HitTestSync(scroll_to.x(), scroll_to.y());
+
+ AXPlatformNode* current_result = this;
+ while (true) {
+ gfx::NativeViewAccessible hit_child =
+ current_result->GetDelegate()->HitTestSync(scroll_to.x(),
+ scroll_to.y());
+ if (!hit_child)
+ return nullptr;
+ AXPlatformNode* hit_child_node =
+ AXPlatformNode::FromNativeViewAccessible(hit_child);
+ if (!hit_child_node || !hit_child_node->IsDescendantOf(current_result))
+ break;
+
+ // If we get the same node, we're done.
+ if (hit_child_node == current_result)
+ break;
+
+ // Continue to check recursively. That's because HitTestSync may have
+ // returned the best result within a particular accessibility tree,
+ // but we might need to recurse further in a tree of a different type
+ // (for example, from Views to Web).
+ current_result = hit_child_node;
+ }
+ return current_result->GetNativeViewAccessible();
}
bool AXPlatformNodeAuraLinux::GrabFocus() {
@@ -4303,9 +4366,9 @@ AtkAttributeSet* AXPlatformNodeAuraLinux::GetAtkAttributes() {
AtkStateType AXPlatformNodeAuraLinux::GetAtkStateTypeForCheckableNode() {
if (GetData().GetCheckedState() == ax::mojom::CheckedState::kMixed)
return ATK_STATE_INDETERMINATE;
- if (GetData().role == ax::mojom::Role::kToggleButton)
- return ATK_STATE_PRESSED;
- return ATK_STATE_CHECKED;
+ if (IsPlatformCheckable())
+ return ATK_STATE_CHECKED;
+ return ATK_STATE_PRESSED;
}
// AtkDocumentHelpers
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
index 6fde338d9e1..610b2041b97 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -206,8 +206,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
void OnWindowActivated();
void OnWindowDeactivated();
void OnMenuPopupStart();
- void OnMenuPopupHide();
void OnMenuPopupEnd();
+ void OnAllMenusEnded();
void OnSelected();
void OnSelectedChildrenChanged();
void OnTextSelectionChanged();
@@ -237,6 +237,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// AXPlatformNodeBase overrides.
void Init(AXPlatformNodeDelegate* delegate) override;
+ bool IsPlatformCheckable() const override;
bool IsNameExposed();
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 b120f746734..706471c0a84 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -201,6 +201,7 @@ static bool AtkObjectHasState(AtkObject* atk_object, AtkStateType state) {
TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectDetachedObject) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -231,6 +232,7 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectDetachedObject) {
TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectName) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -659,12 +661,14 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkComponentRefAtPoint) {
AXNodeData node1;
node1.id = 2;
+ node1.role = ax::mojom::Role::kGenericContainer;
node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
node1.SetName("Name1");
root.child_ids.push_back(node1.id);
AXNodeData node2;
node2.id = 3;
+ node2.role = ax::mojom::Role::kGenericContainer;
node2.relative_bounds.bounds = gfx::RectF(20, 20, 10, 10);
node2.SetName("Name2");
root.child_ids.push_back(node2.id);
@@ -1820,23 +1824,10 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkPopupWindowActive) {
{
ActivationTester tester(menu_atk_node);
GetPlatformNode(menu_node)->NotifyAccessibilityEvent(
- ax::mojom::Event::kMenuPopupHide);
- EXPECT_FALSE(tester.saw_activate_);
- EXPECT_TRUE(tester.saw_deactivate_);
- EXPECT_FALSE(tester.IsActivatedInStateSet());
- EXPECT_EQ(focus_events_on_original_node, 0);
- }
-
- {
- ActivationTester tester(menu_atk_node);
- GetPlatformNode(menu_node)->NotifyAccessibilityEvent(
ax::mojom::Event::kMenuPopupEnd);
EXPECT_FALSE(tester.saw_activate_);
- EXPECT_FALSE(tester.saw_deactivate_);
+ EXPECT_TRUE(tester.saw_deactivate_);
EXPECT_FALSE(tester.IsActivatedInStateSet());
-
- // The menu has closed so the original node should have received focus
- // again.
EXPECT_EQ(focus_events_on_original_node, 1);
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.cc b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
index 99de6603989..36081b70c14 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
@@ -4,6 +4,9 @@
#include "ui/accessibility/platform/ax_platform_node_base.h"
+#include <algorithm>
+#include <limits>
+#include <set>
#include <string>
#include <unordered_map>
#include <utility>
@@ -26,6 +29,7 @@
namespace ui {
namespace {
+
// A function to call when focus changes, for testing only.
base::LazyInstance<std::map<ax::mojom::Event, base::RepeatingClosure>>::
DestructorAtExit g_on_notify_event_for_testing;
@@ -54,6 +58,7 @@ bool FindDescendantRoleWithMaxDepth(AXPlatformNodeBase* node,
return false;
}
+
} // namespace
const base::char16 AXPlatformNodeBase::kEmbeddedCharacter = L'\xfffc';
@@ -139,7 +144,7 @@ gfx::NativeViewAccessible AXPlatformNodeBase::ChildAtIndex(int index) const {
std::string AXPlatformNodeBase::GetName() const {
if (delegate_)
return delegate_->GetName();
- return base::EmptyString();
+ return std::string();
}
base::string16 AXPlatformNodeBase::GetNameAsString16() const {
@@ -495,11 +500,20 @@ bool AXPlatformNodeBase::IsDocument() const {
}
bool AXPlatformNodeBase::IsTextOnlyObject() const {
+ if (!delegate_)
+ return false;
+
+ // In Legacy Layout, a list marker has no children and is thus represented on
+ // all platforms as a leaf node that exposes the marker itself, i.e., it forms
+ // part of the AX tree's text representation. In contrast, in Layout NG, a
+ // list marker has a static text child.
+ if (GetData().role == ax::mojom::Role::kListMarker)
+ return !GetChildCount();
return ui::IsText(GetData().role);
}
bool AXPlatformNodeBase::IsTextField() const {
- return IsPlainTextField() || IsRichTextField();
+ return GetData().IsTextField();
}
bool AXPlatformNodeBase::IsPlainTextField() const {
@@ -507,16 +521,18 @@ bool AXPlatformNodeBase::IsPlainTextField() const {
}
bool AXPlatformNodeBase::IsRichTextField() const {
- return GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot) &&
- GetData().HasState(ax::mojom::State::kRichlyEditable);
+ return GetData().IsRichTextField();
}
base::string16 AXPlatformNodeBase::GetHypertext() const {
+ if (!delegate_)
+ return base::string16();
+
// Hypertext of platform leaves, which internally are composite objects, are
// represented with the inner text of the internal composite object. These
// don't exist on non-web content.
if (IsChildOfLeaf())
- return GetDelegate()->GetInnerText();
+ return GetInnerText();
if (hypertext_.needs_update)
UpdateComputedHypertext();
@@ -524,38 +540,9 @@ base::string16 AXPlatformNodeBase::GetHypertext() const {
}
base::string16 AXPlatformNodeBase::GetInnerText() const {
- // In order to get the inner text for web content, we potentially need access
- // to nodes that are not exposed to platform APIs, i.e. they are only visible
- // in the internal accessibility tree. For example, nodes representing the
- // shadow DOM inside a native text field.
- if (GetDelegate()->IsWebContent())
- return GetDelegate()->GetInnerText();
-
- // Allows us to get text even in non-web content, e.g. in the browser's UI
- // (AKA Views).
- //
- // Unlike in web content The "kValue" attribute takes precedence, because the
- // accessibility of Views controls are carefully crafted by hand, in contrast
- // to HTML pages, where any content that might be present in the shadow DOM
- // (i.e. in the internal accessibility tree) is actually used by the renderer.
- base::string16 value =
- GetString16Attribute(ax::mojom::StringAttribute::kValue);
- if (!value.empty())
- return value;
-
- // TODO(https://crbug.com/1030703): The check for IsInvisibleOrIgnored()
- // should not be needed. ChildAtIndex() and GetChildCount() are already
- // supposed to skip over nodes that are invisible or ignored, but
- // ViewAXPlatformNodeDelegate does not currently implement this behavior.
- if (!GetChildCount() && !IsInvisibleOrIgnored())
- return GetNameAsString16();
-
- base::string16 text;
- for (auto child_iter = AXPlatformNodeChildrenBegin();
- child_iter != AXPlatformNodeChildrenEnd(); ++child_iter) {
- text += child_iter->GetInnerText();
- }
- return text;
+ if (!delegate_)
+ return base::string16();
+ return delegate_->GetInnerText();
}
bool AXPlatformNodeBase::IsSelectionItemSupported() const {
@@ -847,48 +834,15 @@ bool AXPlatformNodeBase::HasCaret(
}
bool AXPlatformNodeBase::IsLeaf() const {
- if (!GetChildCount())
- return true;
-
- // These types of objects may have children that we use as internal
- // implementation details, but we want to expose them as leaves to platform
- // accessibility APIs because screen readers might be confused if they find
- // any children.
- if (IsPlainTextField() || IsTextOnlyObject())
- return true;
-
- // Roles whose children are only presentational according to the ARIA and
- // HTML5 Specs should be hidden from screen readers.
- // (Note that whilst ARIA buttons can have only presentational children, HTML5
- // buttons are allowed to have content.)
- switch (GetData().role) {
- case ax::mojom::Role::kImage:
- case ax::mojom::Role::kMeter:
- case ax::mojom::Role::kScrollBar:
- case ax::mojom::Role::kSlider:
- case ax::mojom::Role::kSplitter:
- case ax::mojom::Role::kProgressIndicator:
- return true;
- default:
- return false;
- }
+ return delegate_ && delegate_->IsLeaf();
}
bool AXPlatformNodeBase::IsChildOfLeaf() const {
- AXPlatformNodeBase* ancestor = FromNativeViewAccessible(GetParent());
-
- while (ancestor) {
- if (ancestor->IsLeaf())
- return true;
- ancestor = FromNativeViewAccessible(ancestor->GetParent());
- }
-
- return false;
+ return delegate_ && delegate_->IsChildOfLeaf();
}
bool AXPlatformNodeBase::IsInvisibleOrIgnored() const {
- const AXNodeData& data = GetData();
- return data.HasState(ax::mojom::State::kInvisible) || data.IsIgnored();
+ return GetData().IsInvisibleOrIgnored();
}
bool AXPlatformNodeBase::IsScrollable() const {
@@ -981,7 +935,7 @@ void AXPlatformNodeBase::ComputeAttributes(PlatformAttributeList* attributes) {
AddAttributeToList(ax::mojom::IntAttribute::kPosInSet, "posinset",
attributes);
- if (HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
+ if (IsPlatformCheckable())
AddAttributeToList("checkable", "true", attributes);
if (IsInvisibleOrIgnored()) // Note: NVDA prefers this over INVISIBLE state.
@@ -1280,6 +1234,8 @@ AXHypertext::AXHypertext(const AXHypertext& other) = default;
AXHypertext& AXHypertext::operator=(const AXHypertext& other) = default;
void AXPlatformNodeBase::UpdateComputedHypertext() const {
+ if (!delegate_)
+ return;
hypertext_ = AXHypertext();
if (IsLeaf()) {
@@ -1728,6 +1684,10 @@ bool AXPlatformNodeBase::IsText(const base::string16& text,
return ch != kEmbeddedCharacter;
}
+bool AXPlatformNodeBase::IsPlatformCheckable() const {
+ return delegate_ && GetData().HasCheckedState();
+}
+
void AXPlatformNodeBase::ComputeHypertextRemovedAndInserted(
const AXHypertext& old_hypertext,
size_t* start,
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.h b/chromium/ui/accessibility/platform/ax_platform_node_base.h
index 90fca62bb3f..c14c8c7e698 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.h
@@ -216,19 +216,13 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// Optionally accepts an unignored selection to avoid redundant computation.
bool HasCaret(const AXTree::Selection* unignored_selection = nullptr);
- // Returns true if an ancestor of this node (not including itself) is a
- // leaf node, meaning that this node is not actually exposed to the
- // platform.
+ // See AXPlatformNodeDelegate::IsChildOfLeaf().
bool IsChildOfLeaf() const;
- // Returns true if this is a leaf node on this platform, meaning any
- // children should not be exposed to this platform's native accessibility
- // layer. Each platform subclass should implement this itself.
- // The definition of a leaf may vary depending on the platform,
- // but a leaf node should never have children that are focusable or
- // that might send notifications.
+ // See AXPlatformNodeDelegate::IsLeaf().
bool IsLeaf() const;
+ // See AXPlatformNodeDelegate::IsInvisibleOrIgnored().
bool IsInvisibleOrIgnored() const;
// Returns true if this node can be scrolled either in the horizontal or the
@@ -241,34 +235,41 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// Returns true if this node can be scrolled in the vertical direction.
bool IsVerticallyScrollable() const;
- // Returns true if this node has role of StaticText, LineBreak, or
+ // Returns true if this node has a role of StaticText, LineBreak, or
// InlineTextBox
bool IsTextOnlyObject() const;
- // A text field is any widget in which the user should be able to enter and
- // edit text.
- //
- // Examples include <input type="text">, <input type="password">, <textarea>,
- // <div contenteditable="true">, <div role="textbox">, <div role="searchbox">
- // and <div role="combobox">. Note that when an ARIA role that indicates that
- // the widget is editable is used, such as "role=textbox", the element doesn't
- // need to be contenteditable for this method to return true, as in theory
- // JavaScript could be used to implement editing functionality. In practice,
- // this situation should be rare.
+ // See AXNodeData::IsTextField().
bool IsTextField() const;
- // Returns true if the node is an editable text field.
+ // See AXNodeData::IsPlainTextField().
bool IsPlainTextField() const;
+ // See AXNodeData::IsRichTextField().
+ bool IsRichTextField() const;
+
+ // Determines whether an element should be exposed with checkable state, and
+ // possibly the checked state. Examples are check box and radio button.
+ // Objects that are exposed as toggle buttons use the platform pressed state
+ // in some platform APIs, and should not be exposed as checkable. They don't
+ // expose the platform equivalent of the internal checked state.
+ virtual bool IsPlatformCheckable() const;
+
bool HasFocus();
- // If this node is a leaf, returns the text of this node, otherwise represents
- // each child node with a special "embedded object" character. This is how
- // text is represented in ATK and IA2 APIs.
+ // If this node is a leaf, returns the visible accessible name of this node.
+ // Otherwise represents every non-leaf child node with a special "embedded
+ // object character", and every leaf child node with its visible accessible
+ // name. This is how displayed text and embedded objects are represented in
+ // ATK and IA2 APIs.
base::string16 GetHypertext() const;
// Returns the text of this node and all descendant nodes; including text
// found in embedded objects.
+ //
+ // Only text displayed on screen is included. Text from ARIA and HTML
+ // attributes that is either not displayed on screen, or outside this node,
+ // e.g. aria-label and HTML title, is not returned.
base::string16 GetInnerText() const;
virtual base::string16 GetValue() const;
@@ -344,11 +345,10 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
//
// Delegate. This is a weak reference which owns |this|.
//
- AXPlatformNodeDelegate* delegate_;
+ AXPlatformNodeDelegate* delegate_ = nullptr;
protected:
bool IsDocument() const;
- bool IsRichTextField() const;
bool IsSelectionItemSupported() const;
// Get the range value text, which might come from aria-valuetext or
@@ -491,7 +491,7 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
mutable AXHypertext hypertext_;
private:
- // Return true if the index represents a text character.
+ // Returns true if the index represents a text character.
bool IsText(const base::string16& text,
size_t index,
bool is_indexed_from_end = false);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
index c6064c6e248..e8ff6876672 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -78,6 +78,14 @@ class AX_EXPORT AXPlatformNodeDelegate {
// Get the accessibility tree data for this node.
virtual const AXTreeData& GetTreeData() const = 0;
+ // Returns the text of this node and all descendant nodes; including text
+ // found in embedded objects.
+ //
+ // Only text displayed on screen is included. Text from ARIA and HTML
+ // attributes that is either not displayed on screen, or outside this node,
+ // e.g. aria-label and HTML title, is not returned.
+ virtual base::string16 GetInnerText() const = 0;
+
// Get the unignored selection from the tree
virtual const AXTree::Selection GetUnignoredSelection() const = 0;
@@ -121,12 +129,22 @@ class AX_EXPORT AXPlatformNodeDelegate {
virtual gfx::NativeViewAccessible GetPreviousSibling() = 0;
// Returns true if an ancestor of this node (not including itself) is a
- // leaf node, meaning that this node is not actually exposed to the
- // platform.
+ // leaf node, meaning that this node is not actually exposed to any
+ // platform's accessibility layer.
virtual bool IsChildOfLeaf() const = 0;
- // If this object is exposed to the platform, returns this object. Otherwise,
- // returns the platform leaf under which this object is found.
+ // Returns true if this current node is editable and the root editable node is
+ // a plain text field.
+ virtual bool IsChildOfPlainTextField() const = 0;
+
+ // Returns true if this is a leaf node, meaning all its
+ // children should not be exposed to any platform's native accessibility
+ // layer.
+ virtual bool IsLeaf() const = 0;
+
+ // If this object is exposed to the platform's accessibility layer, returns
+ // this object. Otherwise, returns the platform leaf under which this object
+ // is found.
virtual gfx::NativeViewAccessible GetClosestPlatformObject() const = 0;
class ChildIterator {
@@ -172,10 +190,6 @@ class AX_EXPORT AXPlatformNodeDelegate {
// implementations.
virtual std::string GetInheritedFontFamilyName() const = 0;
- // Returns the text of this node and all descendant nodes; including text
- // found in embedded objects.
- virtual base::string16 GetInnerText() const = 0;
-
// Return the bounds of this node in the coordinate system indicated. If the
// clipping behavior is set to clipped, clipping is applied. If an offscreen
// result address is provided, it will be populated depending on whether the
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 8abc5a9300e..39d55498dbd 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -31,6 +31,38 @@ const AXTreeData& AXPlatformNodeDelegateBase::GetTreeData() const {
return *empty_data;
}
+base::string16 AXPlatformNodeDelegateBase::GetInnerText() const {
+ // Unlike in web content The "kValue" attribute always takes precedence,
+ // because we assume that users of this base class, such as Views controls,
+ // are carefully crafted by hand, in contrast to HTML pages, where any content
+ // that might be present in the shadow DOM (AKA in the internal accessibility
+ // tree) is actually used by the renderer when assigning the "kValue"
+ // attribute, including any redundant white space.
+ base::string16 value =
+ GetData().GetString16Attribute(ax::mojom::StringAttribute::kValue);
+ if (!value.empty())
+ return value;
+
+ // TODO(https://crbug.com/1030703): The check for IsInvisibleOrIgnored()
+ // should not be needed. ChildAtIndex() and GetChildCount() are already
+ // supposed to skip over nodes that are invisible or ignored, but
+ // ViewAXPlatformNodeDelegate does not currently implement this behavior.
+ if (IsLeaf() && !GetData().IsInvisibleOrIgnored())
+ return GetData().GetString16Attribute(ax::mojom::StringAttribute::kName);
+
+ base::string16 inner_text;
+ for (int i = 0; i < GetChildCount(); ++i) {
+ // TODO(nektar): Add const to all tree traversal methods and remove
+ // const_cast.
+ const AXPlatformNode* child = AXPlatformNode::FromNativeViewAccessible(
+ const_cast<AXPlatformNodeDelegateBase*>(this)->ChildAtIndex(i));
+ if (!child || !child->GetDelegate())
+ continue;
+ inner_text += child->GetDelegate()->GetInnerText();
+ }
+ return inner_text;
+}
+
const AXTree::Selection AXPlatformNodeDelegateBase::GetUnignoredSelection()
const {
return AXTree::Selection{-1, -1, -1, ax::mojom::TextAffinity::kDownstream};
@@ -95,6 +127,21 @@ gfx::NativeViewAccessible AXPlatformNodeDelegateBase::GetPreviousSibling() {
}
bool AXPlatformNodeDelegateBase::IsChildOfLeaf() const {
+ // TODO(nektar): Make all tree traversal methods const and remove const_cast.
+ const AXPlatformNodeDelegate* parent =
+ const_cast<AXPlatformNodeDelegateBase*>(this)->GetParentDelegate();
+ if (!parent)
+ return false;
+ if (parent->IsLeaf())
+ return true;
+ return parent->IsChildOfLeaf();
+}
+
+bool AXPlatformNodeDelegateBase::IsLeaf() const {
+ return !GetChildCount();
+}
+
+bool AXPlatformNodeDelegateBase::IsChildOfPlainTextField() const {
return false;
}
@@ -200,10 +247,6 @@ bool AXPlatformNodeDelegateBase::SetHypertextSelection(int start_offset,
return AccessibilityPerformAction(action_data);
}
-base::string16 AXPlatformNodeDelegateBase::GetInnerText() const {
- return base::string16();
-}
-
gfx::Rect AXPlatformNodeDelegateBase::GetBoundsRect(
const AXCoordinateSystem coordinate_system,
const AXClippingBehavior clipping_behavior,
@@ -533,6 +576,11 @@ AXPlatformNodeDelegateBase::GetTargetNodesForRelation(
std::set<AXPlatformNode*> AXPlatformNodeDelegateBase::GetReverseRelations(
ax::mojom::IntAttribute attr) {
+ // TODO(accessibility) Implement these if views ever use relations more
+ // widely. The use so far has been for the Omnibox to the suggestion popup.
+ // If this is ever implemented, then the "popup for" to "controlled by"
+ // mapping in AXPlatformRelationWin can be removed, as it would be
+ // redundant with setting the controls relationship.
return std::set<AXPlatformNode*>();
}
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 350b9f6a44b..7dd68b928d7 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -35,7 +35,7 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
// Get the accessibility tree data for this node.
const AXTreeData& GetTreeData() const override;
- // Get the unignored selection from the tree
+ base::string16 GetInnerText() const override;
const AXTree::Selection GetUnignoredSelection() const override;
// Creates a text position rooted at this object.
@@ -67,13 +67,9 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
gfx::NativeViewAccessible GetNextSibling() override;
gfx::NativeViewAccessible GetPreviousSibling() override;
- // Returns true if an ancestor of this node (not including itself) is a
- // leaf node, meaning that this node is not actually exposed to the
- // platform.
bool IsChildOfLeaf() const override;
-
- // If this object is exposed to the platform, returns this object. Otherwise,
- // returns the platform leaf under which this object is found.
+ bool IsChildOfPlainTextField() const override;
+ bool IsLeaf() const override;
gfx::NativeViewAccessible GetClosestPlatformObject() const override;
class ChildIteratorBase : public ChildIterator {
@@ -107,8 +103,6 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
const TextAttributeList& default_attributes) const override;
std::string GetInheritedFontFamilyName() const override;
- base::string16 GetInnerText() const override;
-
gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system,
const AXClippingBehavior clipping_behavior,
AXOffscreenResult* offscreen_result) const override;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_mac.h b/chromium/ui/accessibility/platform/ax_platform_node_mac.h
index 920f0a05363..c28d1a9fdc5 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_mac.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_mac.h
@@ -27,6 +27,7 @@ class AXPlatformNodeMac : public AXPlatformNodeBase {
// AXPlatformNodeBase.
void Destroy() override;
+ bool IsPlatformCheckable() const override;
protected:
void AddAttributeToList(const char* name,
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
index 9a8f6f6d60a..911344e83a4 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -497,6 +497,8 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
return nil;
for (id child in [[self AXChildren] reverseObjectEnumerator]) {
+ if (!NSPointInRect(point, [child accessibilityFrame]))
+ continue;
if (id foundChild = [child accessibilityHitTest:point])
return foundChild;
}
@@ -744,7 +746,7 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
if (ui::IsNameExposedInAXValueForRole(role))
return [self getName];
- if (_node->HasIntAttribute(ax::mojom::IntAttribute::kCheckedState)) {
+ if (_node->IsPlatformCheckable()) {
// Mixed checkbox state not currently supported in views, but could be.
// See browser_accessibility_cocoa.mm for details.
const auto checkedState = static_cast<ax::mojom::CheckedState>(
@@ -844,8 +846,10 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
- (NSValue*)AXSelectedTextRange {
// Selection might not be supported. Return (NSRange){0,0} in that case.
int start = 0, end = 0;
- _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, &start);
- _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &end);
+ if (_node->IsPlainTextField()) {
+ start = _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart);
+ end = _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd);
+ }
// NSRange cannot represent the direction the text was selected in.
return [NSValue valueWithRange:{std::min(start, end), abs(end - start)}];
@@ -1209,6 +1213,17 @@ void AXPlatformNodeMac::Destroy() {
AXPlatformNodeBase::Destroy();
}
+// On Mac, the checked state is mapped to AXValue.
+bool AXPlatformNodeMac::IsPlatformCheckable() const {
+ if (GetData().role == ax::mojom::Role::kTab) {
+ // On Mac, tabs are exposed as radio buttons, and are treated as checkable.
+ // Also, the internal State::kSelected is be mapped to checked via AXValue.
+ return true;
+ }
+
+ return AXPlatformNodeBase::IsPlatformCheckable();
+}
+
gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() {
if (!native_node_)
native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc
index 167e1af7f8e..ec5adb620d6 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc
@@ -60,7 +60,7 @@ class AXPlatformNodeTextChildProviderTest : public AXPlatformNodeWinTest {
ui::AXNodeData text_child_of_text;
text_child_of_text.id = 6;
- text_child_of_text.role = ax::mojom::Role::kStaticText;
+ text_child_of_text.role = ax::mojom::Role::kInlineTextBox;
text_child_of_text.SetName("text child of text.");
text_child_of_root.child_ids.push_back(text_child_of_text.id);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
index ec472458f9f..d86ee466120 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
@@ -7,6 +7,8 @@
#include <UIAutomationClient.h>
#include <UIAutomationCoreApi.h>
+#include <vector>
+
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
#include "ui/accessibility/ax_action_data.h"
@@ -107,8 +109,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderRangeFromChild) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
root_data.child_ids.push_back(3);
@@ -196,8 +198,8 @@ TEST_F(AXPlatformNodeTextProviderTest,
ui::AXNodeData root;
root.id = ROOT_ID;
- root.SetName("Document");
root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("Document");
root.child_ids = {DIALOG_ID};
ui::AXNodeData dialog;
@@ -343,8 +345,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderDocumentRange) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
Init(root_data, text_data);
@@ -374,8 +376,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderDocumentRangeNested) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
Init(root_data, paragraph_data, text_data);
@@ -400,8 +402,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderSupportedSelection) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
Init(root_data, text_data);
@@ -433,8 +435,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetSelection) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
root_data.child_ids.push_back(3);
@@ -608,8 +610,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetActiveComposition) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
ui::AXTreeUpdate update;
@@ -668,8 +670,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetConversionTarget) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
ui::AXTreeUpdate update;
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 ee4026570b1..379fa000578 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -172,6 +172,11 @@ HRESULT AXPlatformNodeTextRangeProviderWin::CompareEndpoints(
HRESULT AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
TextUnit unit) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_EXPANDTOENCLOSINGUNIT);
+ return ExpandToEnclosingUnitImpl(unit);
+}
+
+HRESULT AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnitImpl(
+ TextUnit unit) {
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
NormalizeTextRange();
@@ -627,8 +632,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// Move the start of the text range forward or backward in the document by the
// requested number of text unit boundaries.
int start_units_moved = 0;
- HRESULT hr = MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count,
- &start_units_moved);
+ HRESULT hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_Start, unit,
+ count, &start_units_moved);
bool succeeded_move = SUCCEEDED(hr) && start_units_moved != 0;
if (succeeded_move) {
@@ -640,8 +645,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// by one text unit to expand the text range from the degenerate range
// state.
int current_start_units_moved = 0;
- hr = MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, -1,
- &current_start_units_moved);
+ hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_Start, unit, -1,
+ &current_start_units_moved);
start_units_moved -= 1;
succeeded_move = SUCCEEDED(hr) && current_start_units_moved == -1 &&
start_units_moved > 0;
@@ -650,8 +655,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// forward by one text unit to expand the text range from the degenerate
// state.
int end_units_moved = 0;
- hr = MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, 1,
- &end_units_moved);
+ hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_End, unit, 1,
+ &end_units_moved);
succeeded_move = SUCCEEDED(hr) && end_units_moved == 1;
}
@@ -660,7 +665,7 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// sure to bring back the end endpoint to the end of the start's anchor.
if (start_->anchor_id() != end_->anchor_id() &&
(unit == TextUnit_Character || unit == TextUnit_Word)) {
- ExpandToEnclosingUnit(unit);
+ ExpandToEnclosingUnitImpl(unit);
}
}
}
@@ -683,6 +688,14 @@ HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
int count,
int* units_moved) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_MOVEENDPOINTBYUNIT);
+ return MoveEndpointByUnitImpl(endpoint, unit, count, units_moved);
+}
+
+HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitImpl(
+ TextPatternRangeEndpoint endpoint,
+ TextUnit unit,
+ int count,
+ int* units_moved) {
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(units_moved);
// Per MSDN, MoveEndpointByUnit with zero count has no effect.
@@ -1124,12 +1137,22 @@ void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange() {
NormalizeAsUnignoredTextRange();
// Do not normalize text ranges when a cursor or selection is visible. ATs
- // may depend on the specific position that the caret or selection is at.
+ // may depend on the specific position that the caret or selection is at. This
+ // condition fixes issues when the caret is inside a plain text field, but
+ // causes more issues when used inside of a rich text field. For this reason,
+ // if we have a caret or a selection inside of an editable node, restrict this
+ // to a plain text field as we gain nothing from using it in a rich text
+ // field.
AXPlatformNodeDelegate* start_delegate = GetDelegate(start_.get());
AXPlatformNodeDelegate* end_delegate = GetDelegate(end_.get());
- if ((start_delegate && start_delegate->HasVisibleCaretOrSelection()) ||
- (start_delegate && end_delegate->HasVisibleCaretOrSelection()))
+ if ((start_delegate && start_delegate->HasVisibleCaretOrSelection() &&
+ (!start_delegate->GetData().HasState(ax::mojom::State::kEditable) ||
+ start_delegate->IsChildOfPlainTextField())) ||
+ (end_delegate && end_delegate->HasVisibleCaretOrSelection() &&
+ (!end_delegate->GetData().HasState(ax::mojom::State::kEditable) ||
+ end_delegate->IsChildOfPlainTextField()))) {
return;
+ }
AXPositionInstance normalized_start =
start_->AsLeafTextPositionBeforeCharacter();
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
index db595fdf424..c967b191124 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -99,6 +99,16 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
AXBoundaryBehavior boundary_behavior,
ax::mojom::MoveDirection boundary_direction);
+ // Prefer these *Impl methods when functionality is needed internally. We
+ // should avoid calling external APIs internally as it will cause the
+ // histograms to become innaccurate.
+ HRESULT MoveEndpointByUnitImpl(TextPatternRangeEndpoint endpoint,
+ TextUnit unit,
+ int count,
+ int* units_moved);
+
+ IFACEMETHODIMP ExpandToEnclosingUnitImpl(TextUnit unit);
+
base::string16 GetString(int max_count,
size_t* appended_newlines_count = nullptr);
AXPlatformNodeWin* owner() const;
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 13617fa8a8c..a698ca3c021 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
@@ -7,6 +7,9 @@
#include <UIAutomationClient.h>
#include <UIAutomationCoreApi.h>
+#include <memory>
+#include <utility>
+
#include "base/win/atl.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
@@ -3074,24 +3077,12 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = root_data.id;
- update.nodes.push_back(root_data);
- update.nodes.push_back(paragraph_data);
- update.nodes.push_back(static_text_data1);
- update.nodes.push_back(inline_text_data1);
- update.nodes.push_back(link_data);
- update.nodes.push_back(static_text_data2);
- update.nodes.push_back(inline_text_data2);
- update.nodes.push_back(link_data2);
- update.nodes.push_back(list_data);
- update.nodes.push_back(list_item_data);
- update.nodes.push_back(static_text_data3);
- update.nodes.push_back(inline_text_data3);
- update.nodes.push_back(search_box);
- update.nodes.push_back(search_text);
- update.nodes.push_back(pdf_highlight_data);
- update.nodes.push_back(static_text_data4);
- update.nodes.push_back(inline_text_data4);
-
+ update.nodes = {root_data, paragraph_data, static_text_data1,
+ inline_text_data1, link_data, static_text_data2,
+ inline_text_data2, link_data2, list_data,
+ list_item_data, static_text_data3, inline_text_data3,
+ search_box, search_text, pdf_highlight_data,
+ static_text_data4, inline_text_data4};
Init(update);
// Set up variables from the tree for testing.
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
index 480a7233253..1cc881f5af8 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
@@ -359,31 +359,31 @@ AXTreeUpdate AXPlatformNodeTest::BuildListBox(
const std::vector<ax::mojom::State>& additional_state) {
AXNodeData listbox;
listbox.id = 1;
- listbox.SetName("ListBox");
listbox.role = ax::mojom::Role::kListBox;
+ listbox.SetName("ListBox");
for (auto state : additional_state)
listbox.AddState(state);
AXNodeData option_1;
option_1.id = 2;
- option_1.SetName("Option1");
option_1.role = ax::mojom::Role::kListBoxOption;
+ option_1.SetName("Option1");
if (option_1_is_selected)
option_1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
listbox.child_ids.push_back(option_1.id);
AXNodeData option_2;
option_2.id = 3;
- option_2.SetName("Option2");
option_2.role = ax::mojom::Role::kListBoxOption;
+ option_2.SetName("Option2");
if (option_2_is_selected)
option_2.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
listbox.child_ids.push_back(option_2.id);
AXNodeData option_3;
option_3.id = 4;
- option_3.SetName("Option3");
option_3.role = ax::mojom::Role::kListBoxOption;
+ option_3.SetName("Option3");
if (option_3_is_selected)
option_3.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
listbox.child_ids.push_back(option_3.id);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.cc b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
index b9fbc55ae57..d0fbf20bcf0 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
@@ -15,11 +15,14 @@
#include <utility>
#include <vector>
+#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
#include "base/win/enum_variant.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
@@ -27,6 +30,7 @@
#include "skia/ext/skia_utils_win.h"
#include "third_party/iaccessible2/ia2_api_all.h"
#include "third_party/skia/include/core/SkColor.h"
+#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_active_popup.h"
@@ -43,6 +47,7 @@
#include "ui/accessibility/platform/ax_platform_node_textchildprovider_win.h"
#include "ui/accessibility/platform/ax_platform_node_textprovider_win.h"
#include "ui/accessibility/platform/ax_platform_relation_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/base/win/atl_module.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect_conversions.h"
@@ -297,7 +302,7 @@ AXPlatformNode* AXPlatformNode::FromNativeViewAccessible(
// AXPlatformNodeWin
//
-AXPlatformNodeWin::AXPlatformNodeWin() : force_new_hypertext_(false) {}
+AXPlatformNodeWin::AXPlatformNodeWin() {}
AXPlatformNodeWin::~AXPlatformNodeWin() {
ClearOwnRelations();
@@ -305,7 +310,6 @@ AXPlatformNodeWin::~AXPlatformNodeWin() {
void AXPlatformNodeWin::Init(AXPlatformNodeDelegate* delegate) {
AXPlatformNodeBase::Init(delegate);
- force_new_hypertext_ = false;
}
void AXPlatformNodeWin::ClearOwnRelations() {
@@ -314,10 +318,6 @@ void AXPlatformNodeWin::ClearOwnRelations() {
relations_.clear();
}
-void AXPlatformNodeWin::ForceNewHypertext() {
- force_new_hypertext_ = true;
-}
-
// Static
void AXPlatformNodeWin::SanitizeStringAttributeForUIAAriaProperty(
const base::string16& input,
@@ -645,7 +645,7 @@ void AXPlatformNodeWin::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
::VariantInit(old_value.Receive());
base::win::ScopedVariant new_value;
::VariantInit(new_value.Receive());
- GetPropertyValue((*uia_property), new_value.Receive());
+ GetPropertyValueImpl((*uia_property), new_value.Receive());
::UiaRaiseAutomationPropertyChangedEvent(this, (*uia_property), old_value,
new_value);
}
@@ -1263,6 +1263,14 @@ IFACEMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) {
IFACEMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_UNIQUE_ID);
COM_OBJECT_VALIDATE_1_ARG(id);
+ // We want to negate the unique id for it to be consistent across different
+ // Windows accessiblity APIs. The negative unique id convention originated
+ // from ::NotifyWinEvent() takes an hwnd and a child id. A 0 child id means
+ // self, and a positive child id means child #n. In order to fire an event for
+ // an arbitrary descendant of the window, Firefox started the practice of
+ // using a negative unique id. We follow the same negative unique id
+ // convention here and when we fire events via
+ // ::NotifyWinEvent().
*id = -GetUniqueId();
return S_OK;
}
@@ -2075,7 +2083,23 @@ HRESULT AXPlatformNodeWin::ISelectionItemProviderSetSelected(
return UIA_E_ELEMENTNOTENABLED;
}
- if (selected == ISelectionItemProviderIsSelected())
+ // The platform implements selection follows focus for single-selection
+ // container elements. Focus action can change a node's accessibility selected
+ // state, but does not cause the actual control to be selected.
+ // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_selection_follows_focus
+ // https://www.w3.org/TR/core-aam-1.2/#mapping_events_selection
+ //
+ // We don't want to perform |Action::kDoDefault| for an ax node that has
+ // |kSelected=true| and |kSelectedFromFocus=false|, because perform
+ // |Action::kDoDefault| may cause the control to be unselected. However, if an
+ // ax node is selected due to focus, i.e. |kSelectedFromFocus=true|, we need
+ // to perform |Action::kDoDefault| on the ax node, since focus action only
+ // changes an ax node's accessibility selected state to |kSelected=true| and
+ // no |Action::kDoDefault| was performed on that node yet. So we need to
+ // perform |Action::kDoDefault| on the ax node to cause its associated control
+ // to be selected.
+ if (selected == ISelectionItemProviderIsSelected() &&
+ !GetBoolAttribute(ax::mojom::BoolAttribute::kSelectedFromFocus))
return S_OK;
AXActionData data;
@@ -3971,6 +3995,11 @@ IFACEMETHODIMP AXPlatformNodeWin::get_FragmentRoot(
IFACEMETHODIMP AXPlatformNodeWin::GetPatternProvider(PATTERNID pattern_id,
IUnknown** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PATTERN_PROVIDER);
+ return GetPatternProviderImpl(pattern_id, result);
+}
+
+HRESULT AXPlatformNodeWin::GetPatternProviderImpl(PATTERNID pattern_id,
+ IUnknown** result) {
UIA_VALIDATE_CALL_1_ARG(result);
*result = nullptr;
@@ -3997,7 +4026,11 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
// Collapse all unknown property IDs into a single bucket.
base::UmaHistogramSparse("Accessibility.WinAPIs.GetPropertyValue", 0);
}
+ return GetPropertyValueImpl(property_id, result);
+}
+HRESULT AXPlatformNodeWin::GetPropertyValueImpl(PROPERTYID property_id,
+ VARIANT* result) {
UIA_VALIDATE_CALL_1_ARG(result);
result->vt = VT_EMPTY;
@@ -4005,6 +4038,7 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
int int_attribute;
const AXNodeData& data = GetData();
+ // Default UIA Property Ids.
switch (property_id) {
case UIA_AriaPropertiesPropertyId:
result->vt = VT_BSTR;
@@ -4044,9 +4078,7 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
break;
case UIA_CulturePropertyId:
- result->vt = VT_BSTR;
- GetStringAttributeAsBstr(ax::mojom::StringAttribute::kLanguage,
- &result->bstrVal);
+ return GetCultureAttributeAsVariant(result);
break;
case UIA_DescribedByPropertyId:
@@ -4366,6 +4398,21 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
case UIA_ProviderDescriptionPropertyId:
case UIA_RuntimeIdPropertyId:
break;
+ } // End of default UIA property ids.
+
+ // Custom UIA Property Ids.
+ if (property_id ==
+ UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId()) {
+ // We want to negate the unique id for it to be consistent across different
+ // Windows accessiblity APIs. The negative unique id convention originated
+ // from ::NotifyWinEvent() takes an hwnd and a child id. A 0 child id means
+ // self, and a positive child id means child #n. In order to fire an event
+ // for an arbitrary descendant of the window, Firefox started the practice
+ // of using a negative unique id. We follow the same negative unique id
+ // convention here and when we fire events via ::NotifyWinEvent().
+ result->vt = VT_BSTR;
+ result->bstrVal =
+ SysAllocString(base::NumberToString16(-GetUniqueId()).c_str());
}
return S_OK;
@@ -4405,6 +4452,71 @@ IFACEMETHODIMP AXPlatformNodeWin::ShowContextMenu() {
}
//
+// IChromeAccessible implementation.
+//
+
+void SendBulkFetchResponse(
+ Microsoft::WRL::ComPtr<IChromeAccessibleDelegate> delegate,
+ LONG request_id,
+ std::string json_result) {
+ base::string16 json_result_utf16 = base::UTF8ToUTF16(json_result);
+ delegate->put_bulkFetchResult(request_id,
+ SysAllocString(json_result_utf16.c_str()));
+}
+
+IFACEMETHODIMP AXPlatformNodeWin::get_bulkFetch(
+ BSTR input_json,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) {
+ COM_OBJECT_VALIDATE();
+ if (!delegate)
+ return E_INVALIDARG;
+
+ // TODO(crbug.com/1083834): if parsing |input_json|, use
+ // DataDecoder because the json is untrusted. For now, this is just
+ // a stub that calls PostTask so that it's async, but it doesn't
+ // actually parse the input.
+
+ base::Value result(base::Value::Type::DICTIONARY);
+ result.SetKey("role", base::Value(ui::ToString(GetData().role)));
+
+ gfx::Rect bounds = GetDelegate()->GetBoundsRect(
+ AXCoordinateSystem::kScreenDIPs, AXClippingBehavior::kUnclipped);
+ result.SetKey("x", base::Value(bounds.x()));
+ result.SetKey("y", base::Value(bounds.y()));
+ result.SetKey("width", base::Value(bounds.width()));
+ result.SetKey("height", base::Value(bounds.height()));
+ std::string json_result;
+ base::JSONWriter::Write(result, &json_result);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &SendBulkFetchResponse,
+ Microsoft::WRL::ComPtr<IChromeAccessibleDelegate>(delegate),
+ request_id, json_result));
+ return S_OK;
+}
+
+IFACEMETHODIMP AXPlatformNodeWin::get_hitTest(
+ LONG screen_physical_pixel_x,
+ LONG screen_physical_pixel_y,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) {
+ COM_OBJECT_VALIDATE();
+
+ if (!delegate)
+ return E_INVALIDARG;
+
+ // TODO(crbug.com/1083834): Plumb through an actual async hit test.
+ AXPlatformNodeWin* hit_child = static_cast<AXPlatformNodeWin*>(
+ FromNativeViewAccessible(GetDelegate()->HitTestSync(
+ screen_physical_pixel_x, screen_physical_pixel_y)));
+
+ delegate->put_hitTestResult(request_id, static_cast<IAccessible*>(hit_child));
+ return S_OK;
+}
+
+//
// IServiceProvider implementation.
//
@@ -4430,6 +4542,12 @@ IFACEMETHODIMP AXPlatformNodeWin::QueryService(REFGUID guidService,
return QueryInterface(riid, object);
}
+ if (guidService == IID_IChromeAccessible) {
+ if (features::IsIChromeAccessibleEnabled()) {
+ return QueryInterface(riid, object);
+ }
+ }
+
// TODO(suproteem): Include IAccessibleEx in the list, potentially checking
// for version.
@@ -4468,6 +4586,10 @@ STDMETHODIMP AXPlatformNodeWin::InternalQueryInterface(
if (!accessible->GetData().IsRangeValueSupported()) {
return E_NOINTERFACE;
}
+ } else if (riid == IID_IChromeAccessible) {
+ if (!features::IsIChromeAccessibleEnabled()) {
+ return E_NOINTERFACE;
+ }
}
return CComObjectRootBase::InternalQueryInterface(this_ptr, entries, riid,
@@ -5254,7 +5376,7 @@ int32_t AXPlatformNodeWin::ComputeIA2State() {
const AXNodeData& data = GetData();
int32_t ia2_state = IA2_STATE_OPAQUE;
- if (HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
+ if (IsPlatformCheckable())
ia2_state |= IA2_STATE_CHECKABLE;
if (HasIntAttribute(ax::mojom::IntAttribute::kInvalidState) &&
@@ -6800,7 +6922,12 @@ bool AXPlatformNodeWin::IsUIAControl() const {
// UIA provides multiple "views": raw, content and control. We only want to
// populate the content and control views with items that make sense to
// traverse over.
+
if (GetDelegate()->IsWebContent()) {
+ // Invisible or ignored elements should not show up in control view at all.
+ if (IsInvisibleOrIgnored())
+ return false;
+
if (IsTextOnlyObject()) {
// A text leaf can be a UIAControl, but text inside of a heading, link,
// button, etc. where the role allows the name to be generated from the
@@ -6840,7 +6967,8 @@ bool AXPlatformNodeWin::IsUIAControl() const {
}
parent = FromNativeViewAccessible(parent->GetParent());
}
- }
+ } // end of text only case.
+
const AXNodeData& data = GetData();
// https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-treeoverview#control-view
// The control view also includes noninteractive UI items that contribute
@@ -6892,9 +7020,10 @@ bool AXPlatformNodeWin::IsUIAControl() const {
!data.HasState(ax::mojom::State::kFocusable) && !data.IsClickable()) {
return false;
}
+
return true;
- }
- // non web-content case.
+ } // end of web-content only case.
+
const AXNodeData& data = GetData();
return !((IsReadOnlySupported(data.role) && data.IsReadOnlyOrDisabled()) ||
data.HasState(ax::mojom::State::kInvisible) ||
@@ -6959,8 +7088,10 @@ bool AXPlatformNodeWin::IsInaccessibleDueToAncestor() const {
}
bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
- auto role = GetData().role;
+ if (IsPlainTextField())
+ return true;
+ auto role = GetData().role;
if (HasPresentationalChildren(role))
return true;
@@ -6982,7 +7113,6 @@ bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
return only_child && only_child->IsTextOnlyObject();
}
return false;
- case ax::mojom::Role::kTextField:
case ax::mojom::Role::kPdfActionableHighlight:
return true;
default:
@@ -7004,6 +7134,13 @@ base::string16 AXPlatformNodeWin::GetValue() const {
return value;
}
+bool AXPlatformNodeWin::IsPlatformCheckable() const {
+ if (GetData().role == ax::mojom::Role::kToggleButton)
+ return false;
+
+ return AXPlatformNodeBase::IsPlatformCheckable();
+}
+
bool AXPlatformNodeWin::ShouldNodeHaveFocusableState(
const AXNodeData& data) const {
switch (data.role) {
@@ -7212,6 +7349,8 @@ base::Optional<DWORD> AXPlatformNodeWin::MojoEventToMSAAEvent(
switch (event) {
case ax::mojom::Event::kAlert:
return EVENT_SYSTEM_ALERT;
+ case ax::mojom::Event::kActiveDescendantChanged:
+ return IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
case ax::mojom::Event::kCheckedStateChanged:
case ax::mojom::Event::kExpandedChanged:
case ax::mojom::Event::kStateChanged:
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.h b/chromium/ui/accessibility/platform/ax_platform_node_win.h
index 1055422d59c..9f4367393bc 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.h
@@ -28,6 +28,7 @@
#include "ui/accessibility/ax_text_utils.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_text_boundary.h"
+#include "ui/accessibility/platform/ichromeaccessible.h"
#include "ui/gfx/range/range.h"
// IMPORTANT!
@@ -280,6 +281,11 @@ enum {
UMA_API_WINDOW_GET_WINDOWVISUALSTATE = 243,
UMA_API_WINDOW_GET_WINDOWINTERACTIONSTATE = 244,
UMA_API_WINDOW_GET_ISTOPMOST = 245,
+ UMA_API_ELEMENT_PROVIDER_FROM_POINT = 246,
+ UMA_API_GET_FOCUS = 247,
+ UMA_API_ADVISE_EVENT_ADDED = 248,
+ UMA_API_ADVISE_EVENT_REMOVED = 249,
+ UMA_API_ITEMCONTAINER_FINDITEMBYPROPERTY = 250,
// This must always be the last enum. It's okay for its value to
// increase, but none of the other enum values may change.
@@ -358,6 +364,7 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
public IToggleProvider,
public IValueProvider,
public IWindowProvider,
+ public IChromeAccessible,
public AXPlatformNodeBase {
using IDispatchImpl::Invoke;
@@ -381,6 +388,7 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
COM_INTERFACE_ENTRY(IAccessibleTable2)
COM_INTERFACE_ENTRY(IAccessibleTableCell)
COM_INTERFACE_ENTRY(IAccessibleValue)
+ COM_INTERFACE_ENTRY(IChromeAccessible)
COM_INTERFACE_ENTRY(IExpandCollapseProvider)
COM_INTERFACE_ENTRY(IGridItemProvider)
COM_INTERFACE_ENTRY(IGridProvider)
@@ -408,8 +416,6 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Clear any AXPlatformRelationWin nodes owned by this node.
void ClearOwnRelations();
- void ForceNewHypertext();
-
// AXPlatformNode overrides.
gfx::NativeViewAccessible GetNativeViewAccessible() override;
void NotifyAccessibilityEvent(ax::mojom::Event event_type) override;
@@ -417,6 +423,7 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// AXPlatformNodeBase overrides.
void Destroy() override;
base::string16 GetValue() const override;
+ bool IsPlatformCheckable() const override;
//
// IAccessible methods.
@@ -1023,6 +1030,19 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
IFACEMETHODIMP ShowContextMenu() override;
//
+ // IChromeAccessible methods.
+ //
+
+ IFACEMETHODIMP get_bulkFetch(BSTR input_json,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) override;
+
+ IFACEMETHODIMP get_hitTest(LONG screen_physical_pixel_x,
+ LONG screen_physical_pixel_y,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) override;
+
+ //
// IServiceProvider methods.
//
@@ -1046,6 +1066,16 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// IRawElementProviderSimple support method.
bool IsPatternProviderSupported(PATTERNID pattern_id);
+ // Prefer GetPatternProviderImpl when calling internally. We should avoid
+ // calling external APIs internally as it will cause the histograms to become
+ // innaccurate.
+ HRESULT GetPatternProviderImpl(PATTERNID pattern_id, IUnknown** result);
+
+ // Prefer GetPropertyValueImpl when calling internally. We should avoid
+ // calling external APIs internally as it will cause the histograms to become
+ // innaccurate.
+ HRESULT GetPropertyValueImpl(PROPERTYID property_id, VARIANT* result);
+
// Helper to return the runtime id (without going through a SAFEARRAY)
using RuntimeIdArray = std::array<int, 2>;
void GetRuntimeIdArray(RuntimeIdArray& runtime_id);
@@ -1123,7 +1153,6 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
std::vector<Microsoft::WRL::ComPtr<AXPlatformRelationWin>> relations_;
AXHypertext old_hypertext_;
- bool force_new_hypertext_;
// These protected methods are still used by BrowserAccessibilityComWin. At
// some point post conversion, we can probably move these to be private
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 50b78af9642..c7fce2de057 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -11,8 +11,11 @@
#include <memory>
#include "base/auto_reset.h"
+#include "base/json/json_reader.h"
+#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
#include "base/win/atl.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
@@ -20,6 +23,7 @@
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/iaccessible2/ia2_api_all.h"
+#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
@@ -219,7 +223,10 @@ ScopedVariant SELF(CHILDID_SELF);
testing::UnorderedElementsAreArray(expected_property_values)); \
}
-AXPlatformNodeWinTest::AXPlatformNodeWinTest() {}
+AXPlatformNodeWinTest::AXPlatformNodeWinTest() {
+ scoped_feature_list_.InitAndEnableFeature(features::kIChromeAccessible);
+}
+
AXPlatformNodeWinTest::~AXPlatformNodeWinTest() {}
void AXPlatformNodeWinTest::SetUp() {
@@ -451,6 +458,7 @@ bool TestFragmentRootDelegate::IsAXFragmentRootAControlElement() {
TEST_F(AXPlatformNodeWinTest, IAccessibleDetachedObject) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -472,12 +480,14 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleHitTest) {
AXNodeData node1;
node1.id = 2;
+ node1.role = ax::mojom::Role::kGenericContainer;
node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
node1.SetName("Name1");
root.child_ids.push_back(node1.id);
AXNodeData node2;
node2.id = 3;
+ node2.role = ax::mojom::Role::kGenericContainer;
node2.relative_bounds.bounds = gfx::RectF(20, 20, 20, 20);
node2.SetName("Name2");
root.child_ids.push_back(node2.id);
@@ -512,6 +522,7 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleHitTestDoesNotLoopForever) {
AXNodeData node1;
node1.id = 2;
+ node1.role = ax::mojom::Role::kGenericContainer;
node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
node1.SetName("Name1");
root.child_ids.push_back(node1.id);
@@ -535,6 +546,7 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleHitTestDoesNotLoopForever) {
TEST_F(AXPlatformNodeWinTest, IAccessibleName) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -1928,6 +1940,103 @@ TEST_F(AXPlatformNodeWinTest, IAccessible2GetNRelations) {
// TODO(dougt): Try adding one more relation.
}
+TEST_F(AXPlatformNodeWinTest,
+ IAccessible2TestPopupForRelationMapsToControlledByRelation) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+
+ AXNodeData child1;
+ child1.id = 2;
+ child1.role = ax::mojom::Role::kTextField;
+ child1.AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, {3});
+ root.child_ids.push_back(2);
+
+ // Add listbox that is popup for the textfield.
+ AXNodeData child2;
+ child2.id = 3;
+ child2.role = ax::mojom::Role::kListBox;
+ child2.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, 2);
+ root.child_ids.push_back(3);
+
+ Init(root, child1, child2);
+ ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
+ ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);
+
+ ComPtr<IDispatch> result;
+ EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(1), &result));
+ ComPtr<IAccessible2> ax_child1;
+ EXPECT_EQ(S_OK, result.As(&ax_child1));
+ result.Reset();
+
+ EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(2), &result));
+ ComPtr<IAccessible2> ax_child2;
+ EXPECT_EQ(S_OK, result.As(&ax_child2));
+ result.Reset();
+
+ LONG n_relations = 0;
+ LONG n_targets = 0;
+ ScopedBstr relation_type;
+ ComPtr<IAccessibleRelation> controls_relation;
+ ComPtr<IAccessibleRelation> controlled_by_relation;
+ ComPtr<IUnknown> target;
+
+ EXPECT_HRESULT_SUCCEEDED(ax_child1->get_nRelations(&n_relations));
+ EXPECT_EQ(1, n_relations);
+
+ EXPECT_HRESULT_SUCCEEDED(ax_child1->get_relation(0, &controls_relation));
+
+ EXPECT_HRESULT_SUCCEEDED(
+ controls_relation->get_relationType(relation_type.Receive()));
+ EXPECT_EQ(L"controllerFor", base::string16(relation_type.Get()));
+
+ relation_type.Reset();
+
+ EXPECT_HRESULT_SUCCEEDED(controls_relation->get_nTargets(&n_targets));
+ EXPECT_EQ(1, n_targets);
+
+ EXPECT_HRESULT_SUCCEEDED(controls_relation->get_target(0, &target));
+ target.Reset();
+
+ controls_relation.Reset();
+
+ // Test the controlled by relation, mapped from the popup for relation.
+ EXPECT_HRESULT_SUCCEEDED(ax_child2->get_nRelations(&n_relations));
+ // The test is currently outsmarting us, and automatically mapping the
+ // reverse relation in addition to mapping the popup for -> controlled by.
+ // Therefore, the same relation will exist twice in this test, which
+ // actually shows that the popup for -> controlled by relation is working.
+ // As a result, both relations should have the same result in this test.
+ EXPECT_EQ(2, n_relations);
+
+ // Both relations should have the same result in this test.
+ EXPECT_HRESULT_SUCCEEDED(ax_child2->get_relation(0, &controlled_by_relation));
+ EXPECT_HRESULT_SUCCEEDED(
+ controlled_by_relation->get_relationType(relation_type.Receive()));
+ EXPECT_EQ(L"controlledBy", base::string16(relation_type.Get()));
+ relation_type.Reset();
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_nTargets(&n_targets));
+ EXPECT_EQ(1, n_targets);
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_target(0, &target));
+ target.Reset();
+ controlled_by_relation.Reset();
+
+ // Both relations should have the same result in this test.
+ EXPECT_HRESULT_SUCCEEDED(ax_child2->get_relation(1, &controlled_by_relation));
+ EXPECT_HRESULT_SUCCEEDED(
+ controlled_by_relation->get_relationType(relation_type.Receive()));
+ EXPECT_EQ(L"controlledBy", base::string16(relation_type.Get()));
+ relation_type.Reset();
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_nTargets(&n_targets));
+ EXPECT_EQ(1, n_targets);
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_target(0, &target));
+ target.Reset();
+}
+
TEST_F(AXPlatformNodeWinTest, DISABLED_TestRelationTargetsOfType) {
AXNodeData root;
root.id = 1;
@@ -3474,6 +3583,124 @@ TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeaders) {
EXPECT_EQ(nullptr, safearray.Get());
}
+TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeadersMultipleHeaders) {
+ // Build a table like this:
+ // header_r1c1 | header_r1c2 | header_r1c3
+ // cell_r2c1 | cell_r2c2 | cell_r2c3
+ // cell_r3c1 | header_r3c2 |
+
+ // <table>
+ // <tr aria-label="row1">
+ // <th>header_r1c1</th>
+ // <th>header_r1c2</th>
+ // <th>header_r1c3</th>
+ // </tr>
+ // <tr aria-label="row2">
+ // <td>cell_r2c1</td>
+ // <td>cell_r2c2</td>
+ // <td>cell_r2c3</td>
+ // </tr>
+ // <tr aria-label="row3">
+ // <td>cell_r3c1</td>
+ // <th>header_r3c2</th>
+ // </tr>
+ // </table>
+
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kTable;
+
+ AXNodeData row1;
+ row1.id = 2;
+ row1.role = ax::mojom::Role::kRow;
+ root.child_ids.push_back(row1.id);
+
+ AXNodeData row2;
+ row2.id = 3;
+ row2.role = ax::mojom::Role::kRow;
+ root.child_ids.push_back(row2.id);
+
+ AXNodeData row3;
+ row3.id = 4;
+ row3.role = ax::mojom::Role::kRow;
+ root.child_ids.push_back(row3.id);
+
+ // <tr aria-label="row1">
+ // <th>header_r1c1</th> <th>header_r1c2</th> <th>header_r1c3</th>
+ // </tr>
+ AXNodeData header_r1c1;
+ header_r1c1.id = 5;
+ header_r1c1.role = ax::mojom::Role::kColumnHeader;
+ header_r1c1.SetName(L"header_r1c1");
+ row1.child_ids.push_back(header_r1c1.id);
+
+ AXNodeData header_r1c2;
+ header_r1c2.id = 6;
+ header_r1c2.role = ax::mojom::Role::kColumnHeader;
+ header_r1c2.SetName(L"header_r1c2");
+ row1.child_ids.push_back(header_r1c2.id);
+
+ AXNodeData header_r1c3;
+ header_r1c3.id = 7;
+ header_r1c3.role = ax::mojom::Role::kColumnHeader;
+ header_r1c3.SetName(L"header_r1c3");
+ row1.child_ids.push_back(header_r1c3.id);
+
+ // <tr aria-label="row2">
+ // <td>cell_r2c1</td> <td>cell_r2c2</td> <td>cell_r2c3</td>
+ // </tr>
+ AXNodeData cell_r2c1;
+ cell_r2c1.id = 8;
+ cell_r2c1.role = ax::mojom::Role::kCell;
+ cell_r2c1.SetName(L"cell_r2c1");
+ row2.child_ids.push_back(cell_r2c1.id);
+
+ AXNodeData cell_r2c2;
+ cell_r2c2.id = 9;
+ cell_r2c2.role = ax::mojom::Role::kCell;
+ cell_r2c2.SetName(L"cell_r2c2");
+ row2.child_ids.push_back(cell_r2c2.id);
+
+ AXNodeData cell_r2c3;
+ cell_r2c3.id = 10;
+ cell_r2c3.role = ax::mojom::Role::kCell;
+ cell_r2c3.SetName(L"cell_r2c3");
+ row2.child_ids.push_back(cell_r2c3.id);
+
+ // <tr aria-label="row3">
+ // <td>cell_r3c1</td> <th>header_r3c2</th>
+ // </tr>
+ AXNodeData cell_r3c1;
+ cell_r3c1.id = 11;
+ cell_r3c1.role = ax::mojom::Role::kCell;
+ cell_r3c1.SetName(L"cell_r3c1");
+ row3.child_ids.push_back(cell_r3c1.id);
+
+ AXNodeData header_r3c2;
+ header_r3c2.id = 12;
+ header_r3c2.role = ax::mojom::Role::kColumnHeader;
+ header_r3c2.SetName(L"header_r3c2");
+ row3.child_ids.push_back(header_r3c2.id);
+
+ Init(root, row1, row2, row3, header_r1c1, header_r1c2, header_r1c3, cell_r2c1,
+ cell_r2c2, cell_r2c3, cell_r3c1, header_r3c2);
+
+ ComPtr<ITableProvider> root_itableprovider(
+ QueryInterfaceFromNode<ITableProvider>(GetRootAsAXNode()));
+
+ base::win::ScopedSafearray safearray;
+ EXPECT_HRESULT_SUCCEEDED(
+ root_itableprovider->GetColumnHeaders(safearray.Receive()));
+ EXPECT_NE(nullptr, safearray.Get());
+
+ // Validate that we retrieve all column headers of the table and in the order
+ // below.
+ std::vector<std::wstring> expected_names = {L"header_r1c1", L"header_r1c2",
+ L"header_r3c2", L"header_r1c3"};
+ EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
+ expected_names);
+}
+
TEST_F(AXPlatformNodeWinTest, ITableProviderGetRowHeaders) {
AXNodeData root;
root.id = 1;
@@ -3690,6 +3917,7 @@ TEST_F(AXPlatformNodeWinTest, IA2GetAttribute) {
TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
AXNodeData root;
+ root.role = ax::mojom::Role::kList;
root.SetName("fake name");
root.AddStringAttribute(ax::mojom::StringAttribute::kAccessKey, "Ctrl+Q");
root.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en-us");
@@ -3699,7 +3927,6 @@ TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
root.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 2);
root.AddIntAttribute(ax::mojom::IntAttribute::kInvalidState, 1);
root.id = 1;
- root.role = ax::mojom::Role::kList;
AXNodeData child1;
child1.id = 2;
@@ -3725,7 +3952,8 @@ TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
EXPECT_UIA_BSTR_EQ(root_node, UIA_AriaPropertiesPropertyId,
L"readonly=true;expanded=false;multiline=false;"
L"multiselectable=false;required=false;setsize=2");
- EXPECT_UIA_BSTR_EQ(root_node, UIA_CulturePropertyId, L"en-us");
+ constexpr int en_us_lcid = 1033;
+ EXPECT_UIA_INT_EQ(root_node, UIA_CulturePropertyId, en_us_lcid);
EXPECT_UIA_BSTR_EQ(root_node, UIA_NamePropertyId, L"fake name");
EXPECT_UIA_INT_EQ(root_node, UIA_ControlTypePropertyId,
int{UIA_ListControlTypeId});
@@ -3814,6 +4042,93 @@ TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueIsDialog) {
UIA_IsDialogPropertyId, true);
}
+TEST_F(AXPlatformNodeWinTest,
+ UIAGetPropertyValueIsControlElementIgnoredInvisible) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {2, 3, 4, 5, 6, 7, 8};
+
+ AXNodeData normal_button;
+ normal_button.id = 2;
+ normal_button.role = ax::mojom::Role::kButton;
+
+ AXNodeData ignored_button;
+ ignored_button.id = 3;
+ ignored_button.role = ax::mojom::Role::kButton;
+ ignored_button.AddState(ax::mojom::State::kIgnored);
+
+ AXNodeData invisible_button;
+ invisible_button.id = 4;
+ invisible_button.role = ax::mojom::Role::kButton;
+ invisible_button.AddState(ax::mojom::State::kInvisible);
+
+ AXNodeData invisible_focusable_button;
+ invisible_focusable_button.id = 5;
+ invisible_focusable_button.role = ax::mojom::Role::kButton;
+ invisible_focusable_button.AddState(ax::mojom::State::kInvisible);
+ invisible_focusable_button.AddState(ax::mojom::State::kFocusable);
+
+ AXNodeData focusable_generic_container;
+ focusable_generic_container.id = 6;
+ focusable_generic_container.role = ax::mojom::Role::kGenericContainer;
+ focusable_generic_container.AddState(ax::mojom::State::kFocusable);
+
+ AXNodeData ignored_focusable_generic_container;
+ ignored_focusable_generic_container.id = 7;
+ ignored_focusable_generic_container.role = ax::mojom::Role::kGenericContainer;
+ ignored_focusable_generic_container.AddState(ax::mojom::State::kIgnored);
+ focusable_generic_container.AddState(ax::mojom::State::kFocusable);
+
+ AXNodeData invisible_focusable_generic_container;
+ invisible_focusable_generic_container.id = 8;
+ invisible_focusable_generic_container.role =
+ ax::mojom::Role::kGenericContainer;
+ invisible_focusable_generic_container.AddState(ax::mojom::State::kInvisible);
+ invisible_focusable_generic_container.AddState(ax::mojom::State::kFocusable);
+
+ Init(root, normal_button, ignored_button, invisible_button,
+ invisible_focusable_button, focusable_generic_container,
+ ignored_focusable_generic_container,
+ invisible_focusable_generic_container);
+
+ // Turn on web content mode for the AXTree.
+ TestAXNodeWrapper::SetGlobalIsWebContent(true);
+
+ // Normal button (id=2), no invisible or ignored state set. Should be a
+ // control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
+ UIA_IsControlElementPropertyId, true);
+
+ // Button with ignored state (id=3). Should not be a control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
+ UIA_IsControlElementPropertyId, false);
+
+ // Button with invisible state (id=4). Should not be a control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(2),
+ UIA_IsControlElementPropertyId, false);
+
+ // Button with invisible state, but focusable (id=5). Should not be a control
+ // element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(3),
+ UIA_IsControlElementPropertyId, false);
+
+ // Generic container, focusable (id=6). Should be a control
+ // element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(4),
+ UIA_IsControlElementPropertyId, true);
+
+ // Generic container, ignored but focusable (id=7). Should not be a control
+ // element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(5),
+ UIA_IsControlElementPropertyId, false);
+
+ // Generic container, invisible and ignored, but focusable (id=8). Should not
+ // be a control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(6),
+ UIA_IsControlElementPropertyId, false);
+}
+
TEST_F(AXPlatformNodeWinTest, UIAGetControllerForPropertyId) {
AXNodeData root;
root.id = 1;
@@ -6251,6 +6566,65 @@ TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderGetSelectionContainer) {
EXPECT_EQ(container, container_provider);
}
+TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderSelectFollowFocus) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kTabList;
+
+ AXNodeData tab1;
+ tab1.id = 2;
+ tab1.role = ax::mojom::Role::kTab;
+ tab1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
+ tab1.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
+ root.child_ids.push_back(tab1.id);
+
+ Init(root, tab1);
+
+ auto* tab1_node = GetRootAsAXNode()->children()[0];
+ ComPtr<IRawElementProviderSimple> tab1_raw_element_provider_simple =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(tab1_node);
+ ASSERT_NE(nullptr, tab1_raw_element_provider_simple.Get());
+
+ ComPtr<IRawElementProviderFragment> tab1_raw_element_provider_fragment =
+ IRawElementProviderFragmentFromNode(tab1_node);
+ ASSERT_NE(nullptr, tab1_raw_element_provider_fragment.Get());
+
+ ComPtr<ISelectionItemProvider> tab1_selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(tab1_raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &tab1_selection_item_provider));
+ ASSERT_NE(nullptr, tab1_selection_item_provider.Get());
+
+ BOOL is_selected;
+ // Before setting focus to "tab1", validate that "tab1" has selected=false.
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_FALSE(is_selected);
+
+ // Setting focus on "tab1" will result in selected=true.
+ tab1_raw_element_provider_fragment->SetFocus();
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+
+ // Verify that we can still trigger action::kDoDefault through Select().
+ EXPECT_HRESULT_SUCCEEDED(tab1_selection_item_provider->Select());
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+ EXPECT_EQ(tab1_node, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
+ // Verify that after Select(), "tab1" is still selected.
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+
+ // Since last Select() performed |action::kDoDefault|, which set
+ // |kSelectedFromFocus| to false. Calling Select() again will not perform
+ // |action::kDoDefault| again.
+ TestAXNodeWrapper::SetNodeFromLastDefaultAction(nullptr);
+ EXPECT_HRESULT_SUCCEEDED(tab1_selection_item_provider->Select());
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+ // Verify that after Select(),|action::kDoDefault| was not triggered on
+ // "tab1".
+ EXPECT_EQ(nullptr, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
+}
+
TEST_F(AXPlatformNodeWinTest, IValueProvider_GetValue) {
AXNodeData root;
root.id = 1;
@@ -6512,4 +6886,120 @@ TEST_F(AXPlatformNodeWinTest, SanitizeStringAttributeForIA2) {
EXPECT_EQ("\\\\\\:\\=\\,\\;", output);
}
+//
+// IChromeAccessible tests
+//
+
+class TestIChromeAccessibleDelegate
+ : public CComObjectRootEx<CComMultiThreadModel>,
+ public IDispatchImpl<IChromeAccessibleDelegate> {
+ using IDispatchImpl::Invoke;
+
+ public:
+ BEGIN_COM_MAP(TestIChromeAccessibleDelegate)
+ COM_INTERFACE_ENTRY(IChromeAccessibleDelegate)
+ END_COM_MAP()
+
+ TestIChromeAccessibleDelegate() = default;
+ ~TestIChromeAccessibleDelegate() = default;
+
+ std::string WaitForBulkFetchResult(LONG expected_request_id) {
+ if (bulk_fetch_result_.empty())
+ WaitUsingRunLoop();
+ CHECK_EQ(expected_request_id, request_id_);
+ return bulk_fetch_result_;
+ }
+
+ IUnknown* WaitForHitTestResult(LONG expected_request_id) {
+ if (!hit_test_result_)
+ WaitUsingRunLoop();
+ CHECK_EQ(expected_request_id, request_id_);
+ return hit_test_result_.Get();
+ }
+
+ private:
+ void WaitUsingRunLoop() {
+ base::RunLoop run_loop;
+ run_loop_quit_closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+
+ IFACEMETHODIMP put_bulkFetchResult(LONG request_id, BSTR result) override {
+ bulk_fetch_result_ = base::WideToUTF8(result);
+ request_id_ = request_id;
+ if (run_loop_quit_closure_)
+ run_loop_quit_closure_.Run();
+ return S_OK;
+ }
+
+ IFACEMETHODIMP put_hitTestResult(LONG request_id, IUnknown* result) override {
+ hit_test_result_ = result;
+ request_id_ = request_id;
+ if (run_loop_quit_closure_)
+ run_loop_quit_closure_.Run();
+ return S_OK;
+ }
+
+ std::string bulk_fetch_result_;
+ ComPtr<IUnknown> hit_test_result_;
+ LONG request_id_ = 0;
+ base::RepeatingClosure run_loop_quit_closure_;
+};
+
+// http://crbug.com/1087206: failing on Win7 builders.
+TEST_F(AXPlatformNodeWinTest, DISABLED_BulkFetch) {
+ base::test::SingleThreadTaskEnvironment task_environment;
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kScrollBar;
+
+ Init(root);
+
+ ComPtr<IChromeAccessible> chrome_accessible =
+ QueryInterfaceFromNode<IChromeAccessible>(GetRootAsAXNode());
+
+ CComObject<TestIChromeAccessibleDelegate>* delegate = nullptr;
+ ASSERT_HRESULT_SUCCEEDED(
+ CComObject<TestIChromeAccessibleDelegate>::CreateInstance(&delegate));
+ ScopedBstr input_bstr(L"Potato");
+ chrome_accessible->get_bulkFetch(input_bstr.Get(), 99, delegate);
+ std::string response = delegate->WaitForBulkFetchResult(99);
+
+ // Note: base::JSONReader is fine for unit tests, but production code
+ // that parses untrusted JSON should always use DataDecoder instead.
+ base::Optional<base::Value> result =
+ base::JSONReader::Read(response, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result->FindKey("role"));
+ ASSERT_EQ("scrollBar", result->FindKey("role")->GetString());
+}
+
+TEST_F(AXPlatformNodeWinTest, AsyncHitTest) {
+ base::test::SingleThreadTaskEnvironment task_environment;
+ AXNodeData root;
+ root.id = 50;
+ root.role = ax::mojom::Role::kArticle;
+ root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
+
+ Init(root);
+
+ ComPtr<IChromeAccessible> chrome_accessible =
+ QueryInterfaceFromNode<IChromeAccessible>(GetRootAsAXNode());
+
+ CComObject<TestIChromeAccessibleDelegate>* delegate = nullptr;
+ ASSERT_HRESULT_SUCCEEDED(
+ CComObject<TestIChromeAccessibleDelegate>::CreateInstance(&delegate));
+ ScopedBstr input_bstr(L"Potato");
+ chrome_accessible->get_hitTest(400, 300, 12345, delegate);
+ ComPtr<IUnknown> result = delegate->WaitForHitTestResult(12345);
+ ComPtr<IAccessible2> accessible = ToIAccessible2(result);
+ LONG result_unique_id = 0;
+ ASSERT_HRESULT_SUCCEEDED(accessible->get_uniqueID(&result_unique_id));
+ ComPtr<IAccessible2> root_accessible =
+ QueryInterfaceFromNode<IAccessible2>(GetRootAsAXNode());
+ LONG root_unique_id = 0;
+ ASSERT_HRESULT_SUCCEEDED(root_accessible->get_uniqueID(&root_unique_id));
+ ASSERT_EQ(root_unique_id, result_unique_id);
+}
+
} // namespace ui
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 400be7c18c3..1fb54910d86 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
@@ -10,6 +10,7 @@
#include <memory>
#include <unordered_set>
+#include "base/test/scoped_feature_list.h"
#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
#include "ui/base/win/accessibility_misc_utils.h"
@@ -98,6 +99,8 @@ class AXPlatformNodeWinTest : public AXPlatformNodeTest {
std::unique_ptr<AXFragmentRootWin> ax_fragment_root_;
std::unique_ptr<TestFragmentRootDelegate> test_fragment_root_delegate_;
+
+ base::test::ScopedFeatureList scoped_feature_list_;
};
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_relation_win.cc b/chromium/ui/accessibility/platform/ax_platform_relation_win.cc
index 5f03ec806bf..eee92e3b9cb 100644
--- a/chromium/ui/accessibility/platform/ax_platform_relation_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_relation_win.cc
@@ -43,6 +43,12 @@ base::string16 GetIA2RelationFromIntAttr(ax::mojom::IntAttribute attribute) {
return IA2_RELATION_MEMBER_OF;
case ax::mojom::IntAttribute::kErrormessageId:
return IA2_RELATION_ERROR;
+ case ax::mojom::IntAttribute::kPopupForId:
+ // Map "popup for" to "controlled by".
+ // Unlike ATK there is no special IA2 popup-for relationship, but it can
+ // be exposed via the controlled by relation, which is also computed for
+ // content as the reverse of the controls relationship.
+ return IA2_RELATION_CONTROLLED_BY;
default:
break;
}
diff --git a/chromium/ui/accessibility/platform/ichromeaccessible.idl b/chromium/ui/accessibility/platform/ichromeaccessible.idl
new file mode 100644
index 00000000000..f3567d1ba74
--- /dev/null
+++ b/chromium/ui/accessibility/platform/ichromeaccessible.idl
@@ -0,0 +1,64 @@
+// Copyright 2020 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.
+
+import "objidl.idl";
+import "oaidl.idl";
+
+const long DISPID_CHROME_BULK_FETCH = -1600;
+const long DISPID_CHROME_ON_BULK_FETCH_RESULT = -1601;
+const long DISPID_CHROME_HIT_TEST = -1602;
+const long DISPID_CHROME_ON_HIT_TEST_RESULT = -1603;
+
+// Interface to be implemented by the client that calls IChromeAccessible.
+// For every method in IChromeAccessible, there's a corresponding response
+// method in IChromeAccessibleDelegate.
+[object, uuid(0e3edc14-79f4-413f-b854-d3b6860d74a2), pointer_default(unique)]
+interface IChromeAccessibleDelegate : IUnknown
+{
+ [propput, id(DISPID_CHROME_ON_BULK_FETCH_RESULT)] HRESULT bulkFetchResult(
+ [in] LONG requestID,
+ [in] BSTR resultJson
+ );
+
+ [propput, id(DISPID_CHROME_ON_HIT_TEST_RESULT)] HRESULT hitTestResult(
+ [in] LONG requestID,
+ [in] IUnknown* result
+ );
+};
+
+// Chrome-specific interface exposed on every IAccessible object.
+//
+// This interface is EXPERIMENTAL and only available behind a flag.
+// Run Chrome with --enable-features=IChromeAccessible to use it.
+//
+// Do not depend on this interface remaining stable! It's only designed
+// for prototyping ideas, and anything that's stabilized should move to
+// an open standard API.
+[object, uuid(6175bd95-3b2e-4ebc-bc51-9cab782bec92), pointer_default(unique)]
+interface IChromeAccessible : IUnknown
+{
+ // TODO(crbug.com/1083834): Fully document this interface.
+ // Fetch multiple accessibility properties of one or more accessibility
+ // nodes as JSON. This method is asynchronous; the result is returned
+ // by calling put_bulkFetchResult on |delegate|. The client can pass any
+ // valid LONG as requestID and the same value will be passed to
+ // put_bulkFetchResult to enable matching of requests and responses.
+ [propget, id(DISPID_CHROME_BULK_FETCH)] HRESULT bulkFetch(
+ [in] BSTR inputJson,
+ [in] LONG requestID,
+ [in] IChromeAccessibleDelegate* delegate
+ );
+
+ // Hit-test the given pixel in screen physical pixel coordinates.
+ // This method is asynchronous; the result is returned
+ // by calling put_hitTestResult on |delegate|. The client can pass any
+ // valid LONG as requestID and the same value will be passed to
+ // put_hitTestResult to enable matching of requests and responses.
+ [propget, id(DISPID_CHROME_HIT_TEST)] HRESULT hitTest(
+ [in] LONG screenPhysicalPixelX,
+ [in] LONG screenPhysicalPixelY,
+ [in] LONG requestID,
+ [in] IChromeAccessibleDelegate* delegate
+ );
+};
diff --git a/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc b/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
index 85283d36f99..873ab5c1985 100644
--- a/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -99,6 +99,11 @@ const AXNode* TestAXNodeWrapper::GetNodeFromLastDefaultAction() {
}
// static
+void TestAXNodeWrapper::SetNodeFromLastDefaultAction(AXNode* node) {
+ g_node_from_last_default_action = node;
+}
+
+// static
std::unique_ptr<base::AutoReset<float>> TestAXNodeWrapper::SetScaleFactor(
float value) {
return std::make_unique<base::AutoReset<float>>(&g_scale_factor, value);
@@ -459,30 +464,22 @@ base::Optional<bool> TestAXNodeWrapper::GetTableHasColumnOrRowHeaderNode()
return node_->GetTableHasColumnOrRowHeaderNode();
}
-std::vector<int32_t> TestAXNodeWrapper::GetColHeaderNodeIds() const {
- std::vector<int32_t> header_ids;
- node_->GetTableCellColHeaderNodeIds(&header_ids);
- return header_ids;
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetColHeaderNodeIds() const {
+ return node_->GetTableColHeaderNodeIds();
}
-std::vector<int32_t> TestAXNodeWrapper::GetColHeaderNodeIds(
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetColHeaderNodeIds(
int col_index) const {
- std::vector<int32_t> header_ids;
- node_->GetTableColHeaderNodeIds(col_index, &header_ids);
- return header_ids;
+ return node_->GetTableColHeaderNodeIds(col_index);
}
-std::vector<int32_t> TestAXNodeWrapper::GetRowHeaderNodeIds() const {
- std::vector<int32_t> header_ids;
- node_->GetTableCellRowHeaderNodeIds(&header_ids);
- return header_ids;
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetRowHeaderNodeIds() const {
+ return node_->GetTableCellRowHeaderNodeIds();
}
-std::vector<int32_t> TestAXNodeWrapper::GetRowHeaderNodeIds(
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetRowHeaderNodeIds(
int row_index) const {
- std::vector<int32_t> header_ids;
- node_->GetTableRowHeaderNodeIds(row_index, &header_ids);
- return header_ids;
+ return node_->GetTableRowHeaderNodeIds(row_index);
}
bool TestAXNodeWrapper::IsTableRow() const {
@@ -586,6 +583,15 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
}
case ax::mojom::Action::kDoDefault: {
+ // If a default action such as a click is performed on an element, it
+ // could result in a selected state change. In which case, the element's
+ // selected state no longer comes from focus action, so we should set
+ // |kSelectedFromFocus| to false.
+ if (GetData().HasBoolAttribute(
+ ax::mojom::BoolAttribute::kSelectedFromFocus))
+ ReplaceBoolAttribute(ax::mojom::BoolAttribute::kSelectedFromFocus,
+ false);
+
switch (GetData().role) {
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kCell: {
@@ -611,7 +617,7 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
default:
break;
}
- g_node_from_last_default_action = node_;
+ SetNodeFromLastDefaultAction(node_);
return true;
}
@@ -636,9 +642,21 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
return true;
}
- case ax::mojom::Action::kFocus:
+ case ax::mojom::Action::kFocus: {
g_focused_node_in_tree[tree_] = node_;
+
+ // The platform has select follows focus behavior:
+ // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_selection_follows_focus
+ // For test purpose, we support select follows focus for all elements, and
+ // not just single-selection container elements.
+ if (SupportsSelected(GetData().role)) {
+ ReplaceBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
+ ReplaceBoolAttribute(ax::mojom::BoolAttribute::kSelectedFromFocus,
+ true);
+ }
+
return true;
+ }
case ax::mojom::Action::kShowContextMenu:
g_node_from_last_show_context_menu = node_;
diff --git a/chromium/ui/accessibility/platform/test_ax_node_wrapper.h b/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
index 2f0998c7bab..4cd51d5874c 100644
--- a/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -41,6 +41,10 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
// called from for testing.
static const AXNode* GetNodeFromLastDefaultAction();
+ // Set the last node which AccessibilityPerformAction default action was
+ // called for testing.
+ static void SetNodeFromLastDefaultAction(AXNode* node);
+
// Set a global scale factor for testing.
static std::unique_ptr<base::AutoReset<float>> SetScaleFactor(float value);
diff --git a/chromium/ui/accessibility/platform/uia_registrar_win.cc b/chromium/ui/accessibility/platform/uia_registrar_win.cc
new file mode 100644
index 00000000000..bd6ca8f56aa
--- /dev/null
+++ b/chromium/ui/accessibility/platform/uia_registrar_win.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 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/uia_registrar_win.h"
+#include <wrl/implements.h>
+#include "base/stl_util.h"
+
+namespace ui {
+
+UiaRegistrarWin::UiaRegistrarWin() {
+ // Create the registrar object and get the IUIAutomationRegistrar
+ // interface pointer.
+ Microsoft::WRL::ComPtr<IUIAutomationRegistrar> registrar;
+ if (FAILED(CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar,
+ &registrar)))
+ return;
+
+ // Register the custom UIA property that represents the unique id of an UIA
+ // element which also matches its corresponding IA2 element's unique id.
+ UIAutomationPropertyInfo unique_id_property_info = {
+ kUiaPropertyUniqueIdGuid, L"UniqueId", UIAutomationType_String};
+ registrar->RegisterProperty(&unique_id_property_info,
+ &uia_unique_id_property_id_);
+
+ // Register the custom UIA event that represents the test end event for the
+ // UIA test suite.
+ UIAutomationEventInfo test_complete_event_info = {
+ kUiaEventTestCompleteSentinelGuid, L"kUiaTestCompleteSentinel"};
+ registrar->RegisterEvent(&test_complete_event_info,
+ &uia_test_complete_event_id_);
+}
+
+UiaRegistrarWin::~UiaRegistrarWin() = default;
+
+PROPERTYID UiaRegistrarWin::GetUiaUniqueIdPropertyId() const {
+ return uia_unique_id_property_id_;
+}
+
+EVENTID UiaRegistrarWin::GetUiaTestCompleteEventId() const {
+ return uia_test_complete_event_id_;
+}
+
+const UiaRegistrarWin& UiaRegistrarWin::GetInstance() {
+ static base::NoDestructor<UiaRegistrarWin> instance;
+ return *instance;
+}
+
+} // namespace ui
diff --git a/chromium/ui/accessibility/platform/uia_registrar_win.h b/chromium/ui/accessibility/platform/uia_registrar_win.h
new file mode 100644
index 00000000000..53c8da4fe37
--- /dev/null
+++ b/chromium/ui/accessibility/platform/uia_registrar_win.h
@@ -0,0 +1,45 @@
+// Copyright 2020 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_UIA_REGISTRAR_WIN_H_
+#define UI_ACCESSIBILITY_PLATFORM_UIA_REGISTRAR_WIN_H_
+
+#include <objbase.h>
+#include <uiautomation.h>
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "ui/accessibility/ax_export.h"
+
+namespace ui {
+// {3761326A-34B2-465A-835D-7A3D8F4EFB92}
+static const GUID kUiaEventTestCompleteSentinelGuid = {
+ 0x3761326a,
+ 0x34b2,
+ 0x465a,
+ {0x83, 0x5d, 0x7a, 0x3d, 0x8f, 0x4e, 0xfb, 0x92}};
+
+// {cc7eeb32-4b62-4f4c-aff6-1c2e5752ad8e}
+static const GUID kUiaPropertyUniqueIdGuid = {
+ 0xcc7eeb32,
+ 0x4b62,
+ 0x4f4c,
+ {0xaf, 0xf6, 0x1c, 0x2e, 0x57, 0x52, 0xad, 0x8e}};
+
+class AX_EXPORT UiaRegistrarWin {
+ public:
+ UiaRegistrarWin();
+ ~UiaRegistrarWin();
+ PROPERTYID GetUiaUniqueIdPropertyId() const;
+ EVENTID GetUiaTestCompleteEventId() const;
+
+ static const UiaRegistrarWin& GetInstance();
+
+ private:
+ PROPERTYID uia_unique_id_property_id_ = 0;
+ EVENTID uia_test_complete_event_id_ = 0;
+};
+
+} // namespace ui
+
+#endif // UI_ACCESSIBILITY_PLATFORM_UIA_REGISTRAR_WIN_H_
diff --git a/chromium/ui/accessibility/test_ax_node_helper.cc b/chromium/ui/accessibility/test_ax_node_helper.cc
new file mode 100644
index 00000000000..ba257f7fffe
--- /dev/null
+++ b/chromium/ui/accessibility/test_ax_node_helper.cc
@@ -0,0 +1,204 @@
+// Copyright 2020 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_node_helper.h"
+
+#include <map>
+#include <utility>
+
+#include "base/numerics/ranges.h"
+#include "base/stl_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/accessibility/ax_action_data.h"
+#include "ui/accessibility/ax_role_properties.h"
+#include "ui/accessibility/ax_table_info.h"
+#include "ui/accessibility/ax_tree_observer.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+
+namespace ui {
+
+namespace {
+
+// A global map from AXNodes to TestAXNodeHelpers.
+std::map<AXNode::AXID, TestAXNodeHelper*> g_node_id_to_helper_map;
+
+// A simple implementation of AXTreeObserver to catch when AXNodes are
+// deleted so we can delete their helpers.
+class TestAXTreeObserver : public AXTreeObserver {
+ private:
+ void OnNodeDeleted(AXTree* tree, int32_t node_id) override {
+ const auto iter = g_node_id_to_helper_map.find(node_id);
+ if (iter != g_node_id_to_helper_map.end()) {
+ TestAXNodeHelper* helper = iter->second;
+ delete helper;
+ g_node_id_to_helper_map.erase(node_id);
+ }
+ }
+};
+
+TestAXTreeObserver g_ax_tree_observer;
+
+} // namespace
+
+// static
+TestAXNodeHelper* TestAXNodeHelper::GetOrCreate(AXTree* tree, AXNode* node) {
+ if (!tree || !node)
+ return nullptr;
+
+ if (!tree->HasObserver(&g_ax_tree_observer))
+ tree->AddObserver(&g_ax_tree_observer);
+ auto iter = g_node_id_to_helper_map.find(node->id());
+ if (iter != g_node_id_to_helper_map.end())
+ return iter->second;
+ TestAXNodeHelper* helper = new TestAXNodeHelper(tree, node);
+ g_node_id_to_helper_map[node->id()] = helper;
+ return helper;
+}
+
+TestAXNodeHelper::TestAXNodeHelper(AXTree* tree, AXNode* node)
+ : tree_(tree), node_(node) {}
+
+TestAXNodeHelper::~TestAXNodeHelper() = default;
+
+gfx::Rect TestAXNodeHelper::GetBoundsRect(
+ const AXCoordinateSystem coordinate_system,
+ const AXClippingBehavior clipping_behavior,
+ AXOffscreenResult* offscreen_result) const {
+ switch (coordinate_system) {
+ case AXCoordinateSystem::kScreenPhysicalPixels:
+ // For unit testing purposes, assume a device scale factor of 1 and fall
+ // through.
+ case AXCoordinateSystem::kScreenDIPs: {
+ // We could optionally add clipping here if ever needed.
+ gfx::RectF bounds = GetLocation();
+
+ // For test behavior only, for bounds that are offscreen we currently do
+ // not apply clipping to the bounds but we still return the offscreen
+ // status.
+ if (offscreen_result) {
+ *offscreen_result = DetermineOffscreenResult(bounds);
+ }
+
+ return gfx::ToEnclosingRect(bounds);
+ }
+ case AXCoordinateSystem::kRootFrame:
+ case AXCoordinateSystem::kFrame:
+ NOTIMPLEMENTED();
+ return gfx::Rect();
+ }
+}
+
+gfx::Rect TestAXNodeHelper::GetInnerTextRangeBoundsRect(
+ const int start_offset,
+ const int end_offset,
+ const AXCoordinateSystem coordinate_system,
+ const AXClippingBehavior clipping_behavior,
+ AXOffscreenResult* offscreen_result) const {
+ switch (coordinate_system) {
+ case AXCoordinateSystem::kScreenPhysicalPixels:
+ // For unit testing purposes, assume a device scale factor of 1 and fall
+ // through.
+ case AXCoordinateSystem::kScreenDIPs: {
+ gfx::RectF bounds = GetLocation();
+ // This implementation currently only deals with text node that has role
+ // kInlineTextBox and kStaticText.
+ // For test purposes, assume node with kStaticText always has a single
+ // child with role kInlineTextBox.
+ if (GetData().role == ax::mojom::Role::kInlineTextBox) {
+ bounds = GetInlineTextRect(start_offset, end_offset);
+ } else if (GetData().role == ax::mojom::Role::kStaticText &&
+ InternalChildCount() > 0) {
+ TestAXNodeHelper* child = InternalGetChild(0);
+ if (child != nullptr &&
+ child->GetData().role == ax::mojom::Role::kInlineTextBox) {
+ bounds = child->GetInlineTextRect(start_offset, end_offset);
+ }
+ }
+
+ // For test behavior only, for bounds that are offscreen we currently do
+ // not apply clipping to the bounds but we still return the offscreen
+ // status.
+ if (offscreen_result) {
+ *offscreen_result = DetermineOffscreenResult(bounds);
+ }
+
+ return gfx::ToEnclosingRect(bounds);
+ }
+ case AXCoordinateSystem::kRootFrame:
+ case AXCoordinateSystem::kFrame:
+ NOTIMPLEMENTED();
+ return gfx::Rect();
+ }
+}
+
+const AXNodeData& TestAXNodeHelper::GetData() const {
+ return node_->data();
+}
+
+gfx::RectF TestAXNodeHelper::GetLocation() const {
+ return GetData().relative_bounds.bounds;
+}
+
+int TestAXNodeHelper::InternalChildCount() const {
+ return int{node_->GetUnignoredChildCount()};
+}
+
+TestAXNodeHelper* TestAXNodeHelper::InternalGetChild(int index) const {
+ CHECK_GE(index, 0);
+ CHECK_LT(index, InternalChildCount());
+ return GetOrCreate(tree_, node_->GetUnignoredChildAtIndex(size_t{index}));
+}
+
+gfx::RectF TestAXNodeHelper::GetInlineTextRect(const int start_offset,
+ const int end_offset) const {
+ DCHECK(start_offset >= 0 && end_offset >= 0 && start_offset <= end_offset);
+ const std::vector<int32_t>& character_offsets = GetData().GetIntListAttribute(
+ ax::mojom::IntListAttribute::kCharacterOffsets);
+ gfx::RectF location = GetLocation();
+ gfx::RectF bounds;
+
+ switch (static_cast<ax::mojom::TextDirection>(
+ GetData().GetIntAttribute(ax::mojom::IntAttribute::kTextDirection))) {
+ // Currently only kNone and kLtr are supported text direction.
+ case ax::mojom::TextDirection::kNone:
+ case ax::mojom::TextDirection::kLtr: {
+ int start_pixel_offset =
+ start_offset > 0 ? character_offsets[start_offset - 1] : location.x();
+ int end_pixel_offset =
+ end_offset > 0 ? character_offsets[end_offset - 1] : location.x();
+ bounds =
+ gfx::RectF(start_pixel_offset, location.y(),
+ end_pixel_offset - start_pixel_offset, location.height());
+ break;
+ }
+ default:
+ NOTIMPLEMENTED();
+ }
+ return bounds;
+}
+
+AXOffscreenResult TestAXNodeHelper::DetermineOffscreenResult(
+ gfx::RectF bounds) const {
+ if (!tree_ || !tree_->root())
+ return AXOffscreenResult::kOnscreen;
+
+ const AXNodeData& root_web_area_node_data = tree_->root()->data();
+ gfx::RectF root_web_area_bounds =
+ root_web_area_node_data.relative_bounds.bounds;
+
+ // For testing, we only look at the current node's bound relative to the root
+ // web area bounds to determine offscreen status. We currently do not look at
+ // the bounds of the immediate parent of the node for determining offscreen
+ // status.
+ // We only determine offscreen result if the root web area bounds is actually
+ // set in the test. We default the offscreen result of every other situation
+ // to AXOffscreenResult::kOnscreen.
+ if (!root_web_area_bounds.IsEmpty()) {
+ bounds.Intersect(root_web_area_bounds);
+ if (bounds.IsEmpty())
+ return AXOffscreenResult::kOffscreen;
+ }
+ return AXOffscreenResult::kOnscreen;
+}
+} // namespace ui
diff --git a/chromium/ui/accessibility/test_ax_node_helper.h b/chromium/ui/accessibility/test_ax_node_helper.h
new file mode 100644
index 00000000000..a303b81abf9
--- /dev/null
+++ b/chromium/ui/accessibility/test_ax_node_helper.h
@@ -0,0 +1,50 @@
+// Copyright 2020 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_NODE_HELPER_H_
+#define UI_ACCESSIBILITY_TEST_AX_NODE_HELPER_H_
+
+#include "ui/accessibility/ax_clipping_behavior.h"
+#include "ui/accessibility/ax_coordinate_system.h"
+#include "ui/accessibility/ax_node.h"
+#include "ui/accessibility/ax_offscreen_result.h"
+#include "ui/accessibility/ax_tree.h"
+
+namespace ui {
+
+// For testing, a TestAXNodeHelper wraps an AXNode. This is a simple
+// version of TestAXNodeWrapper.
+class TestAXNodeHelper {
+ public:
+ // Create TestAXNodeHelper instances on-demand from an AXTree and AXNode.
+ static TestAXNodeHelper* GetOrCreate(AXTree* tree, AXNode* node);
+ ~TestAXNodeHelper();
+
+ gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system,
+ const AXClippingBehavior clipping_behavior,
+ AXOffscreenResult* offscreen_result) const;
+ gfx::Rect GetInnerTextRangeBoundsRect(
+ const int start_offset,
+ const int end_offset,
+ const AXCoordinateSystem coordinate_system,
+ const AXClippingBehavior clipping_behavior,
+ AXOffscreenResult* offscreen_result) const;
+
+ private:
+ TestAXNodeHelper(AXTree* tree, AXNode* node);
+ int InternalChildCount() const;
+ TestAXNodeHelper* InternalGetChild(int index) const;
+ const AXNodeData& GetData() const;
+ gfx::RectF GetLocation() const;
+ gfx::RectF GetInlineTextRect(const int start_offset,
+ const int end_offset) const;
+ AXOffscreenResult DetermineOffscreenResult(gfx::RectF bounds) const;
+
+ AXTree* tree_;
+ AXNode* node_;
+};
+
+} // namespace ui
+
+#endif // UI_ACCESSIBILITY_TEST_AX_NODE_HELPER_H_