summaryrefslogtreecommitdiff
path: root/chromium/ui/accessibility/platform
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-01-23 17:21:03 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-01-23 16:25:15 +0000
commitc551f43206405019121bd2b2c93714319a0a3300 (patch)
tree1f48c30631c421fd4bbb3c36da20183c8a2ed7d7 /chromium/ui/accessibility/platform
parent7961cea6d1041e3e454dae6a1da660b453efd238 (diff)
downloadqtwebengine-chromium-c551f43206405019121bd2b2c93714319a0a3300.tar.gz
BASELINE: Update Chromium to 79.0.3945.139
Change-Id: I336b7182fab9bca80b709682489c07db112eaca5 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/accessibility/platform')
-rw-r--r--chromium/ui/accessibility/platform/atk_util_auralinux.cc75
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win.cc18
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win.h4
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc25
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc18
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.h4
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc623
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.h77
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc44
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.cc161
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.h10
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate.h34
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc24
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h15
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_mac.mm65
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc481
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h16
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc483
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_unittest.cc30
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_unittest.h4
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.cc334
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.h19
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc387
-rw-r--r--chromium/ui/accessibility/platform/test_ax_node_wrapper.cc102
-rw-r--r--chromium/ui/accessibility/platform/test_ax_node_wrapper.h10
26 files changed, 1991 insertions, 1074 deletions
diff --git a/chromium/ui/accessibility/platform/atk_util_auralinux.cc b/chromium/ui/accessibility/platform/atk_util_auralinux.cc
index 60d24858e33..5eec1b51f99 100644
--- a/chromium/ui/accessibility/platform/atk_util_auralinux.cc
+++ b/chromium/ui/accessibility/platform/atk_util_auralinux.cc
@@ -22,51 +22,23 @@ const char* kAccessibilityEnabledVariables[] = {
"QT_ACCESSIBILITY",
};
-} // namespace
-
-G_BEGIN_DECLS
-
//
-// atk_util_auralinux AtkObject definition and implementation.
+// AtkUtilAuraLinux definition and implementation.
//
-
-#define ATK_UTIL_AURALINUX_TYPE (atk_util_auralinux_get_type())
-#define ATK_UTIL_AURALINUX(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST((obj), \
- ATK_UTIL_AURALINUX_TYPE, \
- AtkUtilAuraLinux))
-#define ATK_UTIL_AURALINUX_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- ATK_UTIL_AURALINUX_TYPE, \
- AtkUtilAuraLinuxClass))
-#define IS_ATK_UTIL_AURALINUX(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE((obj), ATK_UTIL_AURALINUX_TYPE))
-#define IS_ATK_UTIL_AURALINUX_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), ATK_UTIL_AURALINUX_TYPE))
-#define ATK_UTIL_AURALINUX_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS((obj), \
- ATK_UTIL_AURALINUX_TYPE, \
- AtkUtilAuraLinuxClass))
-
-typedef struct _AtkUtilAuraLinux AtkUtilAuraLinux;
-typedef struct _AtkUtilAuraLinuxClass AtkUtilAuraLinuxClass;
-
-struct _AtkUtilAuraLinux {
+struct AtkUtilAuraLinux {
AtkUtil parent;
};
-struct _AtkUtilAuraLinuxClass {
+struct AtkUtilAuraLinuxClass {
AtkUtilClass parent_class;
};
-GType atk_util_auralinux_get_type();
-
G_DEFINE_TYPE(AtkUtilAuraLinux, atk_util_auralinux, ATK_TYPE_UTIL)
static void atk_util_auralinux_init(AtkUtilAuraLinux *ax_util) {
}
-static AtkObject* atk_util_auralinux_get_root() {
+static AtkObject* AtkUtilAuraLinuxGetRoot() {
ui::AXPlatformNode* application = ui::AXPlatformNodeAuraLinux::application();
if (application) {
return application->GetNativeViewAccessible();
@@ -74,22 +46,15 @@ static AtkObject* atk_util_auralinux_get_root() {
return nullptr;
}
-static G_CONST_RETURN gchar* atk_util_auralinux_get_toolkit_name(void) {
- return "Chromium";
-}
-
-static G_CONST_RETURN gchar* atk_util_auralinux_get_toolkit_version(void) {
- return "1.0";
-}
-
using KeySnoopFuncMap = std::map<guint, std::pair<AtkKeySnoopFunc, gpointer>>;
static KeySnoopFuncMap& GetActiveKeySnoopFunctions() {
static base::NoDestructor<KeySnoopFuncMap> active_key_snoop_functions;
return *active_key_snoop_functions;
}
-static guint atk_util_add_key_event_listener(AtkKeySnoopFunc key_snoop_function,
- gpointer data) {
+static guint AtkUtilAuraLinuxAddKeyEventListener(
+ AtkKeySnoopFunc key_snoop_function,
+ gpointer data) {
static guint current_key_event_listener_id = 0;
current_key_event_listener_id++;
@@ -98,29 +63,21 @@ static guint atk_util_add_key_event_listener(AtkKeySnoopFunc key_snoop_function,
return current_key_event_listener_id;
}
-static void atk_util_remove_key_event_listener(guint listener_id) {
+static void AtkUtilAuraLinuxRemoveKeyEventListener(guint listener_id) {
GetActiveKeySnoopFunctions().erase(listener_id);
}
static void atk_util_auralinux_class_init(AtkUtilAuraLinuxClass *klass) {
- AtkUtilClass* atk_class;
- gpointer data;
+ AtkUtilClass* atk_class = ATK_UTIL_CLASS(g_type_class_peek(ATK_TYPE_UTIL));
- data = g_type_class_peek(ATK_TYPE_UTIL);
- atk_class = ATK_UTIL_CLASS(data);
-
- atk_class->get_root = atk_util_auralinux_get_root;
- atk_class->get_toolkit_name = atk_util_auralinux_get_toolkit_name;
- atk_class->get_toolkit_version = atk_util_auralinux_get_toolkit_version;
- atk_class->add_key_event_listener = atk_util_add_key_event_listener;
- atk_class->remove_key_event_listener = atk_util_remove_key_event_listener;
+ atk_class->get_root = AtkUtilAuraLinuxGetRoot;
+ atk_class->get_toolkit_name = []() { return "Chromium"; };
+ atk_class->get_toolkit_version = []() { return "1.0"; };
+ atk_class->add_key_event_listener = AtkUtilAuraLinuxAddKeyEventListener;
+ atk_class->remove_key_event_listener = AtkUtilAuraLinuxRemoveKeyEventListener;
}
-G_END_DECLS
-
-//
-// AtkUtilAuraLinuxClass implementation.
-//
+} // namespace
namespace ui {
@@ -150,7 +107,7 @@ void AtkUtilAuraLinux::InitializeAsync() {
initialized = true;
// Register our util class.
- g_type_class_unref(g_type_class_ref(ATK_UTIL_AURALINUX_TYPE));
+ g_type_class_unref(g_type_class_ref(atk_util_auralinux_get_type()));
PlatformInitializeAsync();
}
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
index cd12e01f0ca..a4380c4b846 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -90,14 +90,6 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
return S_OK;
}
- STDMETHOD(GetRuntimeId)(SAFEARRAY** runtime_id) {
- UIA_VALIDATE_CALL_1_ARG(runtime_id);
-
- // UIA obtains a runtime ID for a fragment root from the associated HWND.
- *runtime_id = nullptr;
- return S_OK;
- }
-
//
// IRawElementProviderFragmentRoot methods.
//
@@ -272,6 +264,16 @@ AXFragmentRootWin::GetTargetForNativeAccessibilityEvent() {
return widget_;
}
+AXPlatformNode* AXFragmentRootWin::GetFromTreeIDAndNodeID(
+ const ui::AXTreeID& ax_tree_id,
+ int32_t node_id) {
+ AXPlatformNodeDelegate* child_delegate = GetChildNodeDelegate();
+ if (child_delegate)
+ return child_delegate->GetFromTreeIDAndNodeID(ax_tree_id, node_id);
+
+ return nullptr;
+}
+
AXPlatformNodeDelegate* AXFragmentRootWin::GetChildNodeDelegate() {
gfx::NativeViewAccessible child = delegate_->GetChildOfAXFragmentRoot();
if (child)
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win.h b/chromium/ui/accessibility/platform/ax_fragment_root_win.h
index c71af511aba..a765aebe558 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win.h
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win.h
@@ -39,7 +39,7 @@ class AX_EXPORT AXFragmentRootWin : public ui::AXPlatformNodeDelegateBase {
gfx::AcceleratedWidget widget);
// Returns the NativeViewAccessible for this fragment root.
- gfx::NativeViewAccessible GetNativeViewAccessible();
+ gfx::NativeViewAccessible GetNativeViewAccessible() override;
// Assistive technologies will typically use UI Automation's control or
// content view rather than the raw view.
@@ -56,6 +56,8 @@ class AX_EXPORT AXFragmentRootWin : public ui::AXPlatformNodeDelegateBase {
gfx::NativeViewAccessible GetFocus() override;
const ui::AXUniqueId& GetUniqueId() const override;
gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override;
+ AXPlatformNode* GetFromTreeIDAndNodeID(const ui::AXTreeID& ax_tree_id,
+ int32_t id) override;
// If a child node is available, return its delegate.
AXPlatformNodeDelegate* GetChildNodeDelegate();
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 1df70f4c769..87f2774a6c5 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
@@ -5,10 +5,12 @@
#include "ui/accessibility/platform/ax_fragment_root_win.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"
#include <UIAutomationClient.h>
#include <UIAutomationCoreApi.h>
+#include "base/auto_reset.h"
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -35,22 +37,6 @@ TEST_F(AXFragmentRootTest, TestUIAGetFragmentRoot) {
EXPECT_EQ(expected_fragment_root.Get(), actual_fragment_root.Get());
}
-TEST_F(AXFragmentRootTest, TestUIAGetRuntimeId) {
- AXNodeData root;
- Init(root);
- InitFragmentRoot();
-
- ComPtr<IRawElementProviderFragmentRoot> fragment_root_provider =
- GetFragmentRoot();
- ComPtr<IRawElementProviderFragment> fragment_provider;
- fragment_root_provider.As(&fragment_provider);
-
- base::win::ScopedSafearray runtime_id;
- EXPECT_HRESULT_SUCCEEDED(
- fragment_provider->GetRuntimeId(runtime_id.Receive()));
- EXPECT_EQ(runtime_id.Get(), nullptr);
-}
-
TEST_F(AXFragmentRootTest, TestUIAElementProviderFromPoint) {
AXNodeData root_data;
root_data.id = 1;
@@ -93,6 +79,13 @@ TEST_F(AXFragmentRootTest, TestUIAElementProviderFromPoint) {
EXPECT_HRESULT_SUCCEEDED(fragment_root_prov->ElementProviderFromPoint(
47, 67, &provider_from_point));
EXPECT_EQ(root_provider.Get(), provider_from_point.Get());
+
+ // This is on node 1 with scale factor of 1.5.
+ std::unique_ptr<base::AutoReset<float>> scale_factor_reset =
+ TestAXNodeWrapper::SetScaleFactor(1.5);
+ EXPECT_HRESULT_SUCCEEDED(fragment_root_prov->ElementProviderFromPoint(
+ 60, 60, &provider_from_point));
+ EXPECT_EQ(element1_provider.Get(), provider_from_point.Get());
}
TEST_F(AXFragmentRootTest, TestUIAGetFocus) {
diff --git a/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc b/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc
index c3d8f32a69d..e59b7c3a2d7 100644
--- a/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.cc
@@ -12,8 +12,6 @@ namespace ui {
struct _AXPlatformAtkHyperlinkPrivate {
AXPlatformNodeAuraLinux* platform_node = nullptr;
- base::Optional<int> end_index;
- base::Optional<int> start_index;
};
static gpointer kAXPlatformAtkHyperlinkParentClass = nullptr;
@@ -84,13 +82,17 @@ static gboolean AXPlatformAtkHyperlinkIsSelectedLink(
static int AXPlatformAtkHyperlinkGetStartIndex(AtkHyperlink* atk_hyperlink) {
g_return_val_if_fail(IS_AX_PLATFORM_ATK_HYPERLINK(atk_hyperlink), 0);
AXPlatformAtkHyperlink* link = AX_PLATFORM_ATK_HYPERLINK(atk_hyperlink);
- return link->priv->start_index ? *link->priv->start_index : 0;
+ base::Optional<std::pair<int, int>> indices =
+ link->priv->platform_node->GetEmbeddedObjectIndices();
+ return indices.has_value() ? indices->first : 0;
}
static int AXPlatformAtkHyperlinkGetEndIndex(AtkHyperlink* atk_hyperlink) {
g_return_val_if_fail(IS_AX_PLATFORM_ATK_HYPERLINK(atk_hyperlink), 0);
AXPlatformAtkHyperlink* link = AX_PLATFORM_ATK_HYPERLINK(atk_hyperlink);
- return link->priv->end_index ? *link->priv->end_index : 0;
+ base::Optional<std::pair<int, int>> indices =
+ link->priv->platform_node->GetEmbeddedObjectIndices();
+ return indices.has_value() ? indices->second : 0;
}
static void AXPlatformAtkHyperlinkClassInit(AtkHyperlinkClass* klass) {
@@ -231,14 +233,6 @@ void ax_platform_atk_hyperlink_set_object(
atk_hyperlink->priv->platform_node = platform_node;
}
-void ax_platform_atk_hyperlink_set_indices(
- AXPlatformAtkHyperlink* atk_hyperlink,
- int start_index,
- int end_index) {
- atk_hyperlink->priv->start_index = start_index;
- atk_hyperlink->priv->end_index = end_index;
-}
-
static void AXPlatformAtkHyperlinkInit(AXPlatformAtkHyperlink* self, gpointer) {
AXPlatformAtkHyperlinkPrivate* priv =
G_TYPE_INSTANCE_GET_PRIVATE(self, ax_platform_atk_hyperlink_get_type(),
diff --git a/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.h b/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.h
index 733d418ec20..30e9112038b 100644
--- a/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.h
+++ b/chromium/ui/accessibility/platform/ax_platform_atk_hyperlink.h
@@ -46,10 +46,6 @@ struct _AXPlatformAtkHyperlinkClass {
GType ax_platform_atk_hyperlink_get_type(void) G_GNUC_CONST;
void ax_platform_atk_hyperlink_set_object(AXPlatformAtkHyperlink* hyperlink,
AXPlatformNodeAuraLinux* obj);
-void ax_platform_atk_hyperlink_set_indices(
- AXPlatformAtkHyperlink* atk_hyperlink,
- int start_index,
- int end_index);
G_END_DECLS
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 1fd5a3e15ab..53b0513c7a0 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -18,6 +18,7 @@
#include "base/debug/leak_annotations.h"
#include "base/memory/protected_memory_cfi.h"
#include "base/no_destructor.h"
+#include "base/numerics/ranges.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
@@ -26,7 +27,6 @@
#include "base/strings/utf_string_conversion_utils.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
-#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_mode_observer.h"
@@ -61,6 +61,10 @@
#define ATK_230
#endif
+#if defined(ATK_CHECK_VERSION) && ATK_CHECK_VERSION(2, 34, 0)
+#define ATK_234
+#endif
+
namespace ui {
namespace {
@@ -145,6 +149,14 @@ constexpr AtkRole kAtkFootnoteRole = ATK_ROLE_FOOTNOTE;
constexpr AtkRole kAtkFootnoteRole = ATK_ROLE_LIST_ITEM;
#endif
+#if defined(ATK_234)
+constexpr AtkRole kAtkRoleContentDeletion = ATK_ROLE_CONTENT_DELETION;
+constexpr AtkRole kAtkRoleContentInsertion = ATK_ROLE_CONTENT_INSERTION;
+#else
+constexpr AtkRole kAtkRoleContentDeletion = ATK_ROLE_SECTION;
+constexpr AtkRole kAtkRoleContentInsertion = ATK_ROLE_SECTION;
+#endif
+
AXPlatformNodeAuraLinux* AtkObjectToAXPlatformNodeAuraLinux(
AtkObject* atk_object) {
if (!atk_object)
@@ -191,21 +203,6 @@ AtkObject* FindAtkObjectToplevelParentDocument(AtkObject* atk_object) {
return toplevel_document;
}
-bool EmitsAtkTextEvents(AtkObject* atk_object) {
- // If this node is not a static text node, it supports the full AtkText
- // interface.
- AtkRole role = atk_object_get_role(atk_object);
- if (role != ATK_ROLE_TEXT)
- return true;
-
- // If this node is not a static text leaf node, it supports the full AtkText
- // interface.
- if (atk_object_get_n_accessible_children(atk_object))
- return true;
-
- return false;
-}
-
bool IsFrameAncestorOfAtkObject(AtkObject* frame, AtkObject* atk_object) {
AtkObject* current_frame = FindAtkObjectParentFrame(atk_object);
while (current_frame) {
@@ -353,14 +350,12 @@ AtkObject* GetActiveDescendantOfCurrentFocused() {
return nullptr;
}
-bool SelectionOffsetsIndicateSelection(const std::pair<int, int>& offsets) {
- return offsets.first >= 0 && offsets.second >= 0 &&
- offsets.first != offsets.second;
-}
-void AddTextAttributeToSet(const AtkTextAttribute attribute,
- const std::string& value,
- AtkAttributeSet** attributes) {
+void PrependTextAttributeToSet(const AtkTextAttribute attribute,
+ const std::string& value,
+ AtkAttributeSet** attributes) {
+ DCHECK(attributes);
+
AtkAttribute* new_attribute =
static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
new_attribute->name = g_strdup(atk_text_attribute_get_name(attribute));
@@ -368,59 +363,56 @@ void AddTextAttributeToSet(const AtkTextAttribute attribute,
*attributes = g_slist_prepend(*attributes, new_attribute);
}
-bool HasInvalidAttributeInSet(AtkAttributeSet* attributes) {
- const char* underline_attribute =
- atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE);
- DCHECK(underline_attribute);
-
- AtkAttributeSet* current = attributes;
- while (current) {
- const AtkAttribute* attribute = static_cast<AtkAttribute*>(current->data);
- if (!g_strcmp0(attribute->name, underline_attribute) &&
- !g_strcmp0(attribute->value, "error")) {
- return true;
- }
- current = g_slist_next(current);
- }
- return false;
+std::string ToAtkTextAttributeColor(const std::string color) {
+ // The platform-independent color string is in the form "rgb(r, g, b)",
+ // but ATK expects a string like "r, g, b". We convert the string here
+ // by stripping away the unnecessary characters.
+ DCHECK(base::StartsWith(color, "rgb(", base::CompareCase::INSENSITIVE_ASCII));
+ DCHECK(base::EndsWith(color, ")", base::CompareCase::INSENSITIVE_ASCII));
+ return color.substr(4, color.length() - 5);
}
-bool AttributeSetsEqual(AtkAttributeSet* one, AtkAttributeSet* two) {
- AtkAttributeSet* current_one = one;
- AtkAttributeSet* current_two = two;
-
- while (current_one && current_two) {
- const AtkAttribute* attribute_one =
- static_cast<AtkAttribute*>(current_one->data);
- const AtkAttribute* attribute_two =
- static_cast<AtkAttribute*>(current_two->data);
-
- if (g_strcmp0(attribute_one->name, attribute_two->name) ||
- g_strcmp0(attribute_one->value, attribute_two->value)) {
- return false;
- }
-
- current_one = g_slist_next(current_one);
- current_two = g_slist_next(current_two);
- }
-
- // The sets are only equal if they have the same length, which means that we
- // need to have iterated to the end of both sets.
- return !current_one && !current_two;
-}
-
-AtkAttributeSet* CopyAttributeSet(AtkAttributeSet* attributes) {
+AtkAttributeSet* ToAtkAttributeSet(const TextAttributeList& attributes) {
AtkAttributeSet* copied_attributes = nullptr;
- while (attributes) {
- const AtkAttribute* attribute =
- static_cast<const AtkAttribute*>(attributes->data);
- AtkAttribute* new_attribute =
- static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
- new_attribute->name = g_strdup(attribute->name);
- new_attribute->value = g_strdup(attribute->value);
- copied_attributes = g_slist_prepend(copied_attributes, new_attribute);
-
- attributes = g_slist_next(attributes);
+ for (const auto& attribute : attributes) {
+ if (attribute.first == "background-color") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_BG_COLOR,
+ ToAtkTextAttributeColor(attribute.second),
+ &copied_attributes);
+ } else if (attribute.first == "color") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_FG_COLOR,
+ ToAtkTextAttributeColor(attribute.second),
+ &copied_attributes);
+ } else if (attribute.first == "font-family") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_FAMILY_NAME, attribute.second,
+ &copied_attributes);
+ } else if (attribute.first == "font-size") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_SIZE, attribute.second,
+ &copied_attributes);
+ } else if (attribute.first == "font-weight" && attribute.second == "bold") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_WEIGHT, "700",
+ &copied_attributes);
+ } else if (attribute.first == "font-style") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_STYLE, "italic",
+ &copied_attributes);
+ } else if (attribute.first == "text-line-through-style") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_STRIKETHROUGH, "true",
+ &copied_attributes);
+ } else if (attribute.first == "text-underline-style") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_UNDERLINE, "single",
+ &copied_attributes);
+ } else if (attribute.first == "invalid") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_INVALID, attribute.second,
+ &copied_attributes);
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_UNDERLINE, "error",
+ &copied_attributes);
+ } else if (attribute.first == "language") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_LANGUAGE, attribute.second,
+ &copied_attributes);
+ } else if (attribute.first == "writing-mode") {
+ PrependTextAttributeToSet(ATK_TEXT_ATTR_DIRECTION, attribute.second,
+ &copied_attributes);
+ }
}
return g_slist_reverse(copied_attributes);
@@ -869,8 +861,7 @@ gchar* GetText(AtkText* atk_text, gint start_offset, gint end_offset) {
end_offset = text.size();
} else {
end_offset = obj->UnicodeToUTF16OffsetInText(end_offset);
- end_offset = std::max(start_offset,
- std::min(end_offset, static_cast<int>(text.size())));
+ end_offset = base::ClampToRange(int{text.size()}, start_offset, end_offset);
}
DCHECK_GE(start_offset, 0);
@@ -900,7 +891,7 @@ gunichar GetCharacterAtOffset(AtkText* atk_text, int offset) {
int32_t text_length = text.length();
offset = obj->UnicodeToUTF16OffsetInText(offset);
- int32_t limited_offset = std::max(0, std::min(text_length, offset));
+ int32_t limited_offset = base::ClampToRange(offset, 0, text_length);
uint32_t code_point;
base::ReadUnicodeCharacter(text.c_str(), text_length + 1, &limited_offset,
@@ -1251,7 +1242,7 @@ AtkAttributeSet* GetRunAttributes(AtkText* atk_text,
if (!obj)
return nullptr;
- return CopyAttributeSet(
+ return ToAtkAttributeSet(
obj->GetTextAttributes(offset, start_offset, end_offset));
}
@@ -1260,7 +1251,7 @@ AtkAttributeSet* GetDefaultAttributes(AtkText* atk_text) {
AXPlatformNodeAuraLinux* obj = AtkObjectToAXPlatformNodeAuraLinux(atk_object);
if (!obj)
return nullptr;
- return CopyAttributeSet(obj->GetDefaultTextAttributes());
+ return ToAtkAttributeSet(obj->GetDefaultTextAttributes());
}
void Init(AtkTextIface* iface) {
@@ -1832,7 +1823,23 @@ gint GetIndexInParent(AtkObject* atk_object) {
if (!parent)
return -1;
+ AXPlatformNodeAuraLinux* obj = AtkObjectToAXPlatformNodeAuraLinux(atk_object);
+ if (!obj)
+ return -1;
+
int n_children = atk_object_get_n_accessible_children(parent);
+
+ // Ask the delegate for the index in parent, and return it if it's plausible.
+ //
+ // Delegates are allowed to not implement this (AXPlatformNodeDelegateBase
+ // returns -1). Also, delegates may not know the correct answer if this
+ // node is the root of a tree that's embedded in another tree, in which
+ // case the delegate should return -1 and we'll compute it.
+ int index_in_parent = obj->GetDelegate()->GetIndexInParent();
+ if (index_in_parent >= 0 && index_in_parent < n_children)
+ return index_in_parent;
+
+ // Otherwise, search the parent's children.
for (int i = 0; i < n_children; i++) {
AtkObject* child = atk_object_ref_accessible_child(parent, i);
g_object_unref(child);
@@ -2288,7 +2295,7 @@ void AXPlatformNodeAuraLinux::StaticInitialize() {
AtkUtilAuraLinux::GetInstance()->InitializeAsync();
}
-AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
+AtkRole AXPlatformNodeAuraLinux::GetAtkRole() const {
switch (GetData().role) {
case ax::mojom::Role::kAlert:
return ATK_ROLE_ALERT;
@@ -2296,10 +2303,12 @@ AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
return ATK_ROLE_DIALOG;
case ax::mojom::Role::kAnchor:
return ATK_ROLE_LINK;
- case ax::mojom::Role::kAnnotation:
- // TODO(accessibility) Panels are generally for containers of widgets.
- // This should probably be a section (if a container) or static if text.
- return ATK_ROLE_PANEL;
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
+ return ATK_ROLE_SECTION;
case ax::mojom::Role::kApplication:
// Only use ATK_ROLE_APPLICATION for elements with no parent, since it
// is only for top level app windows and not ARIA applications.
@@ -2313,6 +2322,7 @@ AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
case ax::mojom::Role::kAudio:
return ATK_ROLE_AUDIO;
case ax::mojom::Role::kBanner:
+ case ax::mojom::Role::kHeader:
return ATK_ROLE_LANDMARK;
case ax::mojom::Role::kBlockquote:
return ATK_ROLE_BLOCK_QUOTE;
@@ -2343,9 +2353,9 @@ AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
case ax::mojom::Role::kComplementary:
return ATK_ROLE_LANDMARK;
case ax::mojom::Role::kContentDeletion:
+ return kAtkRoleContentDeletion;
case ax::mojom::Role::kContentInsertion:
- // TODO(accessibility) https://github.com/w3c/html-aam/issues/141
- return ATK_ROLE_SECTION;
+ return kAtkRoleContentInsertion;
case ax::mojom::Role::kContentInfo:
case ax::mojom::Role::kFooter:
return ATK_ROLE_LANDMARK;
@@ -2429,6 +2439,8 @@ AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
case ax::mojom::Role::kFeed:
return ATK_ROLE_PANEL;
case ax::mojom::Role::kGenericContainer:
+ case ax::mojom::Role::kFooterAsNonLandmark:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
return ATK_ROLE_SECTION;
case ax::mojom::Role::kGraphicsDocument:
return ATK_ROLE_DOCUMENT_FRAME;
@@ -2544,11 +2556,22 @@ AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
return ATK_ROLE_RADIO_BUTTON;
case ax::mojom::Role::kRadioGroup:
return ATK_ROLE_PANEL;
- case ax::mojom::Role::kRegion: {
- std::string html_tag =
- GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
- if (html_tag == "section" &&
- GetData()
+ case ax::mojom::Role::kRegion:
+ return ATK_ROLE_LANDMARK;
+ case ax::mojom::Role::kRootWebArea:
+ return ATK_ROLE_DOCUMENT_WEB;
+ case ax::mojom::Role::kRow:
+ return ATK_ROLE_TABLE_ROW;
+ case ax::mojom::Role::kRowHeader:
+ return ATK_ROLE_ROW_HEADER;
+ case ax::mojom::Role::kRuby:
+ return kStaticRole;
+ case ax::mojom::Role::kRubyAnnotation:
+ // TODO(accessibility) Panels are generally for containers of widgets.
+ // This should probably be a section (if a container) or static if text.
+ return ATK_ROLE_PANEL;
+ case ax::mojom::Role::kSection: {
+ if (GetData()
.GetString16Attribute(ax::mojom::StringAttribute::kName)
.empty()) {
// Do not use ARIA mapping for nameless <section>.
@@ -2558,14 +2581,6 @@ AtkRole AXPlatformNodeAuraLinux::GetAtkRole() {
return ATK_ROLE_LANDMARK;
}
}
- case ax::mojom::Role::kRootWebArea:
- return ATK_ROLE_DOCUMENT_WEB;
- case ax::mojom::Role::kRow:
- return ATK_ROLE_TABLE_ROW;
- case ax::mojom::Role::kRowHeader:
- return ATK_ROLE_ROW_HEADER;
- case ax::mojom::Role::kRuby:
- return kStaticRole;
case ax::mojom::Role::kScrollBar:
return ATK_ROLE_SCROLL_BAR;
case ax::mojom::Role::kSearch:
@@ -3218,35 +3233,139 @@ bool AXPlatformNodeAuraLinux::SelectionAndFocusAreTheSame() {
return false;
}
-void AXPlatformNodeAuraLinux::OnTextSelectionChanged() {
- AtkObject* atk_object = GetOrCreateAtkObject();
- if (!EmitsAtkTextEvents(atk_object)) {
+bool AXPlatformNodeAuraLinux::EmitsAtkTextEvents() const {
+ // If this node is not a static text node, it supports the full AtkText
+ // interface.
+ if (GetAtkRole() != ATK_ROLE_TEXT)
+ return true;
+
+ // If this node has children it is not a static text leaf node and supports
+ // the full AtkText interface.
+ if (GetChildCount())
+ return true;
+
+ return false;
+}
+
+void AXPlatformNodeAuraLinux::GetFullSelection(int32_t* anchor_node_id,
+ int* anchor_offset,
+ int32_t* focus_node_id,
+ int* focus_offset) {
+ DCHECK(anchor_node_id);
+ DCHECK(anchor_offset);
+ DCHECK(focus_node_id);
+ DCHECK(focus_offset);
+
+ if (IsPlainTextField() &&
+ GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, anchor_offset) &&
+ GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, focus_offset)) {
+ int32_t node_id = GetData().id != -1 ? GetData().id : GetUniqueId();
+ *anchor_node_id = *focus_node_id = node_id;
+ return;
+ }
+
+ ui::AXTree::Selection selection = GetDelegate()->GetUnignoredSelection();
+ *anchor_node_id = selection.anchor_object_id;
+ *anchor_offset = selection.anchor_offset;
+ *focus_node_id = selection.focus_object_id;
+ *focus_offset = selection.focus_offset;
+}
+
+AXPlatformNodeAuraLinux& AXPlatformNodeAuraLinux::FindEditableRootOrDocument() {
+ if (GetAtkRole() == ATK_ROLE_DOCUMENT_WEB)
+ return *this;
+ if (GetData().GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot))
+ return *this;
+ if (auto* parent = AtkObjectToAXPlatformNodeAuraLinux(GetParent()))
+ return parent->FindEditableRootOrDocument();
+ return *this;
+}
+
+AXPlatformNodeAuraLinux* AXPlatformNodeAuraLinux::FindCommonAncestor(
+ AXPlatformNodeAuraLinux* other) {
+ if (this == other || other->IsDescendantOf(this))
+ return this;
+ if (auto* parent = AtkObjectToAXPlatformNodeAuraLinux(GetParent()))
+ return parent->FindCommonAncestor(other);
+ return nullptr;
+}
+
+void AXPlatformNodeAuraLinux::UpdateSelectionInformation(int32_t anchor_node_id,
+ int anchor_offset,
+ int32_t focus_node_id,
+ int focus_offset) {
+ had_nonzero_width_selection =
+ focus_node_id != anchor_node_id || focus_offset != anchor_offset;
+ current_caret_ = std::make_pair(focus_node_id, focus_offset);
+}
+
+void AXPlatformNodeAuraLinux::EmitSelectionChangedSignal(bool had_selection) {
+ if (!EmitsAtkTextEvents()) {
if (auto* parent = AtkObjectToAXPlatformNodeAuraLinux(GetParent()))
- parent->OnTextSelectionChanged();
+ parent->EmitSelectionChangedSignal(had_selection);
return;
}
+ AtkObject* atk_object = GetOrCreateAtkObject();
DCHECK(ATK_IS_TEXT(atk_object));
-
- std::pair<int, int> new_selection;
- GetSelectionOffsets(&new_selection.first, &new_selection.second);
- std::pair<int, int> old_selection = text_selection_;
- text_selection_ = new_selection;
+ std::pair<int, int> selection;
+ GetSelectionOffsets(&selection.first, &selection.second);
// ATK does not consider a collapsed selection a selection, so
// when the collapsed selection changes (caret movement), we should
// avoid sending text-selection-changed events.
- bool has_selection = SelectionOffsetsIndicateSelection(new_selection);
- bool had_selection = SelectionOffsetsIndicateSelection(old_selection);
- if (has_selection != had_selection ||
- (has_selection && new_selection != old_selection)) {
+ if (HasSelection() || had_selection)
g_signal_emit_by_name(atk_object, "text-selection-changed");
+}
+
+void AXPlatformNodeAuraLinux::EmitCaretChangedSignal() {
+ if (!EmitsAtkTextEvents()) {
+ if (auto* parent = AtkObjectToAXPlatformNodeAuraLinux(GetParent()))
+ parent->EmitCaretChangedSignal();
+ return;
}
- if (HasCaret() && new_selection.second != old_selection.second) {
- g_signal_emit_by_name(atk_object, "text-caret-moved",
- UTF16ToUnicodeOffsetInText(new_selection.second));
+ DCHECK(HasCaret());
+ std::pair<int, int> selection;
+ GetSelectionOffsets(&selection.first, &selection.second);
+
+ AtkObject* atk_object = GetOrCreateAtkObject();
+ DCHECK(ATK_IS_TEXT(atk_object));
+ g_signal_emit_by_name(atk_object, "text-caret-moved",
+ UTF16ToUnicodeOffsetInText(selection.second));
+}
+
+void AXPlatformNodeAuraLinux::OnTextSelectionChanged() {
+ int32_t anchor_node_id, focus_node_id;
+ int anchor_offset, focus_offset;
+ GetFullSelection(&anchor_node_id, &anchor_offset, &focus_node_id,
+ &focus_offset);
+
+ auto* anchor_node = static_cast<AXPlatformNodeAuraLinux*>(
+ GetDelegate()->GetFromNodeID(anchor_node_id));
+ auto* focus_node = static_cast<AXPlatformNodeAuraLinux*>(
+ GetDelegate()->GetFromNodeID(focus_node_id));
+ if (!anchor_node || !focus_node)
+ return;
+
+ AXPlatformNodeAuraLinux& editable_root = FindEditableRootOrDocument();
+ AXPlatformNodeAuraLinux* common_ancestor =
+ focus_node->FindCommonAncestor(anchor_node);
+ if (common_ancestor) {
+ common_ancestor->EmitSelectionChangedSignal(
+ editable_root.HadNonZeroWidthSelection());
}
+
+ // It's possible for the selection to change and for the caret to stay in
+ // place. This might happen if the selection is totally reset with a
+ // different anchor node, but the same focus node. We should avoid sending a
+ // caret changed signal in that case.
+ std::pair<int32_t, int> prev_caret = editable_root.GetCurrentCaret();
+ if (prev_caret.first != focus_node_id || prev_caret.second != focus_offset)
+ focus_node->EmitCaretChangedSignal();
+
+ editable_root.UpdateSelectionInformation(anchor_node_id, anchor_offset,
+ focus_node_id, focus_offset);
}
bool AXPlatformNodeAuraLinux::SupportsSelectionWithAtkSelection() {
@@ -3456,6 +3575,14 @@ AXPlatformNodeAuraLinux::GetEmbeddedObjectIndicesForId(int id) {
UTF16ToUnicodeOffsetInText(offset->first + 1));
}
+base::Optional<std::pair<int, int>>
+AXPlatformNodeAuraLinux::GetEmbeddedObjectIndices() {
+ auto* parent = AtkObjectToAXPlatformNodeAuraLinux(GetParent());
+ if (!parent)
+ return base::nullopt;
+ return parent->GetEmbeddedObjectIndicesForId(GetUniqueId());
+}
+
void AXPlatformNodeAuraLinux::UpdateHypertext() {
EnsureAtkObjectIsValid();
AXHypertext old_hypertext = hypertext_;
@@ -3474,10 +3601,12 @@ void AXPlatformNodeAuraLinux::UpdateHypertext() {
ComputeHypertextRemovedAndInserted(old_hypertext, &shared_prefix, &old_len,
&new_len);
+ offset_to_text_attributes_.clear();
+
AtkObject* atk_object = GetOrCreateAtkObject();
DCHECK(ATK_IS_TEXT(atk_object));
- if (!EmitsAtkTextEvents(atk_object))
+ if (!EmitsAtkTextEvents())
return;
if (old_len > 0) {
@@ -3827,20 +3956,6 @@ AtkHyperlink* AXPlatformNodeAuraLinux::GetAtkHyperlink() {
ATK_HYPERLINK(g_object_new(AX_PLATFORM_ATK_HYPERLINK_TYPE, 0));
ax_platform_atk_hyperlink_set_object(
AX_PLATFORM_ATK_HYPERLINK(atk_hyperlink_), this);
-
- auto* parent = AtkObjectToAXPlatformNodeAuraLinux(GetParent());
- if (!parent)
- return atk_hyperlink_;
-
- base::Optional<std::pair<int, int>> indices =
- parent->GetEmbeddedObjectIndicesForId(GetUniqueId());
- if (!indices.has_value())
- return atk_hyperlink_;
-
- ax_platform_atk_hyperlink_set_indices(
- AX_PLATFORM_ATK_HYPERLINK(atk_hyperlink_), indices->first,
- indices->second);
-
return atk_hyperlink_;
}
@@ -3908,7 +4023,6 @@ bool AXPlatformNodeAuraLinux::SetCaretOffset(int offset) {
if (!SetHypertextSelection(offset, offset))
return false;
- OnTextSelectionChanged();
return true;
}
@@ -3938,14 +4052,14 @@ bool AXPlatformNodeAuraLinux::SetTextSelectionForAtkText(int start_offset,
if (!SetHypertextSelection(start_offset, end_offset))
return false;
- OnTextSelectionChanged();
return true;
}
bool AXPlatformNodeAuraLinux::HasSelection() {
std::pair<int, int> selection;
GetSelectionOffsets(&selection.first, &selection.second);
- return SelectionOffsetsIndicateSelection(selection);
+ return selection.first >= 0 && selection.second >= 0 &&
+ selection.first != selection.second;
}
void AXPlatformNodeAuraLinux::GetSelectionExtents(int* start_offset,
@@ -4101,242 +4215,16 @@ void AXPlatformNodeAuraLinux::ScrollNodeIntoView(
#endif // defined(ATK_230)
-AtkAttributes AXPlatformNodeAuraLinux::ComputeTextAttributes() const {
- AtkAttributeSet* attributes = nullptr;
-
- int color;
- if (GetIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, &color)) {
- unsigned int red = SkColorGetR(color);
- unsigned int green = SkColorGetG(color);
- unsigned int blue = SkColorGetB(color);
- std::string color_value = base::NumberToString(red) + ',' +
- base::NumberToString(green) + ',' +
- base::NumberToString(blue);
- AddTextAttributeToSet(ATK_TEXT_ATTR_BG_COLOR, color_value, &attributes);
- }
-
- if (GetIntAttribute(ax::mojom::IntAttribute::kColor, &color)) {
- unsigned int red = SkColorGetR(color);
- unsigned int green = SkColorGetG(color);
- unsigned int blue = SkColorGetB(color);
- std::string color_value = base::NumberToString(red) + ',' +
- base::NumberToString(green) + ',' +
- base::NumberToString(blue);
- AddTextAttributeToSet(ATK_TEXT_ATTR_FG_COLOR, color_value, &attributes);
- }
-
- std::string font_family(
- GetInheritedStringAttribute(ax::mojom::StringAttribute::kFontFamily));
- // Attribute has no default value.
- if (!font_family.empty()) {
- AddTextAttributeToSet(ATK_TEXT_ATTR_FAMILY_NAME, font_family, &attributes);
- }
-
- float font_size;
- // Attribute has no default value.
- if (GetFloatAttribute(ax::mojom::FloatAttribute::kFontSize, &font_size)) {
- // The ATK Spec requires the value to be in pt, not in pixels.
- // There are 72 points per inch.
- // We assume that there are 96 pixels per inch on a standard display.
- // TODO(nektar): Figure out the current value of pixels per inch.
- float points = font_size * 72.0 / 96.0;
- AddTextAttributeToSet(ATK_TEXT_ATTR_SIZE,
- base::NumberToString(points) + "pt", &attributes);
- }
-
- // TODO(nektar): Add Blink support for the following attributes:
- // text-line-through-mode, text-line-through-width, text-outline:false,
- // text-position:baseline, text-shadow:none, text-underline-mode:continuous.
-
- int32_t text_style = GetIntAttribute(ax::mojom::IntAttribute::kTextStyle);
- if (text_style) {
- if (GetData().HasTextStyle(ax::mojom::TextStyle::kBold)) {
- // TODO(mrobinson): This is based on the weight that CSS uses for "bold,"
- // but we'd like to support more font weights here.
- AddTextAttributeToSet(ATK_TEXT_ATTR_WEIGHT, "700", &attributes);
- }
-
- if (GetData().HasTextStyle(ax::mojom::TextStyle::kItalic)) {
- // TODO(mrobinson): We'd also like to support "oblique" here.
- AddTextAttributeToSet(ATK_TEXT_ATTR_STYLE, "italic", &attributes);
- }
-
- if (GetData().HasTextStyle(ax::mojom::TextStyle::kLineThrough))
- AddTextAttributeToSet(ATK_TEXT_ATTR_STRIKETHROUGH, "true", &attributes);
-
- if (GetData().HasTextStyle(ax::mojom::TextStyle::kUnderline)) {
- // TODO(mrobinson): Figure out a more specific value.
- AddTextAttributeToSet(ATK_TEXT_ATTR_UNDERLINE, "single", &attributes);
- }
- }
-
- // Screen readers look at the text attributes to determine if something is
- // misspelled, so we need to propagate any spelling attributes from immediate
- // parents of text-only objects. AXPlatformNodeBase::GetInvalidValue takes
- // care of searching upward to find the appropriate values.
- std::string invalid_value = GetInvalidValue();
- if (!invalid_value.empty())
- AddTextAttributeToSet(ATK_TEXT_ATTR_UNDERLINE, "error", &attributes);
-
- std::string language = GetDelegate()->GetLanguage();
- if (!language.empty())
- AddTextAttributeToSet(ATK_TEXT_ATTR_LANGUAGE, language, &attributes);
-
- auto text_direction = static_cast<ax::mojom::TextDirection>(
- GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
- switch (text_direction) {
- case ax::mojom::TextDirection::kNone:
- break;
- case ax::mojom::TextDirection::kLtr:
- AddTextAttributeToSet(ATK_TEXT_ATTR_LANGUAGE, "ltr", &attributes);
- break;
- case ax::mojom::TextDirection::kRtl:
- AddTextAttributeToSet(ATK_TEXT_ATTR_LANGUAGE, "rtl", &attributes);
- break;
- case ax::mojom::TextDirection::kTtb:
- // Not listed in the ATK docs.
- AddTextAttributeToSet(ATK_TEXT_ATTR_LANGUAGE, "ttb", &attributes);
- break;
- case ax::mojom::TextDirection::kBtt:
- // Not listed in the ATK docs.
- AddTextAttributeToSet(ATK_TEXT_ATTR_LANGUAGE, "btt", &attributes);
- break;
- }
-
- return AtkAttributes(attributes);
-}
-
void AXPlatformNodeAuraLinux::ComputeStylesIfNeeded() {
if (!offset_to_text_attributes_.empty())
return;
default_text_attributes_ = ComputeTextAttributes();
- std::map<int, AtkAttributes> attributes_map;
- if (IsLeaf()) {
- attributes_map[0] =
- AtkAttributes(CopyAttributeSet(default_text_attributes_.get()));
- MergeSpellingIntoAtkTextAttributesAtOffset(0 /* start_offset */,
- &attributes_map);
- offset_to_text_attributes_.swap(attributes_map);
- return;
- }
-
- int start_offset = 0;
- for (auto child_iterator_ptr = GetDelegate()->ChildrenBegin();
- *child_iterator_ptr != *GetDelegate()->ChildrenEnd();
- ++(*child_iterator_ptr)) {
- auto* child = AtkObjectToAXPlatformNodeAuraLinux(
- child_iterator_ptr->GetNativeViewAccessible());
- if (!child)
- continue;
-
- AtkAttributes attributes = child->ComputeTextAttributes();
- if (attributes_map.empty()) {
- attributes_map[start_offset] = std::move(attributes);
- } else {
- // Only add the attributes for this child if we are at the start of a new
- // style span.
- AtkAttributeSet* previous_attributes =
- attributes_map.rbegin()->second.get();
- if (!AttributeSetsEqual(attributes.get(), previous_attributes))
- attributes_map[start_offset] = std::move(attributes);
- }
-
- if (child->IsTextOnlyObject()) {
- MergeSpellingIntoAtkTextAttributesAtOffset(start_offset, &attributes_map);
- start_offset += child->GetHypertext().length();
- } else {
- start_offset += 1;
- }
- }
-
+ TextAttributeMap attributes_map =
+ GetDelegate()->ComputeTextAttributeMap(default_text_attributes_);
offset_to_text_attributes_.swap(attributes_map);
}
-void AXPlatformNodeAuraLinux::MergeSpellingIntoAtkTextAttributesAtOffset(
- int offset,
- std::map<int, AtkAttributes>* text_attributes) {
- DCHECK(text_attributes);
-
- int hypertext_length = static_cast<int>(GetHypertext().length());
- std::map<int, AtkAttributeSet*> spelling_attributes;
- if (IsTextOnlyObject()) {
- const std::vector<int32_t>& marker_types =
- GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerTypes);
- const std::vector<int>& marker_starts =
- GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerStarts);
- const std::vector<int>& marker_ends =
- GetIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds);
- for (size_t i = 0; i < marker_types.size(); ++i) {
- bool isSpellingError =
- (marker_types[i] &
- static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)) != 0;
- bool isGrammarError =
- (marker_types[i] &
- static_cast<int32_t>(ax::mojom::MarkerType::kGrammar)) != 0;
- if (!isSpellingError && !isGrammarError)
- continue;
-
- // Add a starting attribute for the error, if one does not already exist.
- // We use a default value of an AtkAttributes (unique_ptr of
- // AtkAttributeSet*) set to a nullptr. Since AtkAttributeSet is a GSList,
- // a nullptr is an empty set.
- int marker_start_offset = offset + marker_starts[i];
- auto default_value = std::make_pair(marker_start_offset, nullptr);
- auto iterator = text_attributes->insert(default_value).first;
-
- if (!HasInvalidAttributeInSet(iterator->second.get())) {
- // `attributes` is a unique_ptr, but here we want to modify the head of
- // the list without having the unique_ptr delete it. We leak the
- // pointer to `attribute_set` and then reset the new list value into
- // the iterator.
- AtkAttributeSet* attribute_set = iterator->second.release();
- AddTextAttributeToSet(ATK_TEXT_ATTR_UNDERLINE, "error", &attribute_set);
- if (isSpellingError) {
- AddTextAttributeToSet(ATK_TEXT_ATTR_INVALID, "spelling",
- &attribute_set);
- }
- if (isGrammarError) {
- AddTextAttributeToSet(ATK_TEXT_ATTR_INVALID, "grammar",
- &attribute_set);
- }
- iterator->second.reset(attribute_set);
- }
-
- // Make sure there is at least an empty attribute set to end the marker.
- int marker_end_offset = offset + marker_ends[i];
- DCHECK_LE(marker_end_offset, hypertext_length);
- text_attributes->insert(std::make_pair(marker_end_offset, nullptr));
- }
- }
-
- if (IsPlainTextField()) {
- AXPlatformNodeDelegate* delegate = GetDelegate();
- DCHECK(delegate);
-
- int anchor_start_offset = offset;
- AXNodeRange range(delegate->CreateTextPositionAt(0),
- delegate->CreateTextPositionAt(hypertext_length));
- for (const auto& leaf_text_range : range) {
- DCHECK(!leaf_text_range.IsNull());
- DCHECK_EQ(leaf_text_range.anchor()->GetAnchor(),
- leaf_text_range.focus()->GetAnchor())
- << "An leaf text range should only span a single object.";
- auto* node = static_cast<AXPlatformNodeAuraLinux*>(
- delegate->GetFromNodeID(leaf_text_range.anchor()->GetAnchor()->id()));
-
- // The AXNodeRange may include this node as well. We need to skip
- // that one to avoid an infinite recursive loop.
- if (node == this)
- continue;
-
- node->MergeSpellingIntoAtkTextAttributesAtOffset(anchor_start_offset,
- text_attributes);
- anchor_start_offset += leaf_text_range.GetText().length();
- }
- }
-}
-
int AXPlatformNodeAuraLinux::FindStartOfStyle(
int start_offset,
AXTextBoundaryDirection direction) {
@@ -4364,9 +4252,10 @@ int AXPlatformNodeAuraLinux::FindStartOfStyle(
return start_offset;
}
-AtkAttributeSet* AXPlatformNodeAuraLinux::GetTextAttributes(int offset,
- int* start_offset,
- int* end_offset) {
+const TextAttributeList& AXPlatformNodeAuraLinux::GetTextAttributes(
+ int offset,
+ int* start_offset,
+ int* end_offset) {
ComputeStylesIfNeeded();
DCHECK(!offset_to_text_attributes_.empty());
@@ -4383,12 +4272,12 @@ AtkAttributeSet* AXPlatformNodeAuraLinux::GetTextAttributes(int offset,
UTF16ToUnicodeOffsetInText(style_start));
SetIntPointerValueIfNotNull(end_offset,
UTF16ToUnicodeOffsetInText(style_end));
- return iterator->second.get();
+ return iterator->second;
}
-AtkAttributeSet* AXPlatformNodeAuraLinux::GetDefaultTextAttributes() {
+const TextAttributeList& AXPlatformNodeAuraLinux::GetDefaultTextAttributes() {
ComputeStylesIfNeeded();
- return default_text_attributes_.get();
+ return default_text_attributes_;
}
void AXPlatformNodeAuraLinux::TerminateFindInPage() {
@@ -4400,7 +4289,7 @@ void AXPlatformNodeAuraLinux::ActivateFindInPageResult(int start_offset,
AtkObject* atk_object = GetOrCreateAtkObject();
DCHECK(ATK_IS_TEXT(atk_object));
- if (!EmitsAtkTextEvents(atk_object)) {
+ if (!EmitsAtkTextEvents()) {
ActivateFindInPageInParent(start_offset, end_offset);
return;
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
index 2d64e2abfcc..71a3c910d5f 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -11,6 +11,7 @@
#include <memory>
#include <string>
#include <utility>
+#include <vector>
#include "base/macros.h"
#include "base/memory/protected_memory.h"
@@ -105,7 +106,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
void EnsureAtkObjectIsValid();
void Destroy() override;
- AtkRole GetAtkRole();
+ AtkRole GetAtkRole() const;
void GetAtkState(AtkStateSet* state_set);
AtkRelationSet* GetAtkRelations();
void GetExtents(gint* x, gint* y, gint* width, gint* height,
@@ -196,6 +197,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
bool SetCaretOffset(int offset);
bool SetTextSelectionForAtkText(int start_offset, int end_offset);
bool HasSelection();
+
void GetSelectionExtents(int* start_offset, int* end_offset);
gchar* GetSelectionWithText(int* start_offset, int* end_offset);
@@ -203,16 +205,16 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// and end attributes will be assigned to the start_offset and end_offset
// pointers if they are non-null. The return value AtkAttributeSet should
// be freed with atk_attribute_set_free.
- AtkAttributeSet* GetTextAttributes(int offset,
- int* start_offset,
- int* end_offset);
+ const TextAttributeList& GetTextAttributes(int offset,
+ int* start_offset,
+ int* end_offset);
// Return the default text attributes for this node. The default text
// attributes are the ones that apply to the entire node. Attributes found at
// a given offset can be thought of as overriding the default attribute.
// The return value AtkAttributeSet should be freed with
// atk_attribute_set_free.
- AtkAttributeSet* GetDefaultTextAttributes();
+ const TextAttributeList& GetDefaultTextAttributes();
void ActivateFindInPageResult(int start_offset, int end_offset);
void TerminateFindInPage();
@@ -221,6 +223,11 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// return it, otherwise return base::nullopt;
base::Optional<FindInPageResultInfo> GetSelectionOffsetsFromFindInPage();
+ // Get the embedded object ("hyperlink") indices for this object in the
+ // parent. If this object doesn't have a parent or isn't embedded, return
+ // nullopt.
+ base::Optional<std::pair<int, int>> GetEmbeddedObjectIndices();
+
std::string accessible_name_;
protected:
@@ -268,11 +275,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
base::Optional<std::pair<int, int>> GetEmbeddedObjectIndicesForId(int id);
void ComputeStylesIfNeeded();
- void MergeSpellingIntoAtkTextAttributesAtOffset(
- int offset,
- std::map<int, AtkAttributes>* text_attributes);
- AtkAttributes ComputeTextAttributes() const;
- int FindStartOfStyle(int start_offset, AXTextBoundaryDirection direction);
+ int FindStartOfStyle(int start_offset, ui::AXTextBoundaryDirection direction);
// Reset any find in page operations for the toplevel document of this node.
void ForgetCurrentFindInPageResult();
@@ -287,6 +290,41 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// Find the first child which is a document containing web content.
AtkObject* FindFirstWebContentDocument();
+ // If a selection that intersects this node get the full selection
+ // including start and end node ids.
+ void GetFullSelection(int32_t* anchor_node_id,
+ int* anchor_offset,
+ int32_t* focus_node_id,
+ int* focus_offset);
+
+ // Returns true if this node's AtkObject is suitable for emitting AtkText
+ // signals. ATs don't expect static text objects to emit AtkText signals.
+ bool EmitsAtkTextEvents() const;
+
+ // Find the first ancestor which is an editable root or a document. This node
+ // will be one which contains a single selection.
+ AXPlatformNodeAuraLinux& FindEditableRootOrDocument();
+
+ // Find the first common ancestor between this node and a given node.
+ AXPlatformNodeAuraLinux* FindCommonAncestor(AXPlatformNodeAuraLinux* other);
+
+ // Update the selection information stored in this node. This should be
+ // called on the editable root, the root node of the accessibility tree, or
+ // the document (ie the node returned by FindEditableRootOrDocument()).
+ void UpdateSelectionInformation(int32_t anchor_node_id,
+ int anchor_offset,
+ int32_t focus_node_id,
+ int focus_offset);
+
+ // Emit a GObject signal indicating a selection change.
+ void EmitSelectionChangedSignal(bool had_selection);
+
+ // Emit a GObject signal indicating that the caret has moved.
+ void EmitCaretChangedSignal();
+
+ bool HadNonZeroWidthSelection() const { return had_nonzero_width_selection; }
+ std::pair<int32_t, int> GetCurrentCaret() const { return current_caret_; }
+
// If the given argument can be found as a child of this node, return its
// hypertext extents, otherwise return base::nullopt;
base::Optional<std::pair<int, int>> GetHypertextExtentsOfChild(
@@ -310,17 +348,24 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// minimized the last time it's visibility changed.
bool was_minimized_ = false;
- // The previously observed text selection for this node. We store
- // this in order to avoid sending duplicate text-selection-changed
- // and text-caret-moved events.
- std::pair<int, int> text_selection_ = std::make_pair(-1, -1);
+ // Information about the selection meant to be stored on the return value of
+ // FindEditableRootOrDocument().
+ //
+ // Whether or not we previously had a selection where the anchor and focus
+ // were not equal. This is what ATK consider a "selection."
+ bool had_nonzero_width_selection = false;
+
+ // Information about the current caret location (a node id and an offset).
+ // This is used to track when the caret actually moves during a selection
+ // change.
+ std::pair<int32_t, int> current_caret_ = {-1, -1};
// A map which converts between an offset in the node's hypertext and the
// ATK text attributes at that offset.
- std::map<int, AtkAttributes> offset_to_text_attributes_;
+ TextAttributeMap offset_to_text_attributes_;
// The default ATK text attributes for this node.
- AtkAttributes default_text_attributes_;
+ TextAttributeList default_text_attributes_;
DISALLOW_COPY_AND_ASSIGN(AXPlatformNodeAuraLinux);
};
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 44973634166..75908edd3d2 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -1399,34 +1399,52 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextCaretMoved) {
&caret_position_from_event);
atk_text_set_caret_offset(atk_text, 4);
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
ASSERT_EQ(atk_text_get_caret_offset(atk_text), 4);
ASSERT_EQ(caret_position_from_event, 4);
// Setting the same position should not trigger another event.
caret_position_from_event = -1;
atk_text_set_caret_offset(atk_text, 4);
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
ASSERT_EQ(atk_text_get_caret_offset(atk_text), 4);
ASSERT_EQ(caret_position_from_event, -1);
int character_count = atk_text_get_character_count(atk_text);
atk_text_set_caret_offset(atk_text, -1);
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count);
ASSERT_EQ(caret_position_from_event, character_count);
+
atk_text_set_caret_offset(atk_text, 0); // Reset position.
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
caret_position_from_event = -1;
atk_text_set_caret_offset(atk_text, -1000);
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count);
ASSERT_EQ(caret_position_from_event, character_count);
+
atk_text_set_caret_offset(atk_text, 0); // Reset position.
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
caret_position_from_event = -1;
atk_text_set_caret_offset(atk_text, 1000);
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count);
ASSERT_EQ(caret_position_from_event, character_count);
caret_position_from_event = -1;
atk_text_set_caret_offset(atk_text, character_count - 1);
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
ASSERT_EQ(atk_text_get_caret_offset(atk_text), character_count - 1);
ASSERT_EQ(caret_position_from_event, character_count - 1);
@@ -2096,6 +2114,8 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldSetSelection) {
int selection_start, selection_end;
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 1));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
EXPECT_TRUE(saw_selection_change);
g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end));
EXPECT_EQ(selection_start, 0);
@@ -2103,25 +2123,22 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldSetSelection) {
// Reset position.
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 0));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
saw_selection_change = false;
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 1, 0));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
EXPECT_TRUE(saw_selection_change);
g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end));
EXPECT_EQ(selection_start, 0);
EXPECT_EQ(selection_end, 1);
- // Setting the selection to the same location should not trigger
- // another event.
- saw_selection_change = false;
- EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 1, 0));
- EXPECT_FALSE(saw_selection_change);
- g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end));
- EXPECT_EQ(selection_start, 0);
- EXPECT_EQ(selection_end, 1);
-
saw_selection_change = false;
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 2, 4));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
EXPECT_TRUE(saw_selection_change);
g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end));
EXPECT_EQ(selection_start, 2);
@@ -2136,11 +2153,12 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldSetSelection) {
saw_selection_change = false;
EXPECT_FALSE(atk_text_set_selection(atk_text, 0, 0, 50));
- EXPECT_FALSE(saw_selection_change);
saw_selection_change = false;
int n_characters = atk_text_get_character_count(atk_text);
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, -1));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
EXPECT_TRUE(saw_selection_change);
g_free(atk_text_get_selection(atk_text, 0, &selection_start, &selection_end));
EXPECT_EQ(selection_start, 0);
@@ -2148,6 +2166,8 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldSetSelection) {
saw_selection_change = false;
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 1));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
EXPECT_EQ(1, atk_text_get_n_selections(atk_text));
EXPECT_TRUE(atk_text_remove_selection(atk_text, 0));
EXPECT_TRUE(saw_selection_change);
@@ -2155,9 +2175,13 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkTextTextFieldSetSelection) {
// Reset position.
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 0));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
saw_selection_change = false;
EXPECT_TRUE(atk_text_set_selection(atk_text, 0, 0, 1));
+ GetRootPlatformNode()->NotifyAccessibilityEvent(
+ ax::mojom::Event::kTextSelectionChanged);
EXPECT_EQ(1, atk_text_get_n_selections(atk_text));
EXPECT_FALSE(atk_text_remove_selection(atk_text, 1));
EXPECT_TRUE(saw_selection_change);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.cc b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
index bb3af2c0066..5f672836406 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
@@ -13,6 +13,7 @@
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
@@ -458,14 +459,24 @@ bool AXPlatformNodeBase::IsSelectionItemSupported() const {
return table->GetData().role == ax::mojom::Role::kGrid ||
table->GetData().role == ax::mojom::Role::kTreeGrid;
}
+ // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
+ // SelectionItem.IsSelected is exposed when aria-checked is True or False,
+ // for 'radio' and 'menuitemradio' roles.
+ case ax::mojom::Role::kRadioButton:
+ case ax::mojom::Role::kMenuItemRadio: {
+ if (GetData().GetCheckedState() == ax::mojom::CheckedState::kTrue ||
+ GetData().GetCheckedState() == ax::mojom::CheckedState::kFalse)
+ return true;
+ return false;
+ }
+ // https://www.w3.org/TR/wai-aria-1.1/#aria-selected
+ // SelectionItem.IsSelected is exposed when aria-select is True or False.
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kListItem:
- case ax::mojom::Role::kMenuItemRadio:
case ax::mojom::Role::kMenuListOption:
- case ax::mojom::Role::kRadioButton:
case ax::mojom::Role::kTab:
case ax::mojom::Role::kTreeItem:
- return true;
+ return HasBoolAttribute(ax::mojom::BoolAttribute::kSelected);
default:
return false;
}
@@ -1711,4 +1722,148 @@ std::string AXPlatformNodeBase::GetInvalidValue() const {
return invalid_value;
}
+ui::TextAttributeList AXPlatformNodeBase::ComputeTextAttributes() const {
+ ui::TextAttributeList attributes;
+
+ // We include list markers for now, but there might be other objects that are
+ // auto generated.
+ // TODO(nektar): Compute what objects are auto-generated in Blink.
+ if (GetData().role == ax::mojom::Role::kListMarker)
+ attributes.push_back(std::make_pair("auto-generated", "true"));
+
+ int color;
+ if (GetIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, &color)) {
+ unsigned int alpha = SkColorGetA(color);
+ unsigned int red = SkColorGetR(color);
+ unsigned int green = SkColorGetG(color);
+ unsigned int blue = SkColorGetB(color);
+ // Don't expose default value of pure white.
+ if (alpha && (red != 255 || green != 255 || blue != 255)) {
+ std::string color_value = "rgb(" + base::NumberToString(red) + ',' +
+ base::NumberToString(green) + ',' +
+ base::NumberToString(blue) + ')';
+ SanitizeTextAttributeValue(color_value, &color_value);
+ attributes.push_back(std::make_pair("background-color", color_value));
+ }
+ }
+
+ if (GetIntAttribute(ax::mojom::IntAttribute::kColor, &color)) {
+ unsigned int red = SkColorGetR(color);
+ unsigned int green = SkColorGetG(color);
+ unsigned int blue = SkColorGetB(color);
+ // Don't expose default value of black.
+ if (red || green || blue) {
+ std::string color_value = "rgb(" + base::NumberToString(red) + ',' +
+ base::NumberToString(green) + ',' +
+ base::NumberToString(blue) + ')';
+ SanitizeTextAttributeValue(color_value, &color_value);
+ attributes.push_back(std::make_pair("color", color_value));
+ }
+ }
+
+ // First try to get the inherited font family name from the delegate. If we
+ // cannot find any name, fall back to looking the hierarchy of this node's
+ // AXNodeData instead.
+ std::string font_family(GetDelegate()->GetInheritedFontFamilyName());
+ if (font_family.empty()) {
+ font_family =
+ GetInheritedStringAttribute(ax::mojom::StringAttribute::kFontFamily);
+ }
+
+ // Attribute has no default value.
+ if (!font_family.empty()) {
+ SanitizeTextAttributeValue(font_family, &font_family);
+ attributes.push_back(std::make_pair("font-family", font_family));
+ }
+
+ float font_size;
+ // Attribute has no default value.
+ if (GetFloatAttribute(ax::mojom::FloatAttribute::kFontSize, &font_size)) {
+ // The IA2 Spec requires the value to be in pt, not in pixels.
+ // There are 72 points per inch.
+ // We assume that there are 96 pixels per inch on a standard display.
+ // TODO(nektar): Figure out the current value of pixels per inch.
+ float points = font_size * 72.0 / 96.0;
+
+ // Round to the nearest 0.5 points.
+ points = std::round(points * 2.0) / 2.0;
+
+ attributes.push_back(
+ std::make_pair("font-size", base::NumberToString(points) + "pt"));
+ }
+
+ // TODO(nektar): Add Blink support for the following attributes:
+ // text-line-through-mode, text-line-through-width, text-outline:false,
+ // text-position:baseline, text-shadow:none, text-underline-mode:continuous.
+
+ int32_t text_style = GetIntAttribute(ax::mojom::IntAttribute::kTextStyle);
+ if (text_style) {
+ if (GetData().HasTextStyle(ax::mojom::TextStyle::kBold))
+ attributes.push_back(std::make_pair("font-weight", "bold"));
+ if (GetData().HasTextStyle(ax::mojom::TextStyle::kItalic))
+ attributes.push_back(std::make_pair("font-style", "italic"));
+ if (GetData().HasTextStyle(ax::mojom::TextStyle::kLineThrough)) {
+ // TODO(nektar): Figure out a more specific value.
+ attributes.push_back(std::make_pair("text-line-through-style", "solid"));
+ }
+ if (GetData().HasTextStyle(ax::mojom::TextStyle::kUnderline)) {
+ // TODO(nektar): Figure out a more specific value.
+ attributes.push_back(std::make_pair("text-underline-style", "solid"));
+ }
+ }
+
+ // Screen readers look at the text attributes to determine if something is
+ // misspelled, so we need to propagate any spelling attributes from immediate
+ // parents of text-only objects.
+ std::string invalid_value = GetInvalidValue();
+ if (!invalid_value.empty())
+ attributes.push_back(std::make_pair("invalid", invalid_value));
+
+ std::string language = GetDelegate()->GetLanguage();
+ if (!language.empty()) {
+ SanitizeTextAttributeValue(language, &language);
+ attributes.push_back(std::make_pair("language", language));
+ }
+
+ auto text_direction = static_cast<ax::mojom::TextDirection>(
+ GetIntAttribute(ax::mojom::IntAttribute::kTextDirection));
+ switch (text_direction) {
+ case ax::mojom::TextDirection::kNone:
+ break;
+ case ax::mojom::TextDirection::kLtr:
+ attributes.push_back(std::make_pair("writing-mode", "lr"));
+ break;
+ case ax::mojom::TextDirection::kRtl:
+ attributes.push_back(std::make_pair("writing-mode", "rl"));
+ break;
+ case ax::mojom::TextDirection::kTtb:
+ attributes.push_back(std::make_pair("writing-mode", "tb"));
+ break;
+ case ax::mojom::TextDirection::kBtt:
+ // Not listed in the IA2 Spec.
+ attributes.push_back(std::make_pair("writing-mode", "bt"));
+ break;
+ }
+
+ auto text_position = static_cast<ax::mojom::TextPosition>(
+ GetIntAttribute(ax::mojom::IntAttribute::kTextPosition));
+ switch (text_position) {
+ case ax::mojom::TextPosition::kNone:
+ break;
+ case ax::mojom::TextPosition::kSubscript:
+ attributes.push_back(std::make_pair("text-position", "sub"));
+ break;
+ case ax::mojom::TextPosition::kSuperscript:
+ attributes.push_back(std::make_pair("text-position", "super"));
+ break;
+ }
+
+ return attributes;
+}
+
+void AXPlatformNodeBase::SanitizeTextAttributeValue(const std::string& input,
+ std::string* output) const {
+ DCHECK(output);
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.h b/chromium/ui/accessibility/platform/ax_platform_node_base.h
index 2ae8bfd736a..e545591e9a4 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.h
@@ -13,6 +13,7 @@
#include "build/build_config.h"
#include "ui/accessibility/ax_enums.mojom-forward.h"
#include "ui/accessibility/platform/ax_platform_node.h"
+#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/accessibility/platform/ax_platform_text_boundary.h"
#include "ui/base/buildflags.h"
#include "ui/gfx/geometry/rect.h"
@@ -25,7 +26,6 @@
namespace ui {
struct AXNodeData;
-class AXPlatformNodeDelegate;
struct AX_EXPORT AXHypertext {
AXHypertext();
@@ -273,6 +273,8 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// input node. The node's subtree will not be searched.
int NearestTextIndexToPoint(gfx::Point point);
+ ui::TextAttributeList ComputeTextAttributes() const;
+
//
// Delegate. This is a weak reference which owns |this|.
//
@@ -349,6 +351,12 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
static void SanitizeStringAttribute(const std::string& input,
std::string* output);
+ // Escapes characters in text attribute values as required by the platform.
+ // It's okay for input to be the same as output. The default implementation
+ // does nothing to the input value.
+ virtual void SanitizeTextAttributeValue(const std::string& input,
+ std::string* output) const;
+
// Compute the hypertext for this node to be exposed via IA2 and ATK This
// method is responsible for properly embedding children using the special
// embedded element character.
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
index dd26414994d..ff2b83be0c3 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -7,6 +7,7 @@
#include <stdint.h>
+#include <map>
#include <memory>
#include <new>
#include <set>
@@ -24,6 +25,7 @@
#include "ui/accessibility/ax_position.h"
#include "ui/accessibility/ax_text_boundary.h"
#include "ui/accessibility/ax_text_utils.h"
+#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/native_widget_types.h"
@@ -41,6 +43,15 @@ struct AXTreeData;
class AXTree;
class AXPlatformNode;
+using TextAttribute = std::pair<std::string, std::string>;
+using TextAttributeList = std::vector<TextAttribute>;
+
+// A TextAttributeMap is a map between the text offset in UTF-16 characters in
+// the node hypertext and the TextAttributeList that starts at that location.
+// An empty TextAttributeList signifies a return to the default node
+// TextAttributeList.
+using TextAttributeMap = std::map<int, TextAttributeList>;
+
// An object that wants to be accessible should derive from this class.
// AXPlatformNode subclasses use this interface to query all of the information
// about the object in order to implement native accessibility APIs.
@@ -77,6 +88,10 @@ class AX_EXPORT AXPlatformNodeDelegate {
// method is only meaningful on macOS.
virtual gfx::NativeViewAccessible GetNSWindow() = 0;
+ // Get the node for this delegate, which may be an AXPlatformNode or it may
+ // be a native accessible object implemented by another class.
+ virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0;
+
// Get the parent of the node, which may be an AXPlatformNode or it may
// be a native accessible object implemented by another class.
virtual gfx::NativeViewAccessible GetParent() = 0;
@@ -129,6 +144,18 @@ class AX_EXPORT AXPlatformNodeDelegate {
// implementation, this may mean the new selection will span multiple nodes.
virtual bool SetHypertextSelection(int start_offset, int end_offset) = 0;
+ // Compute the text attributes map for the node associated with this
+ // delegate, given a set of default text attributes that apply to the entire
+ // node. A text attribute map associates a list of text attributes with a
+ // given hypertext offset in this node.
+ virtual TextAttributeMap ComputeTextAttributeMap(
+ const TextAttributeList& default_attributes) const = 0;
+
+ // Get the inherited font family name for text attributes. We need this
+ // because inheritance works differently between the different delegate
+ // 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;
@@ -193,8 +220,15 @@ class AX_EXPORT AXPlatformNodeDelegate {
// Get whether this node is in web content.
virtual bool IsWebContent() const = 0;
+ // Get another node from this same tree.
virtual AXPlatformNode* GetFromNodeID(int32_t id) = 0;
+ // Get a node from a different tree using a tree ID and node ID.
+ // Note that this is only guaranteed to work if the other tree is of the
+ // same type, i.e. it won't work between web and views or vice-versa.
+ virtual AXPlatformNode* GetFromTreeIDAndNodeID(const ui::AXTreeID& ax_tree_id,
+ int32_t id) = 0;
+
// Given a node ID attribute (one where IsNodeIdIntAttribute is true), return
// a target nodes for which this delegate's node has that relationship
// attribute or NULL if there is no such relationship.
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 a07e5c145f3..b018bd486d3 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -44,6 +44,11 @@ gfx::NativeViewAccessible AXPlatformNodeDelegateBase::GetNSWindow() {
return nullptr;
}
+gfx::NativeViewAccessible
+AXPlatformNodeDelegateBase::GetNativeViewAccessible() {
+ return nullptr;
+}
+
gfx::NativeViewAccessible AXPlatformNodeDelegateBase::GetParent() {
return nullptr;
}
@@ -220,6 +225,12 @@ AXPlatformNode* AXPlatformNodeDelegateBase::GetFromNodeID(int32_t id) {
return nullptr;
}
+AXPlatformNode* AXPlatformNodeDelegateBase::GetFromTreeIDAndNodeID(
+ const ui::AXTreeID& ax_tree_id,
+ int32_t id) {
+ return nullptr;
+}
+
int AXPlatformNodeDelegateBase::GetIndexInParent() {
AXPlatformNodeDelegate* parent = GetParentDelegate();
if (!parent)
@@ -405,6 +416,19 @@ AXPlatformNodeDelegateBase::GetStyleNameAttributeAsLocalizedString() const {
return base::string16();
}
+TextAttributeMap AXPlatformNodeDelegateBase::ComputeTextAttributeMap(
+ const TextAttributeList& default_attributes) const {
+ ui::TextAttributeMap attributes_map;
+ attributes_map[0] = default_attributes;
+ return attributes_map;
+}
+
+std::string AXPlatformNodeDelegateBase::GetInheritedFontFamilyName() const {
+ // We don't have access to AXNodeData here, so we cannot return
+ // an inherited font family name.
+ return std::string();
+}
+
bool AXPlatformNodeDelegateBase::ShouldIgnoreHoveredStateForTesting() {
return true;
}
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 6fdcd605765..a9623c13204 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -7,6 +7,7 @@
#include <stdint.h>
+#include <memory>
#include <set>
#include <string>
#include <vector>
@@ -44,6 +45,10 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
// See comments in AXPlatformNodeDelegate.
gfx::NativeViewAccessible GetNSWindow() override;
+ // Get the node for this delegate, which may be an AXPlatformNode or it may
+ // be a native accessible object implemented by another class.
+ gfx::NativeViewAccessible GetNativeViewAccessible() override;
+
// Get the parent of the node, which may be an AXPlatformNode or it may
// be a native accessible object implemented by another class.
gfx::NativeViewAccessible GetParent() override;
@@ -88,6 +93,9 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
base::string16 GetHypertext() const override;
bool SetHypertextSelection(int start_offset, int end_offset) override;
+ TextAttributeMap ComputeTextAttributeMap(
+ const TextAttributeList& default_attributes) const override;
+ std::string GetInheritedFontFamilyName() const override;
base::string16 GetInnerText() const override;
@@ -141,8 +149,15 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
// Get whether this node is in web content.
bool IsWebContent() const override;
+ // Get another node from this same tree.
AXPlatformNode* GetFromNodeID(int32_t id) override;
+ // Get a node from a different tree using a tree ID and node ID.
+ // Note that this is only guaranteed to work if the other tree is of the
+ // same type, i.e. it won't work between web and views or vice-versa.
+ AXPlatformNode* GetFromTreeIDAndNodeID(const ui::AXTreeID& ax_tree_id,
+ int32_t id) override;
+
// Given a node ID attribute (one where IsNodeIdIntAttribute is true), return
// a target nodes for which this delegate's node has that relationship
// attribute or NULL if there is no such relationship.
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
index 418729482e8..485e7505dd6 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -43,7 +43,11 @@ RoleMap BuildRoleMap() {
{ax::mojom::Role::kAlert, NSAccessibilityGroupRole},
{ax::mojom::Role::kAlertDialog, NSAccessibilityGroupRole},
{ax::mojom::Role::kAnchor, NSAccessibilityGroupRole},
- {ax::mojom::Role::kAnnotation, NSAccessibilityUnknownRole},
+ {ax::mojom::Role::kAnnotationAttribution, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kAnnotationCommentary, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kAnnotationPresence, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kAnnotationRevision, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kAnnotationSuggestion, NSAccessibilityGroupRole},
{ax::mojom::Role::kApplication, NSAccessibilityGroupRole},
{ax::mojom::Role::kArticle, NSAccessibilityGroupRole},
{ax::mojom::Role::kAudio, NSAccessibilityGroupRole},
@@ -121,6 +125,7 @@ RoleMap BuildRoleMap() {
{ax::mojom::Role::kFigcaption, NSAccessibilityGroupRole},
{ax::mojom::Role::kFigure, NSAccessibilityGroupRole},
{ax::mojom::Role::kFooter, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kFooterAsNonLandmark, NSAccessibilityGroupRole},
{ax::mojom::Role::kForm, NSAccessibilityGroupRole},
{ax::mojom::Role::kGenericContainer, NSAccessibilityGroupRole},
{ax::mojom::Role::kGraphicsDocument, NSAccessibilityGroupRole},
@@ -130,6 +135,8 @@ RoleMap BuildRoleMap() {
// a list as of 10.12.6, so following WebKit and using table role:
{ax::mojom::Role::kGrid, NSAccessibilityTableRole}, // crbug.com/753925
{ax::mojom::Role::kGroup, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kHeader, NSAccessibilityGroupRole},
+ {ax::mojom::Role::kHeaderAsNonLandmark, NSAccessibilityGroupRole},
{ax::mojom::Role::kHeading, @"AXHeading"},
{ax::mojom::Role::kIframe, NSAccessibilityGroupRole},
{ax::mojom::Role::kIframePresentational, NSAccessibilityGroupRole},
@@ -179,9 +186,14 @@ RoleMap BuildRoleMap() {
{ax::mojom::Role::kRootWebArea, @"AXWebArea"},
{ax::mojom::Role::kRow, NSAccessibilityRowRole},
{ax::mojom::Role::kRowHeader, @"AXCell"},
+ // TODO(accessibility) What should kRuby be? It's not listed? Any others
+ // missing? Maybe use switch statement so that compiler doesn't allow us
+ // to miss any.
+ {ax::mojom::Role::kRubyAnnotation, NSAccessibilityUnknownRole},
{ax::mojom::Role::kScrollBar, NSAccessibilityScrollBarRole},
{ax::mojom::Role::kSearch, NSAccessibilityGroupRole},
{ax::mojom::Role::kSearchBox, NSAccessibilityTextFieldRole},
+ {ax::mojom::Role::kSection, NSAccessibilityGroupRole},
{ax::mojom::Role::kSlider, NSAccessibilitySliderRole},
{ax::mojom::Role::kSliderThumb, NSAccessibilityValueIndicatorRole},
{ax::mojom::Role::kSpinButton, NSAccessibilityIncrementorRole},
@@ -238,15 +250,17 @@ RoleMap BuildSubroleMap() {
{ax::mojom::Role::kFooter, @"AXLandmarkContentInfo"},
{ax::mojom::Role::kForm, @"AXLandmarkForm"},
{ax::mojom::Role::kGraphicsDocument, @"AXDocument"},
+ {ax::mojom::Role::kHeader, @"AXLandmarkBanner"},
{ax::mojom::Role::kLog, @"AXApplicationLog"},
{ax::mojom::Role::kMain, @"AXLandmarkMain"},
{ax::mojom::Role::kMarquee, @"AXApplicationMarquee"},
{ax::mojom::Role::kMath, @"AXDocumentMath"},
{ax::mojom::Role::kNavigation, @"AXLandmarkNavigation"},
{ax::mojom::Role::kNote, @"AXDocumentNote"},
- {ax::mojom::Role::kRegion, @"AXDocumentRegion"},
+ {ax::mojom::Role::kRegion, @"AXLandmarkRegion"},
{ax::mojom::Role::kSearch, @"AXLandmarkSearch"},
{ax::mojom::Role::kSearchBox, @"AXSearchField"},
+ {ax::mojom::Role::kSection, @"AXLandmarkRegion"},
{ax::mojom::Role::kStatus, @"AXApplicationStatus"},
{ax::mojom::Role::kSwitch, @"AXSwitch"},
{ax::mojom::Role::kTabPanel, @"AXTabPanel"},
@@ -307,11 +321,6 @@ void PostAnnouncementNotification(NSString* announcement) {
}
void NotifyMacEvent(AXPlatformNodeCocoa* target, ax::mojom::Event event_type) {
- NSString* announcement_text = [target announcementTextForEvent:event_type];
- if (announcement_text) {
- PostAnnouncementNotification(announcement_text);
- return;
- }
NSString* notification =
[AXPlatformNodeCocoa nativeNotificationFromAXEvent:event_type];
if (notification)
@@ -1146,27 +1155,33 @@ gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() {
void AXPlatformNodeMac::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
GetNativeViewAccessible();
- // Add mappings between ax::mojom::Event and NSAccessibility notifications
- // using the EventMap above. This switch contains exceptions to those
- // mappings.
- switch (event_type) {
- case ax::mojom::Event::kTextChanged:
- // If the view is a user-editable textfield, this should change the value.
- if (GetData().role == ax::mojom::Role::kTextField) {
- NotifyMacEvent(native_node_, ax::mojom::Event::kValueChanged);
- return;
- }
- break;
- case ax::mojom::Event::kSelection:
+ // Handle special cases.
+ NSString* announcement_text =
+ [native_node_ announcementTextForEvent:event_type];
+ if (announcement_text) {
+ PostAnnouncementNotification(announcement_text);
+ return;
+ }
+ if (event_type == ax::mojom::Event::kSelection) {
+ ax::mojom::Role role = GetData().role;
+ if (ui::IsMenuItem(role)) {
// On Mac, map menu item selection to a focus event.
- if (ui::IsMenuItem(GetData().role)) {
- NotifyMacEvent(native_node_, ax::mojom::Event::kFocus);
- return;
+ NotifyMacEvent(native_node_, ax::mojom::Event::kFocus);
+ return;
+ } else if (ui::IsListItem(role)) {
+ if (AXPlatformNodeBase* container = GetSelectionContainer()) {
+ const ui::AXNodeData& data = container->GetData();
+ if (data.role == ax::mojom::Role::kListBox &&
+ !data.HasState(ax::mojom::State::kMultiselectable) &&
+ GetDelegate()->GetFocus() == GetNativeViewAccessible()) {
+ NotifyMacEvent(native_node_, ax::mojom::Event::kFocus);
+ return;
+ }
}
- break;
- default:
- break;
+ }
}
+ // Otherwise, use mappings between ax::mojom::Event and NSAccessibility
+ // notifications from the EventMap above.
NotifyMacEvent(native_node_, event_type);
}
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 b8825da748f..ac7518e5d31 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
@@ -71,6 +71,8 @@ class AXPlatformNodeTextChildProviderTest : public ui::AXPlatformNodeWinTest {
AXNode* root_node = GetRootNode();
AXNodePosition::SetTree(tree_.get());
+ AXTreeManagerMap::GetInstance().AddTreeManager(update.tree_data.tree_id,
+ this);
AXNode* nontext_child_of_root_node = root_node->children()[0];
AXNode* text_child_of_root_node = root_node->children()[1];
AXNode* nontext_child_of_nontext_node =
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 2c2c2e7be83..bcff2a5d35b 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -12,10 +12,30 @@
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
-#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL() \
- if (!owner() || !owner()->GetDelegate() || !start_->GetAnchor() || \
- !end_->GetAnchor()) \
+#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL() \
+ if (!owner() || !owner()->GetDelegate() || !start_ || \
+ !start_->GetAnchor() || !end_ || !end_->GetAnchor()) \
return UIA_E_ELEMENTNOTAVAILABLE;
+#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN(in) \
+ if (!owner() || !owner()->GetDelegate() || !start_ || \
+ !start_->GetAnchor() || !end_ || !end_->GetAnchor()) \
+ return UIA_E_ELEMENTNOTAVAILABLE; \
+ if (!in) \
+ return E_POINTER;
+#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(out) \
+ if (!owner() || !owner()->GetDelegate() || !start_ || \
+ !start_->GetAnchor() || !end_ || !end_->GetAnchor()) \
+ return UIA_E_ELEMENTNOTAVAILABLE; \
+ if (!out) \
+ return E_POINTER; \
+ *out = {};
+#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(in, out) \
+ if (!owner() || !owner()->GetDelegate() || !start_ || \
+ !start_->GetAnchor() || !end_ || !end_->GetAnchor()) \
+ return UIA_E_ELEMENTNOTAVAILABLE; \
+ if (!in || !out) \
+ return E_POINTER; \
+ *out = {};
// Validate bounds calculated by AXPlatformNodeDelegate. Degenerate bounds
// indicate the interface is not yet supported on the platform.
@@ -25,6 +45,38 @@
namespace ui {
+class AXRangeScreenRectDelegateImpl : public AXRangeScreenRectDelegate {
+ public:
+ AXRangeScreenRectDelegateImpl(AXPlatformNodeTextRangeProviderWin* host)
+ : host_(host) {}
+
+ gfx::Rect GetInnerTextRangeBoundsRect(
+ AXTreeID tree_id,
+ AXNode::AXID node_id,
+ int start_offset,
+ int end_offset,
+ AXOffscreenResult* offscreen_result) override {
+ AXPlatformNodeDelegate* delegate = host_->GetDelegate(tree_id, node_id);
+ DCHECK(delegate);
+ return delegate->GetInnerTextRangeBoundsRect(
+ start_offset, end_offset, ui::AXCoordinateSystem::kScreen,
+ ui::AXClippingBehavior::kClipped, offscreen_result);
+ }
+
+ gfx::Rect GetBoundsRect(AXTreeID tree_id,
+ AXNode::AXID node_id,
+ AXOffscreenResult* offscreen_result) override {
+ AXPlatformNodeDelegate* delegate = host_->GetDelegate(tree_id, node_id);
+ DCHECK(delegate);
+ return delegate->GetBoundsRect(ui::AXCoordinateSystem::kScreen,
+ ui::AXClippingBehavior::kClipped,
+ offscreen_result);
+ }
+
+ private:
+ AXPlatformNodeTextRangeProviderWin* host_;
+};
+
AXPlatformNodeTextRangeProviderWin::AXPlatformNodeTextRangeProviderWin() {
DVLOG(1) << __func__;
}
@@ -55,7 +107,7 @@ ITextRangeProvider* AXPlatformNodeTextRangeProviderWin::CreateTextRangeProvider(
STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Clone(
ITextRangeProvider** clone) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_CLONE);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(clone);
*clone = CreateTextRangeProvider(owner_, start_->Clone(), end_->Clone());
return S_OK;
@@ -65,19 +117,16 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Compare(
ITextRangeProvider* other,
BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_COMPARE);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(other, result);
CComPtr<AXPlatformNodeTextRangeProviderWin> other_provider;
- if (other->QueryInterface(&other_provider) != S_OK) {
+ if (other->QueryInterface(&other_provider) != S_OK)
return UIA_E_INVALIDOPERATION;
- }
- *result = FALSE;
if (*start_ == *(other_provider->start_) &&
*end_ == *(other_provider->end_)) {
*result = TRUE;
}
-
return S_OK;
}
@@ -87,29 +136,30 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::CompareEndpoints(
TextPatternRangeEndpoint other_endpoint,
int* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_COMPAREENDPOINTS);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(other, result);
CComPtr<AXPlatformNodeTextRangeProviderWin> other_provider;
- if (other->QueryInterface(&other_provider) != S_OK) {
+ if (other->QueryInterface(&other_provider) != S_OK)
return UIA_E_INVALIDOPERATION;
- }
const AXPositionInstance& this_provider_endpoint =
(this_endpoint == TextPatternRangeEndpoint_Start) ? start_ : end_;
-
const AXPositionInstance& other_provider_endpoint =
(other_endpoint == TextPatternRangeEndpoint_Start)
? other_provider->start_
: other_provider->end_;
- if (*this_provider_endpoint < *other_provider_endpoint) {
+ base::Optional<int> comparison =
+ this_provider_endpoint->CompareTo(*other_provider_endpoint);
+ if (!comparison)
+ return UIA_E_INVALIDOPERATION;
+
+ if (comparison.value() < 0)
*result = -1;
- } else if (*this_provider_endpoint > *other_provider_endpoint) {
+ else if (comparison.value() > 0)
*result = 1;
- } else {
+ else
*result = 0;
- }
-
return S_OK;
}
@@ -146,6 +196,8 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
AXBoundaryBehavior::CrossBoundary);
DCHECK(!end_->IsNullPosition());
}
+
+ NormalizeTextRange();
break;
}
case TextUnit_Format:
@@ -157,14 +209,16 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
case TextUnit_Word:
start_ = start_->CreatePreviousWordStartPosition(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
-
// Since start_ is already located at a word boundary, we need to cross it
- // in order to move to the next one; the only edge case here is the last
- // word in the document, which will result in a null position.
+ // in order to move to the next one (stopping at the last anchor's end).
end_ = start_->CreateNextWordStartPosition(
- AXBoundaryBehavior::CrossBoundary);
- if (end_->IsNullPosition())
- end_ = start_->CreatePositionAtEndOfDocument();
+ AXBoundaryBehavior::StopAtLastAnchorBoundary);
+ // Because Windows ATs behave undesirably when the start and end endpoints
+ // are not in the same anchor (for character and word navigation), make
+ // sure to bring back the end endpoint to the end of the start's anchor.
+ if (start_->anchor_id() != end_->anchor_id()) {
+ end_ = start_->CreatePositionAtEndOfAnchor();
+ }
break;
case TextUnit_Line:
start_ = start_->CreatePreviousLineStartPosition(
@@ -179,10 +233,10 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
AXBoundaryBehavior::StopIfAlreadyAtBoundary);
break;
case TextUnit_Page: {
- // If the document doesn't support pagination, default to document units
- // per UIA spec
- AXPositionInstance common_ancestor = start_->LowestCommonAncestor(*end_);
- if (common_ancestor->GetAnchor()->tree()->HasPaginationSupport()) {
+ // Per UIA spec, if the document containing the current range doesn't
+ // support pagination, default to document navigation.
+ const AXNode* common_anchor = start_->LowestCommonAnchor(*end_);
+ if (common_anchor->tree()->HasPaginationSupport()) {
start_ = start_->CreatePreviousPageStartPosition(
ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary);
end_ = start_->CreateNextPageEndPosition(
@@ -198,21 +252,6 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
default:
return UIA_E_NOTSUPPORTED;
}
-
- // Some text positions are equal when compared, but they could be located at
- // different anchors, affecting how `GetEnclosingElement` works. Normalize the
- // endpoints to correctly enclose characters of the text representation.
- AXPositionInstance normalized_start = start_->AsPositionBeforeCharacter();
- AXPositionInstance normalized_end = end_->AsPositionBeforeCharacter();
-
- if (!normalized_start->IsNullPosition()) {
- DCHECK_EQ(*start_, *normalized_start);
- start_ = std::move(normalized_start);
- }
- if (!normalized_end->IsNullPosition()) {
- DCHECK_EQ(*end_, *normalized_end);
- end_ = std::move(normalized_end);
- }
return S_OK;
}
@@ -250,7 +289,8 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::FindAttribute(
// match the attribute and value and there is a previously matched range.
// The previously matched range is the final match we found.
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_FINDATTRIBUTE);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(result);
+ NormalizeTextRange();
*result = nullptr;
AXPositionInstance matched_range_start = nullptr;
@@ -337,7 +377,6 @@ HRESULT AXPlatformNodeTextRangeProviderWin::FindAttributeRange(
break;
}
}
-
return S_OK;
}
@@ -347,13 +386,9 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::FindText(
BOOL ignore_case,
ITextRangeProvider** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_FINDTEXT);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
- if (!result || !string)
- return E_INVALIDARG;
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN_1_OUT(string, result);
- *result = nullptr;
base::string16 search_string(string);
-
if (search_string.length() <= 0)
return E_INVALIDARG;
@@ -374,17 +409,23 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
TEXTATTRIBUTEID attribute_id,
VARIANT* value) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETATTRIBUTEVALUE);
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(value);
+ NormalizeTextRange();
base::win::ScopedVariant attribute_value_variant;
- AXNodeRange range(start_->Clone(), end_->Clone());
- for (const AXNodeRange& leaf_text_range : range) {
- AXPositionInstanceType* anchor_start = leaf_text_range.anchor();
- AXPlatformNodeDelegate* delegate = GetDelegate(anchor_start);
- DCHECK(anchor_start && delegate);
+ // The range is inclusive, so advance our endpoint to the next position
+ auto end = end_->CreateNextAnchorPosition();
+
+ // Iterate over anchor positions
+ for (auto it = start_->Clone();
+ it->anchor_id() != end->anchor_id() || it->tree_id() != end->tree_id();
+ it = it->CreateNextAnchorPosition()) {
+ AXPlatformNodeDelegate* delegate = GetDelegate(it.get());
+ DCHECK(it && delegate);
AXPlatformNodeWin* platform_node = static_cast<AXPlatformNodeWin*>(
- delegate->GetFromNodeID(anchor_start->anchor_id()));
+ delegate->GetFromNodeID(it->anchor_id()));
DCHECK(platform_node);
base::win::ScopedVariant current_variant;
@@ -412,11 +453,12 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetBoundingRectangles(
SAFEARRAY** rectangles) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETBOUNDINGRECTANGLES);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(rectangles);
*rectangles = nullptr;
AXNodeRange range(start_->Clone(), end_->Clone());
- std::vector<gfx::Rect> rects = range.GetScreenRects();
+ AXRangeScreenRectDelegateImpl rect_delegate(this);
+ std::vector<gfx::Rect> rects = range.GetScreenRects(&rect_delegate);
// 4 array items per rect: left, top, width, height
SAFEARRAY* safe_array = SafeArrayCreateVector(
@@ -455,16 +497,33 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetBoundingRectangles(
STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetEnclosingElement(
IRawElementProviderSimple** element) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETENCLOSINGELEMENT);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(element);
- AXPositionInstance common_ancestor = start_->LowestCommonAncestor(*end_);
+ AXNode* common_anchor = start_->LowestCommonAnchor(*end_);
+ DCHECK(common_anchor);
+ if (!common_anchor)
+ return UIA_E_ELEMENTNOTAVAILABLE;
- AXPlatformNodeDelegate* delegate = GetDelegate(common_ancestor.get());
- DCHECK(delegate);
+ const AXTreeID tree_id = common_anchor->tree()->GetAXTreeID();
+ const AXNode::AXID node_id = common_anchor->id();
+ AXPlatformNodeWin* enclosing_node =
+ static_cast<AXPlatformNodeWin*>(AXPlatformNode::FromNativeViewAccessible(
+ GetDelegate(tree_id, node_id)->GetNativeViewAccessible()));
+ DCHECK(enclosing_node);
+ // If this node has an ancestor that is a control type, use that as the
+ // enclosing element.
+ enclosing_node = enclosing_node->GetLowestAccessibleElement();
+ DCHECK(enclosing_node);
+
+ while (ui::IsIgnored(enclosing_node->GetData())) {
+ AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
+ AXPlatformNode::FromNativeViewAccessible(enclosing_node->GetParent()));
+ DCHECK(parent);
+ enclosing_node = parent;
+ }
- delegate->GetFromNodeID(common_ancestor->anchor_id())
- ->GetNativeViewAccessible()
- ->QueryInterface(IID_PPV_ARGS(element));
+ enclosing_node->GetNativeViewAccessible()->QueryInterface(
+ IID_PPV_ARGS(element));
DCHECK(*element);
return S_OK;
@@ -473,11 +532,11 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetEnclosingElement(
STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetText(int max_count,
BSTR* text) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETTEXT);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(text);
// -1 is a valid value that signifies that the caller wants complete text.
- // Any other negative value is invalid.
- if (max_count < -1 || !text)
+ // Any other negative value is an invalid argument.
+ if (max_count < -1)
return E_INVALIDARG;
base::string16 full_text = GetString(max_count);
@@ -491,7 +550,6 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetText(int max_count,
} else {
*text = SysAllocString(L"");
}
-
return S_OK;
}
@@ -499,9 +557,7 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
int count,
int* units_moved) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_MOVE);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
-
- *units_moved = 0;
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(units_moved);
// Per MSDN, move with zero count has no effect.
if (count == 0)
@@ -542,6 +598,14 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
&end_units_moved);
succeeded_move = SUCCEEDED(hr) && end_units_moved == 1;
}
+
+ // Because Windows ATs behave undesirably when the start and end endpoints
+ // are not in the same anchor (for character and word navigation), make
+ // 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);
+ }
}
}
@@ -563,7 +627,7 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
int count,
int* units_moved) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_MOVEENDPOINTBYUNIT);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(units_moved);
// Per MSDN, MoveEndpointByUnit with zero count has no effect.
if (count == 0) {
@@ -584,8 +648,7 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
new_position = MoveEndpointByFormat(position_to_move, count, units_moved);
break;
case TextUnit_Word:
- new_position = MoveEndpointByWord(position_to_move, is_start_endpoint,
- count, units_moved);
+ new_position = MoveEndpointByWord(position_to_move, count, units_moved);
break;
case TextUnit_Line:
new_position = MoveEndpointByLine(position_to_move, is_start_endpoint,
@@ -606,12 +669,11 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
default:
return UIA_E_NOTSUPPORTED;
}
-
position_to_move = std::move(new_position);
// If the start was moved past the end, create a degenerate range with the end
// equal to the start. Do the equivalent if the end moved past the start.
- if (*start_ > *end_) {
+ if (*end_->AsTreePosition() < *start_->AsTreePosition() || *end_ < *start_) {
if (is_start_endpoint)
end_ = start_->Clone();
else
@@ -625,12 +687,11 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByRange(
ITextRangeProvider* other,
TextPatternRangeEndpoint other_endpoint) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_MOVEENPOINTBYRANGE);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_IN(other);
CComPtr<AXPlatformNodeTextRangeProviderWin> other_provider;
- if (other->QueryInterface(&other_provider) != S_OK) {
+ if (other->QueryInterface(&other_provider) != S_OK)
return UIA_E_INVALIDOPERATION;
- }
const AXPositionInstance& other_provider_endpoint =
(other_endpoint == TextPatternRangeEndpoint_Start)
@@ -646,7 +707,6 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::MoveEndpointByRange(
if (*start_ > *end_)
start_ = end_->Clone();
}
-
return S_OK;
}
@@ -696,16 +756,23 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ScrollIntoView(
const AXTreeManager* ax_tree_manager =
AXTreeManagerMap::GetInstance().GetManager(common_ancestor_tree_id);
DCHECK(ax_tree_manager);
-
+ AXNode* root_node = ax_tree_manager->GetRootAsAXNode();
+ const AXPlatformNode* root_platform_node =
+ owner_->GetDelegate()->GetFromTreeIDAndNodeID(common_ancestor_tree_id,
+ root_node->id());
+ DCHECK(root_platform_node);
const AXPlatformNodeDelegate* root_delegate =
- ax_tree_manager->GetRootDelegate(common_ancestor_tree_id);
+ root_platform_node->GetDelegate();
const gfx::Rect root_frame_bounds = root_delegate->GetBoundsRect(
AXCoordinateSystem::kFrame, AXClippingBehavior::kUnclipped);
UIA_VALIDATE_BOUNDS(root_frame_bounds);
+ const AXPlatformNode* common_ancestor_platform_node =
+ owner_->GetDelegate()->GetFromTreeIDAndNodeID(
+ common_ancestor_tree_id, common_ancestor_anchor->id());
+ DCHECK(common_ancestor_platform_node);
AXPlatformNodeDelegate* common_ancestor_delegate =
- ax_tree_manager->GetDelegate(common_ancestor_tree_id,
- common_ancestor_anchor->id());
+ common_ancestor_platform_node->GetDelegate();
DCHECK(common_ancestor_delegate);
const gfx::Rect text_range_container_frame_bounds =
common_ancestor_delegate->GetBoundsRect(AXCoordinateSystem::kFrame,
@@ -758,21 +825,23 @@ STDMETHODIMP AXPlatformNodeTextRangeProviderWin::ScrollIntoView(
STDMETHODIMP AXPlatformNodeTextRangeProviderWin::GetChildren(
SAFEARRAY** children) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_GETCHILDREN);
- UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
+ UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(children);
std::vector<gfx::NativeViewAccessible> descendants;
- AXPositionInstance common_ancestor =
- start_->LowestCommonAncestor(*end_.get());
-
- if (!common_ancestor->GetAnchor()->children().empty()) {
- AXPlatformNodeDelegate* delegate = GetDelegate(common_ancestor.get());
- DCHECK(delegate);
-
- descendants = delegate->GetFromNodeID(common_ancestor->anchor_id())
- ->GetDelegate()
- ->GetDescendants();
+ const AXNode* common_anchor = start_->LowestCommonAnchor(*end_);
+ const AXTreeID tree_id = common_anchor->tree()->GetAXTreeID();
+ const AXNode::AXID node_id = common_anchor->id();
+ AXPlatformNodeDelegate* delegate = GetDelegate(tree_id, node_id);
+ DCHECK(delegate);
+ while (ui::IsIgnored(delegate->GetData())) {
+ auto* node = static_cast<AXPlatformNodeWin*>(
+ AXPlatformNode::FromNativeViewAccessible(delegate->GetParent()));
+ DCHECK(node);
+ delegate = node->GetDelegate();
}
+ if (delegate->GetChildCount())
+ descendants = delegate->GetDescendants();
SAFEARRAY* safe_array =
SafeArrayCreateVector(VT_UNKNOWN, 0, descendants.size());
@@ -809,11 +878,18 @@ AXPlatformNodeWin* AXPlatformNodeTextRangeProviderWin::owner() const {
AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetDelegate(
const AXPositionInstanceType* position) const {
- AXTreeManager* manager =
- AXTreeManagerMap::GetInstance().GetManager(position->tree_id());
- return manager
- ? manager->GetDelegate(position->tree_id(), position->anchor_id())
- : owner()->GetDelegate();
+ return GetDelegate(position->tree_id(), position->anchor_id());
+}
+
+AXPlatformNodeDelegate* AXPlatformNodeTextRangeProviderWin::GetDelegate(
+ const AXTreeID tree_id,
+ const AXNode::AXID node_id) const {
+ AXPlatformNode* platform_node =
+ owner_->GetDelegate()->GetFromTreeIDAndNodeID(tree_id, node_id);
+ if (!platform_node)
+ return nullptr;
+
+ return platform_node->GetDelegate();
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
@@ -821,39 +897,17 @@ AXPlatformNodeTextRangeProviderWin::MoveEndpointByCharacter(
const AXPositionInstance& endpoint,
const int count,
int* units_moved) {
- DCHECK_NE(count, 0);
return MoveEndpointByUnitHelper(
- std::move(endpoint),
- (count > 0) ? &AXPositionInstanceType::CreateNextCharacterPosition
- : &AXPositionInstanceType::CreatePreviousCharacterPosition,
- count, units_moved);
+ std::move(endpoint), AXTextBoundary::kCharacter, count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
AXPlatformNodeTextRangeProviderWin::MoveEndpointByWord(
const AXPositionInstance& endpoint,
- bool is_start_endpoint,
const int count,
int* units_moved) {
- DCHECK_NE(count, 0);
-
- bool going_forward = count > 0;
- AXPositionInstance new_position = MoveEndpointByUnitHelper(
- std::move(endpoint),
- going_forward ? &AXPositionInstanceType::CreateNextWordStartPosition
- : &AXPositionInstanceType::CreatePreviousWordStartPosition,
- count, units_moved);
-
- if (going_forward && *units_moved < count &&
- !new_position->AsPositionBeforeCharacter()->IsNullPosition()) {
- AXPositionInstance next_word_position =
- new_position->CreatePositionAtEndOfDocument();
- DCHECK(!next_word_position->IsNullPosition());
-
- new_position = std::move(next_word_position);
- ++*units_moved;
- }
- return new_position;
+ return MoveEndpointByUnitHelper(
+ std::move(endpoint), AXTextBoundary::kWordStart, count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
@@ -862,21 +916,10 @@ AXPlatformNodeTextRangeProviderWin::MoveEndpointByLine(
bool is_start_endpoint,
const int count,
int* units_moved) {
- DCHECK_NE(count, 0);
-
- CreateNextPositionFunction create_next_position = nullptr;
- if (count > 0)
- create_next_position =
- is_start_endpoint ? &AXPositionInstanceType::CreateNextLineStartPosition
- : &AXPositionInstanceType::CreateNextLineEndPosition;
- else
- create_next_position =
- is_start_endpoint
- ? &AXPositionInstanceType::CreatePreviousLineStartPosition
- : &AXPositionInstanceType::CreatePreviousLineEndPosition;
-
- return MoveEndpointByUnitHelper(std::move(endpoint), create_next_position,
- count, units_moved);
+ return MoveEndpointByUnitHelper(
+ std::move(endpoint),
+ is_start_endpoint ? AXTextBoundary::kLineStart : AXTextBoundary::kLineEnd,
+ count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
@@ -884,106 +927,39 @@ AXPlatformNodeTextRangeProviderWin::MoveEndpointByFormat(
const AXPositionInstance& endpoint,
const int count,
int* units_moved) {
- DCHECK_NE(count, 0);
-
- CreateNextPositionFunction create_next_position = nullptr;
- if (count > 0)
- create_next_position = &AXPositionInstanceType::CreateNextFormatEndPosition;
- else
- create_next_position =
- &AXPositionInstanceType::CreatePreviousFormatStartPosition;
-
- return MoveEndpointByUnitHelper(std::move(endpoint), create_next_position,
- count, units_moved);
+ return MoveEndpointByUnitHelper(
+ std::move(endpoint), AXTextBoundary::kFormatChange, count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
AXPlatformNodeTextRangeProviderWin::MoveEndpointByParagraph(
- const AXNodePosition::AXPositionInstance& endpoint,
+ const AXPositionInstance& endpoint,
const bool is_start_endpoint,
const int count,
- int* count_moved) {
- auto current_endpoint = endpoint->Clone();
- const bool forwards = count > 0;
- const int count_abs = std::abs(count);
- const auto behavior = AXBoundaryBehavior::CrossBoundary;
- int iteration = 0;
- for (iteration = 0; iteration < count_abs; ++iteration) {
- AXPositionInstance next_endpoint;
- if (forwards) {
- next_endpoint =
- is_start_endpoint
- ? current_endpoint->CreateNextParagraphStartPosition(behavior)
- : current_endpoint->CreateNextParagraphEndPosition(behavior);
- } else {
- next_endpoint =
- is_start_endpoint
- ? current_endpoint->CreatePreviousParagraphStartPosition(behavior)
- : current_endpoint->CreatePreviousParagraphEndPosition(behavior);
- }
-
- // End of document
- if (next_endpoint->IsNullPosition()) {
- int document_moved;
- next_endpoint = MoveEndpointByDocument(endpoint, count, &document_moved);
- if (*endpoint != *next_endpoint && !next_endpoint->IsNullPosition()) {
- ++iteration;
- current_endpoint = std::move(next_endpoint);
- }
- break;
- }
- current_endpoint = std::move(next_endpoint);
- }
-
- *count_moved = (forwards) ? iteration : -iteration;
- return current_endpoint;
+ int* units_moved) {
+ return MoveEndpointByUnitHelper(std::move(endpoint),
+ is_start_endpoint
+ ? AXTextBoundary::kParagraphStart
+ : AXTextBoundary::kParagraphEnd,
+ count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
AXPlatformNodeTextRangeProviderWin::MoveEndpointByPage(
- const AXNodePosition::AXPositionInstance& endpoint,
+ const AXPositionInstance& endpoint,
const bool is_start_endpoint,
const int count,
- int* count_moved) {
- // If the document doesn't support pagination, default to document navigation
- // per UIA spec
+ int* units_moved) {
+ // Per UIA spec, if the document containing the current endpoint doesn't
+ // support pagination, default to document navigation.
AXPositionInstance common_ancestor = start_->LowestCommonAncestor(*end_);
if (!common_ancestor->GetAnchor()->tree()->HasPaginationSupport())
- return MoveEndpointByDocument(std::move(endpoint), count, count_moved);
-
- auto current_endpoint = endpoint->Clone();
- const bool forwards = count > 0;
- const int count_abs = std::abs(count);
- const auto behavior = ui::AXBoundaryBehavior::CrossBoundary;
- int iteration = 0;
- for (iteration = 0; iteration < count_abs; ++iteration) {
- AXPositionInstance next_endpoint;
- if (forwards) {
- next_endpoint =
- is_start_endpoint
- ? current_endpoint->CreateNextPageStartPosition(behavior)
- : current_endpoint->CreateNextPageEndPosition(behavior);
- } else {
- next_endpoint =
- is_start_endpoint
- ? current_endpoint->CreatePreviousPageStartPosition(behavior)
- : current_endpoint->CreatePreviousPageEndPosition(behavior);
- }
- // End of document
- if (next_endpoint->IsNullPosition()) {
- int document_moved;
- next_endpoint = MoveEndpointByDocument(endpoint, count, &document_moved);
- if (*endpoint != *next_endpoint && !next_endpoint->IsNullPosition()) {
- ++iteration;
- current_endpoint = std::move(next_endpoint);
- }
- break;
- }
- current_endpoint = std::move(next_endpoint);
- }
+ return MoveEndpointByDocument(std::move(endpoint), count, units_moved);
- *count_moved = (forwards) ? iteration : -iteration;
- return current_endpoint;
+ return MoveEndpointByUnitHelper(
+ std::move(endpoint),
+ is_start_endpoint ? AXTextBoundary::kPageStart : AXTextBoundary::kPageEnd,
+ count, units_moved);
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
@@ -993,39 +969,39 @@ AXPlatformNodeTextRangeProviderWin::MoveEndpointByDocument(
int* units_moved) {
DCHECK_NE(count, 0);
- *units_moved = 0;
- AXPositionInstance current_endpoint = endpoint->Clone();
- const bool forwards = count > 0;
-
- if (forwards && !current_endpoint->AtEndOfDocument()) {
- current_endpoint = endpoint->CreatePositionAtEndOfDocument();
- *units_moved = 1;
- } else if (!forwards && !current_endpoint->AtStartOfDocument()) {
- current_endpoint = endpoint->CreatePositionAtStartOfDocument();
- *units_moved = -1;
+ if (count < 0) {
+ *units_moved = !endpoint->AtStartOfDocument() ? -1 : 0;
+ return endpoint->CreatePositionAtStartOfDocument();
}
-
- return current_endpoint;
+ *units_moved = !endpoint->AtEndOfDocument() ? 1 : 0;
+ return endpoint->CreatePositionAtEndOfDocument();
}
AXPlatformNodeTextRangeProviderWin::AXPositionInstance
AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitHelper(
const AXPositionInstance& endpoint,
- CreateNextPositionFunction create_next_position,
+ const AXTextBoundary boundary_type,
const int count,
int* units_moved) {
DCHECK_NE(count, 0);
+ const bool going_forward = count > 0;
AXPositionInstance current_endpoint = endpoint->Clone();
-
for (int iteration = 0; iteration < std::abs(count); ++iteration) {
AXPositionInstance next_endpoint =
- (current_endpoint.get()->*create_next_position)(
- AXBoundaryBehavior::CrossBoundary);
-
- // We've reached either the start or the end of the document.
- if (next_endpoint->IsNullPosition()) {
- *units_moved = (count > 0) ? iteration : -iteration;
+ current_endpoint->CreatePositionAtTextBoundary(
+ boundary_type,
+ going_forward ? AXTextBoundaryDirection::kForwards
+ : AXTextBoundaryDirection::kBackwards,
+ AXBoundaryBehavior::StopAtLastAnchorBoundary);
+ DCHECK(!next_endpoint->IsNullPosition());
+
+ // Since AXBoundaryBehavior::StopAtLastAnchorBoundary forces the next text
+ // boundary position to be different than the input position, the only case
+ // where these are equal is when they're already located at the last anchor
+ // boundary. In such case, there is no next position to move to.
+ if (*current_endpoint == *next_endpoint) {
+ *units_moved = going_forward ? iteration : -iteration;
return current_endpoint;
}
current_endpoint = std::move(next_endpoint);
@@ -1035,4 +1011,17 @@ AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitHelper(
return current_endpoint;
}
+void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange() {
+ // Only normalize non-degenerate ranges.
+ if (*start_ != *end_) {
+ AXPositionInstance normalized_start = start_->AsPositionBeforeCharacter();
+ AXPositionInstance normalized_end = end_->AsPositionAfterCharacter();
+ DCHECK_EQ(*start_, *normalized_start);
+ DCHECK_EQ(*end_, *normalized_end);
+
+ start_ = std::move(normalized_start);
+ end_ = std::move(normalized_end);
+ }
+}
+
} // namespace ui
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 2db00331378..b4749b02f7a 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -12,6 +12,7 @@
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_position.h"
#include "ui/accessibility/ax_range.h"
+#include "ui/accessibility/ax_text_boundary.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
namespace ui {
@@ -83,15 +84,17 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
using AXPositionInstance = AXNodePosition::AXPositionInstance;
using AXPositionInstanceType = typename AXPositionInstance::element_type;
using AXNodeRange = AXRange<AXPositionInstanceType>;
- using CreateNextPositionFunction =
- AXPositionInstance (AXPositionInstanceType::*)(AXBoundaryBehavior) const;
friend class AXPlatformNodeTextRangeProviderTest;
friend class AXPlatformNodeTextProviderTest;
+ friend class AXRangeScreenRectDelegateImpl;
+
base::string16 GetString(int max_count);
AXPlatformNodeWin* owner() const;
AXPlatformNodeDelegate* GetDelegate(
const AXPositionInstanceType* position) const;
+ AXPlatformNodeDelegate* GetDelegate(const AXTreeID tree_id,
+ const AXNode::AXID node_id) const;
template <typename AnchorIterator, typename ExpandMatchLambda>
HRESULT FindAttributeRange(const TEXTATTRIBUTEID text_attribute_id,
@@ -104,7 +107,6 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
const int count,
int* units_moved);
AXPositionInstance MoveEndpointByWord(const AXPositionInstance& endpoint,
- bool endpoint_is_start,
const int count,
int* units_moved);
AXPositionInstance MoveEndpointByLine(const AXPositionInstance& endpoint,
@@ -114,11 +116,11 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
AXPositionInstance MoveEndpointByParagraph(const AXPositionInstance& endpoint,
const bool is_start_endpoint,
const int count,
- int* count_moved);
+ int* units_moved);
AXPositionInstance MoveEndpointByPage(const AXPositionInstance& endpoint,
const bool is_start_endpoint,
const int count,
- int* count_moved);
+ int* units_moved);
AXPositionInstance MoveEndpointByFormat(const AXPositionInstance& endpoint,
const int count,
int* units_moved);
@@ -128,10 +130,12 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
AXPositionInstance MoveEndpointByUnitHelper(
const AXPositionInstance& endpoint,
- CreateNextPositionFunction create_next_position,
+ const AXTextBoundary boundary_type,
const int count,
int* units_moved);
+ void NormalizeTextRange();
+
CComPtr<AXPlatformNodeWin> owner_;
AXPositionInstance start_;
AXPositionInstance end_;
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 48a1608068d..e3eca56c27d 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
@@ -167,6 +167,18 @@ namespace ui {
EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text); \
}
+#define EXPECT_ENCLOSING_ELEMENT(ax_node_given, ax_node_expected) \
+ { \
+ ComPtr<ITextRangeProvider> text_range_provider; \
+ GetTextRangeProviderFromTextNode(text_range_provider, ax_node_given); \
+ ComPtr<IRawElementProviderSimple> enclosing_element; \
+ ASSERT_HRESULT_SUCCEEDED( \
+ text_range_provider->GetEnclosingElement(&enclosing_element)); \
+ ComPtr<IRawElementProviderSimple> expected_text_provider = \
+ QueryInterfaceFromNode<IRawElementProviderSimple>(ax_node_expected); \
+ EXPECT_EQ(expected_text_provider.Get(), enclosing_element.Get()); \
+ }
+
class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
public:
const AXNodePosition::AXPositionInstance& GetStart(
@@ -492,16 +504,24 @@ class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
paragraph1_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
- paragraph1_data.child_ids = {10};
+
+ ui::AXNodeData ignored_text_data;
+ ignored_text_data.id = 11;
+ ignored_text_data.role = ax::mojom::Role::kStaticText;
+ ignored_text_data.AddState(ax::mojom::State::kIgnored);
+ text_content = "ignored text";
+ ignored_text_data.SetName(text_content);
+
+ paragraph1_data.child_ids = {10, 11};
ui::AXNodeData paragraph2_data;
- paragraph2_data.id = 11;
+ paragraph2_data.id = 12;
paragraph2_data.role = ax::mojom::Role::kParagraph;
paragraph2_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
ui::AXNodeData paragraph2_text_data;
- paragraph2_text_data.id = 12;
+ paragraph2_text_data.id = 13;
paragraph2_text_data.role = ax::mojom::Role::kStaticText;
text_content = "Paragraph 2";
paragraph2_text_data.SetName(text_content);
@@ -513,28 +533,23 @@ class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
paragraph1_data.AddBoolAttribute(
ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
- paragraph2_data.child_ids = {12};
+ paragraph2_data.child_ids = {13};
ui::AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
- root_data.child_ids = {2, 4, 8, 9, 11};
+ root_data.child_ids = {2, 4, 8, 9, 12};
ui::AXTreeUpdate update;
update.has_tree_data = true;
update.root_id = root_data.id;
- update.nodes.push_back(root_data);
- update.nodes.push_back(group1_data);
- update.nodes.push_back(text_data);
- update.nodes.push_back(group2_data);
- update.nodes.push_back(line_break1_data);
- update.nodes.push_back(standalone_text_data);
- update.nodes.push_back(line_break2_data);
- update.nodes.push_back(bold_text_data);
- update.nodes.push_back(paragraph1_data);
- update.nodes.push_back(paragraph1_text_data);
- update.nodes.push_back(paragraph2_data);
- update.nodes.push_back(paragraph2_text_data);
+ update.nodes = {root_data, group1_data,
+ text_data, group2_data,
+ line_break1_data, standalone_text_data,
+ line_break2_data, bold_text_data,
+ paragraph1_data, paragraph1_text_data,
+ ignored_text_data, paragraph2_data,
+ paragraph2_text_data};
update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
return update;
}
@@ -644,29 +659,26 @@ class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
root_data.child_ids = {2, 4, 8, 10, 12, 14, 16};
ui::AXTreeUpdate update;
- ui::AXTreeData tree_data;
- tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
- update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = root_data.id;
- update.nodes.push_back(root_data);
- update.nodes.push_back(group1_data);
- update.nodes.push_back(text_data);
- update.nodes.push_back(group2_data);
- update.nodes.push_back(group3_data);
- update.nodes.push_back(line_break1_data);
- update.nodes.push_back(standalone_text_data);
- update.nodes.push_back(line_break2_data);
- update.nodes.push_back(bold_text_data);
- update.nodes.push_back(paragraph1_data);
- update.nodes.push_back(paragraph1_text_data);
- update.nodes.push_back(paragraph2_data);
- update.nodes.push_back(paragraph2_text_data);
- update.nodes.push_back(paragraph3_data);
- update.nodes.push_back(paragraph3_text_data);
- update.nodes.push_back(paragraph4_data);
- update.nodes.push_back(paragraph4_text_data);
-
+ update.nodes = {root_data,
+ group1_data,
+ text_data,
+ group2_data,
+ group3_data,
+ line_break1_data,
+ standalone_text_data,
+ line_break2_data,
+ bold_text_data,
+ paragraph1_data,
+ paragraph1_text_data,
+ paragraph2_data,
+ paragraph2_text_data,
+ paragraph3_data,
+ paragraph3_text_data,
+ paragraph4_data,
+ paragraph4_text_data};
+ update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
return update;
}
@@ -719,15 +731,12 @@ class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
root_data.child_ids = {2, 4, 6};
ui::AXTreeUpdate update;
- ui::AXTreeData tree_data;
- tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
- update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = root_data.id;
update.nodes = {root_data, page_1_data, page_1_text_data,
page_2_data, page_2_text_data, page_3_data,
page_3_text_data};
-
+ update.tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
return update;
}
};
@@ -948,8 +957,11 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
TEST_F(AXPlatformNodeTextRangeProviderTest,
TestITextRangeProviderExpandToEnclosingCharacter) {
- Init(BuildTextDocument({"some text", "more text"}));
+ ui::AXTreeUpdate update = BuildTextDocument({"some text", "more text"});
+ Init(update);
AXNodePosition::SetTree(tree_.get());
+ AXTreeManagerMap::GetInstance().AddTreeManager(update.tree_data.tree_id,
+ this);
AXNode* root_node = GetRootNode();
ComPtr<ITextRangeProvider> text_range_provider;
@@ -1146,7 +1158,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"First line of text\n");
// Moving the start by two lines will create a degenerate range positioned
- // at the next paragraph (skipping the newline)
+ // at the next paragraph (skipping the newline).
ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 2, &count));
EXPECT_EQ(2, count);
@@ -1169,10 +1181,10 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
/*expected_count*/ 1);
ASSERT_HRESULT_SUCCEEDED(
text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
- EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"bold text\n");
+ EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"bold text");
// Create a degenerate range at the end of the document, then expand by
- // paragraph
+ // paragraph.
ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
TextPatternRangeEndpoint_Start, TextUnit_Document, /*count*/ 1, &count));
EXPECT_EQ(1, count);
@@ -1422,11 +1434,11 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
/*count*/ 18,
- /*expected_text*/ L"\nS",
+ /*expected_text*/ L"S",
/*expected_count*/ 18);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
/*count*/ 16,
- /*expected_text*/ L"\nb",
+ /*expected_text*/ L"b",
/*expected_count*/ 16);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
/*count*/ 60,
@@ -1656,7 +1668,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMovePage) {
/*count*/ 5,
/*expected_text*/
L"some text on page 1\nsome text on page 2some more text on page 3",
- /*expected_count*/ 4);
+ /*expected_count*/ 3);
// Range moves
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Page,
@@ -1720,11 +1732,11 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveWord) {
/*expected_count*/ 1);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ 2,
- /*expected_text*/ L"text\n",
+ /*expected_text*/ L"text",
/*expected_count*/ 2);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ 2,
- /*expected_text*/ L"line\n",
+ /*expected_text*/ L"line",
/*expected_count*/ 2);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ 3,
@@ -1748,11 +1760,11 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderMoveWord) {
/*expected_count*/ -3);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ -3,
- /*expected_text*/ L"line\n",
+ /*expected_text*/ L"line",
/*expected_count*/ -3);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ -2,
- /*expected_text*/ L"text\n",
+ /*expected_text*/ L"text",
/*expected_count*/ -2);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
/*count*/ -6,
@@ -1922,9 +1934,9 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
/*expected_text*/ L"Standalone line\n",
/*expected_count*/ -3);
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
- /*count*/ -2,
+ /*count*/ -1,
/*expected_text*/ L"First line of text\n",
- /*expected_count*/ -2);
+ /*expected_count*/ -1);
// Moving backward by any number of paragraphs at the start of document
// should have no effect.
@@ -1976,7 +1988,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
/*count*/ -7,
/*expected_text*/ L"First line of text\n",
- /*expected_count*/ -5);
+ /*expected_count*/ -4);
EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
/*count*/ -1,
@@ -2738,28 +2750,109 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
TEST_F(AXPlatformNodeTextRangeProviderTest,
TestITextRangeProviderGetEnclosingElement) {
- Init(BuildTextDocument({"some text", "more text"}));
+ // Set up ax tree with the following structure:
+ //
+ // root
+ // |
+ // paragraph_______
+ // | |
+ // static_text link
+ // | |
+ // text_node static_text
+ // |
+ // text_node
+
+ ui::AXNodeData root_data;
+ root_data.id = 1;
+ root_data.role = ax::mojom::Role::kRootWebArea;
+
+ ui::AXNodeData paragraph_data;
+ paragraph_data.id = 2;
+ paragraph_data.role = ax::mojom::Role::kParagraph;
+ root_data.child_ids.push_back(paragraph_data.id);
+
+ ui::AXNodeData static_text_data1;
+ static_text_data1.id = 3;
+ static_text_data1.role = ax::mojom::Role::kStaticText;
+ paragraph_data.child_ids.push_back(static_text_data1.id);
+
+ ui::AXNodeData inline_text_data1;
+ inline_text_data1.id = 4;
+ inline_text_data1.role = ax::mojom::Role::kInlineTextBox;
+ static_text_data1.child_ids.push_back(inline_text_data1.id);
+
+ ui::AXNodeData link_data;
+ link_data.id = 5;
+ link_data.role = ax::mojom::Role::kLink;
+ paragraph_data.child_ids.push_back(link_data.id);
+
+ ui::AXNodeData static_text_data2;
+ static_text_data2.id = 6;
+ static_text_data2.role = ax::mojom::Role::kStaticText;
+ link_data.child_ids.push_back(static_text_data2.id);
+
+ ui::AXNodeData inline_text_data2;
+ inline_text_data2.id = 7;
+ inline_text_data2.role = ax::mojom::Role::kInlineTextBox;
+ static_text_data2.child_ids.push_back(inline_text_data2.id);
+
+ ui::AXTreeUpdate update;
+ ui::AXTreeData tree_data;
+ tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+ update.tree_data = tree_data;
+ update.has_tree_data = true;
+ update.root_id = root_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);
+
+ Init(update);
+
+ // Set up variables from the tree for testing.
+ AXNode* paragraph_node = GetRootNode()->children()[0];
AXNodePosition::SetTree(tree_.get());
- AXNode* root_node = GetRootNode();
+ AXNode* static_text_node1 = paragraph_node->children()[0];
+ AXNode* link_node = paragraph_node->children()[1];
+ AXNode* inline_text_node1 = static_text_node1->children()[0];
+ AXNode* static_text_node2 = link_node->children()[0];
+ AXNode* inline_text_node2 = static_text_node2->children()[0];
+
+ ComPtr<IRawElementProviderSimple> link_node_raw =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(link_node);
+ ComPtr<IRawElementProviderSimple> inline_text_node_raw1 =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node1);
+ ComPtr<IRawElementProviderSimple> inline_text_node_raw2 =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(inline_text_node2);
+
+ // Test GetEnclosingElement for the two leaves text nodes. The enclosing
+ // element of the first one should be itself and the enclosing element for the
+ // text node that is grandchild of the link node should return the link node.
+ ComPtr<ITextProvider> text_provider;
+ EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw1->GetPatternProvider(
+ UIA_TextPatternId, &text_provider));
- // Test GetEnclosingElement for each child text node.
- for (auto* child : root_node->children()) {
- ComPtr<IRawElementProviderSimple> text_node_raw =
- QueryInterfaceFromNode<IRawElementProviderSimple>(child);
+ ComPtr<ITextRangeProvider> text_range_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ text_provider->get_DocumentRange(&text_range_provider));
- ComPtr<ITextProvider> text_provider;
- EXPECT_HRESULT_SUCCEEDED(
- text_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+ ComPtr<IRawElementProviderSimple> enclosing_element;
+ EXPECT_HRESULT_SUCCEEDED(
+ text_range_provider->GetEnclosingElement(&enclosing_element));
+ EXPECT_EQ(inline_text_node_raw1.Get(), enclosing_element.Get());
- ComPtr<ITextRangeProvider> text_range_provider;
- EXPECT_HRESULT_SUCCEEDED(
- text_provider->get_DocumentRange(&text_range_provider));
+ EXPECT_HRESULT_SUCCEEDED(inline_text_node_raw2->GetPatternProvider(
+ UIA_TextPatternId, &text_provider));
- ComPtr<IRawElementProviderSimple> enclosing_element;
- EXPECT_HRESULT_SUCCEEDED(
- text_range_provider->GetEnclosingElement(&enclosing_element));
- EXPECT_EQ(text_node_raw.Get(), enclosing_element.Get());
- }
+ EXPECT_HRESULT_SUCCEEDED(
+ text_provider->get_DocumentRange(&text_range_provider));
+
+ EXPECT_HRESULT_SUCCEEDED(
+ text_range_provider->GetEnclosingElement(&enclosing_element));
+ EXPECT_EQ(link_node_raw.Get(), enclosing_element.Get());
}
TEST_F(AXPlatformNodeTextRangeProviderTest,
@@ -2973,32 +3066,46 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetChildren) {
// Set up ax tree with the following structure:
//
- // root______________________
- // | |
- // text_node1___ text_node2
+ // root
+ // |
+ // document(ignored)_________________________
+ // | | |
+ // text_node1___ text_node2 ignored_text
// | |
// text_node3 text_node4
ui::AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kRootWebArea;
+ ui::AXNodeData document_data;
+ document_data.id = 2;
+ document_data.role = ax::mojom::Role::kDocument;
+ document_data.AddState(ax::mojom::State::kIgnored);
+ root_data.child_ids.push_back(document_data.id);
+
ui::AXNodeData text_node1;
- text_node1.id = 2;
+ text_node1.id = 3;
text_node1.role = ax::mojom::Role::kStaticText;
- root_data.child_ids.push_back(text_node1.id);
+ document_data.child_ids.push_back(text_node1.id);
ui::AXNodeData text_node2;
- text_node2.id = 3;
+ text_node2.id = 4;
text_node2.role = ax::mojom::Role::kStaticText;
- root_data.child_ids.push_back(text_node2.id);
+ document_data.child_ids.push_back(text_node2.id);
+
+ ui::AXNodeData ignored_text;
+ ignored_text.id = 5;
+ ignored_text.role = ax::mojom::Role::kStaticText;
+ ignored_text.AddState(ax::mojom::State::kIgnored);
+ document_data.child_ids.push_back(ignored_text.id);
ui::AXNodeData text_node3;
- text_node3.id = 4;
+ text_node3.id = 6;
text_node3.role = ax::mojom::Role::kStaticText;
text_node1.child_ids.push_back(text_node3.id);
ui::AXNodeData text_node4;
- text_node4.id = 5;
+ text_node4.id = 7;
text_node4.role = ax::mojom::Role::kStaticText;
text_node1.child_ids.push_back(text_node4.id);
@@ -3009,23 +3116,25 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetChildren) {
update.has_tree_data = true;
update.root_id = root_data.id;
update.nodes.push_back(root_data);
+ update.nodes.push_back(document_data);
update.nodes.push_back(text_node1);
update.nodes.push_back(text_node2);
+ update.nodes.push_back(ignored_text);
update.nodes.push_back(text_node3);
update.nodes.push_back(text_node4);
Init(update);
// Set up variables from the tree for testing.
- AXNode* rootnode = GetRootNode();
+ AXNode* document_node = GetRootNode()->children()[0];
AXNodePosition::SetTree(tree_.get());
- AXNode* node1 = rootnode->children()[0];
- AXNode* node2 = rootnode->children()[1];
+ AXNode* node1 = document_node->children()[0];
+ AXNode* node2 = document_node->children()[1];
AXNode* node3 = node1->children()[0];
AXNode* node4 = node1->children()[1];
- ComPtr<IRawElementProviderSimple> root_node_raw =
- QueryInterfaceFromNode<IRawElementProviderSimple>(rootnode);
+ ComPtr<IRawElementProviderSimple> document_node_raw =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(document_node);
ComPtr<IRawElementProviderSimple> text_node_raw1 =
QueryInterfaceFromNode<IRawElementProviderSimple>(node1);
ComPtr<IRawElementProviderSimple> text_node_raw2 =
@@ -3081,7 +3190,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetChildren) {
// Test root_node - children should include the entire left subtree and
// the entire right subtree.
EXPECT_HRESULT_SUCCEEDED(
- root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+ document_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
EXPECT_HRESULT_SUCCEEDED(
text_provider->get_DocumentRange(&text_range_provider));
@@ -3121,6 +3230,12 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
heading_data.id = 3;
heading_data.role = ax::mojom::Role::kHeading;
heading_data.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel, 6);
+ heading_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+ 0xDEADBEEFU);
+ heading_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
+ heading_data.SetTextDirection(ax::mojom::TextDirection::kRtl);
+ heading_data.SetTextPosition(ax::mojom::TextPosition::kSuperscript);
+ heading_data.AddState(ax::mojom::State::kEditable);
heading_data.child_ids = {4};
ui::AXNodeData heading_text_data;
@@ -3139,6 +3254,10 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
ui::AXNodeData mark_data;
mark_data.id = 5;
mark_data.role = ax::mojom::Role::kMark;
+ mark_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+ 0xDEADBEEFU);
+ mark_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
+ mark_data.SetTextDirection(ax::mojom::TextDirection::kRtl);
mark_data.child_ids = {6};
ui::AXNodeData mark_text_data;
@@ -3154,6 +3273,9 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
list_data.id = 7;
list_data.role = ax::mojom::Role::kList;
list_data.child_ids = {8, 10};
+ list_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+ 0xDEADBEEFU);
+ list_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
ui::AXNodeData list_item_data;
list_item_data.id = 8;
@@ -3162,6 +3284,9 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
list_item_data.AddIntAttribute(
ax::mojom::IntAttribute::kListStyle,
static_cast<int>(ax::mojom::ListStyle::kOther));
+ list_item_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+ 0xDEADBEEFU);
+ list_item_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
ui::AXNodeData list_item_text_data;
list_item_text_data.id = 9;
@@ -3170,6 +3295,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
0xDEADBEEFU);
list_item_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
0xDEADC0DEU);
+ list_item_text_data.SetName("list item");
ui::AXNodeData list_item2_data;
list_item2_data.id = 10;
@@ -3178,6 +3304,9 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
list_item2_data.AddIntAttribute(
ax::mojom::IntAttribute::kListStyle,
static_cast<int>(ax::mojom::ListStyle::kDisc));
+ list_item2_data.AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor,
+ 0xDEADBEEFU);
+ list_item2_data.AddIntAttribute(ax::mojom::IntAttribute::kColor, 0xDEADC0DEU);
ui::AXNodeData list_item2_text_data;
list_item2_text_data.id = 11;
@@ -3186,6 +3315,7 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
ax::mojom::IntAttribute::kBackgroundColor, 0xDEADBEEFU);
list_item2_text_data.AddIntAttribute(ax::mojom::IntAttribute::kColor,
0xDEADC0DEU);
+ list_item2_text_data.SetName("list item 2");
ui::AXNodeData root_data;
root_data.id = 1;
@@ -3396,6 +3526,47 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
EXPECT_UIA_TEXTATTRIBUTE_MIXED(document_range_provider,
UIA_TextFlowDirectionsAttributeId);
expected_variant.Reset();
+
+ // Move the start endpoint back and forth one character to force such endpoint
+ // to be located at the end of the previous anchor, this shouldn't cause
+ // GetAttributeValue to include the previous anchor's attributes.
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
+ TextPatternRangeEndpoint_Start,
+ TextUnit_Character,
+ /*count*/ -1,
+ /*expected_text*/ L"tmarked text",
+ /*expected_count*/ -1);
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
+ TextPatternRangeEndpoint_Start,
+ TextUnit_Character,
+ /*count*/ 1,
+ /*expected_text*/ L"marked text",
+ /*expected_count*/ 1);
+ expected_variant.Set(false);
+ EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
+ UIA_IsSuperscriptAttributeId, expected_variant);
+ expected_variant.Reset();
+
+ // Same idea as above, but moving forth and back the end endpoint to force it
+ // to be located at the start of the next anchor.
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
+ TextPatternRangeEndpoint_End,
+ TextUnit_Character,
+ /*count*/ 1,
+ /*expected_text*/ L"marked textl",
+ /*expected_count*/ 1);
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(mark_text_range_provider,
+ TextPatternRangeEndpoint_End,
+ TextUnit_Character,
+ /*count*/ -1,
+ /*expected_text*/ L"marked text",
+ /*expected_count*/ -1);
+ expected_variant.Set(
+ static_cast<int32_t>(FlowDirections::FlowDirections_RightToLeft));
+ EXPECT_UIA_TEXTATTRIBUTE_EQ(mark_text_range_provider,
+ UIA_TextFlowDirectionsAttributeId,
+ expected_variant);
+ expected_variant.Reset();
}
TEST_F(AXPlatformNodeTextRangeProviderTest,
@@ -4194,4 +4365,140 @@ TEST_F(AXPlatformNodeTextRangeProviderTest, ElementNotAvailable) {
ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
text_range_provider->ScrollIntoView(bool_arg));
}
+
+TEST_F(AXPlatformNodeTextRangeProviderTest,
+ TestITextRangeProviderIgnoredNodes) {
+ // Parent Tree
+ // 1
+ // |
+ // 2(i)
+ // |________________________________
+ // | | | | | |
+ // 3 4 5 6 7(i) 8(i)
+ // | |________
+ // | | |
+ // 9(i) 10(i) 11
+ // | |____
+ // | | |
+ // 12 13 14
+
+ ui::AXTreeUpdate tree_update;
+ ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
+ tree_update.tree_data.tree_id = tree_id;
+ tree_update.has_tree_data = true;
+ tree_update.root_id = 1;
+ tree_update.nodes.resize(14);
+ tree_update.nodes[0].id = 1;
+ tree_update.nodes[0].child_ids = {2};
+ tree_update.nodes[0].role = ax::mojom::Role::kRootWebArea;
+
+ tree_update.nodes[1].id = 2;
+ tree_update.nodes[1].child_ids = {3, 4, 5, 6, 7, 8};
+ tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
+ tree_update.nodes[1].role = ax::mojom::Role::kDocument;
+
+ tree_update.nodes[2].id = 3;
+ tree_update.nodes[2].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[2].SetName(".3.");
+
+ tree_update.nodes[3].id = 4;
+ tree_update.nodes[3].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[3].SetName(".4.");
+
+ tree_update.nodes[4].id = 5;
+ tree_update.nodes[4].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[4].SetName(".5.");
+
+ tree_update.nodes[5].id = 6;
+ tree_update.nodes[5].role = ax::mojom::Role::kGenericContainer;
+ tree_update.nodes[5].child_ids = {9};
+
+ tree_update.nodes[6].id = 7;
+ tree_update.nodes[6].child_ids = {10, 11};
+ tree_update.nodes[6].AddState(ax::mojom::State::kIgnored);
+ tree_update.nodes[6].role = ax::mojom::Role::kGenericContainer;
+
+ tree_update.nodes[7].id = 8;
+ tree_update.nodes[7].AddState(ax::mojom::State::kIgnored);
+ tree_update.nodes[7].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[7].SetName(".8.");
+
+ tree_update.nodes[8].id = 9;
+ tree_update.nodes[8].child_ids = {12};
+ tree_update.nodes[8].AddState(ax::mojom::State::kIgnored);
+ tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
+
+ tree_update.nodes[9].id = 10;
+ tree_update.nodes[9].child_ids = {13, 14};
+ tree_update.nodes[9].AddState(ax::mojom::State::kIgnored);
+ tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
+
+ tree_update.nodes[10].id = 11;
+ tree_update.nodes[10].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[10].SetName(".11.");
+
+ tree_update.nodes[11].id = 12;
+ tree_update.nodes[11].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[11].AddState(ax::mojom::State::kIgnored);
+ tree_update.nodes[11].SetName(".12.");
+
+ tree_update.nodes[12].id = 13;
+ tree_update.nodes[12].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[12].SetName(".13.");
+
+ tree_update.nodes[13].id = 14;
+ tree_update.nodes[13].role = ax::mojom::Role::kStaticText;
+ tree_update.nodes[13].SetName(".14.");
+
+ Init(tree_update);
+ AXNodePosition::SetTree(tree_.get());
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 1),
+ GetNodeFromTree(tree_id, 1));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 2),
+ GetNodeFromTree(tree_id, 1));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 3),
+ GetNodeFromTree(tree_id, 3));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 4),
+ GetNodeFromTree(tree_id, 4));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 5),
+ GetNodeFromTree(tree_id, 5));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 8),
+ GetNodeFromTree(tree_id, 1));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 11),
+ GetNodeFromTree(tree_id, 11));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 12),
+ GetNodeFromTree(tree_id, 6));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 13),
+ GetNodeFromTree(tree_id, 13));
+ EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 14),
+ GetNodeFromTree(tree_id, 14));
+
+ // Test movement and GetText()
+ ComPtr<ITextRangeProvider> text_range_provider;
+ GetTextRangeProviderFromTextNode(text_range_provider,
+ GetNodeFromTree(tree_id, 1));
+
+ ASSERT_HRESULT_SUCCEEDED(
+ text_range_provider->ExpandToEnclosingUnit(TextUnit_Character));
+ EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L".");
+
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
+ text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
+ /*count*/ 2,
+ /*expected_text*/ L".3.",
+ /*expected_count*/ 2);
+
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
+ text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
+ /*count*/ 6,
+ /*expected_text*/ L".3..4..5.",
+ /*expected_count*/ 6);
+
+ EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
+ text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
+ /*count*/ 12,
+ /*expected_text*/ L".3..4..5..13..14..11.",
+ /*expected_count*/ 12);
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
index e8d77a72c58..9950894e9fb 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
@@ -67,36 +67,6 @@ AXNode* AXPlatformNodeTest::GetNodeFromTree(const ui::AXTreeID tree_id,
return nullptr;
}
-AXPlatformNodeDelegate* AXPlatformNodeTest::GetDelegate(
- const ui::AXTreeID tree_id,
- const int32_t node_id) const {
- AXNode* node = GetNodeFromTree(tree_id, node_id);
-
- if (node) {
- TestAXNodeWrapper* wrapper =
- TestAXNodeWrapper::GetOrCreate(tree_.get(), node);
-
- return wrapper;
- }
-
- return nullptr;
-}
-
-AXPlatformNodeDelegate* AXPlatformNodeTest::GetRootDelegate(
- const AXTreeID tree_id) const {
- if (GetTreeID() == tree_id) {
- AXNode* root_node = GetRootNode();
-
- if (root_node) {
- TestAXNodeWrapper* wrapper =
- TestAXNodeWrapper::GetOrCreate(tree_.get(), root_node);
- return wrapper;
- }
- }
-
- return nullptr;
-}
-
AXTreeID AXPlatformNodeTest::GetTreeID() const {
return tree_->data().tree_id;
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_unittest.h b/chromium/ui/accessibility/platform/ax_platform_node_unittest.h
index ecae3caf8bd..2fa8b660612 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_unittest.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_unittest.h
@@ -41,10 +41,6 @@ class AXPlatformNodeTest : public testing::Test, public AXTreeManager {
// AXTreeManager implementation.
AXNode* GetNodeFromTree(const ui::AXTreeID tree_id,
const int32_t node_id) const override;
- AXPlatformNodeDelegate* GetDelegate(const AXTreeID tree_id,
- const int32_t node_id) const override;
- AXPlatformNodeDelegate* GetRootDelegate(
- const AXTreeID tree_id) const override;
AXTreeID GetTreeID() const override;
AXTreeID GetParentTreeID() const override;
ui::AXNode* GetRootAsAXNode() const override;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.cc b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
index 7d6d3817bfa..6331949e5bd 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
@@ -5,6 +5,7 @@
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include <wrl/client.h>
+#include <wrl/implements.h>
#include <algorithm>
#include <map>
@@ -591,9 +592,24 @@ void AXPlatformNodeWin::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
// role of button.
auto* parent =
static_cast<AXPlatformNodeWin*>(FromNativeViewAccessible(GetParent()));
- if (MSAARole() == ROLE_SYSTEM_MENUITEM ||
- (parent && parent->MSAARole() == ROLE_SYSTEM_MENUPOPUP)) {
+ int role = MSAARole();
+ if (role == ROLE_SYSTEM_MENUITEM) {
event_type = ax::mojom::Event::kFocus;
+ } else if (role == ROLE_SYSTEM_LISTITEM) {
+ if (AXPlatformNodeBase* container = GetSelectionContainer()) {
+ const ui::AXNodeData& data = container->GetData();
+ if (data.role == ax::mojom::Role::kListBox &&
+ !data.HasState(ax::mojom::State::kMultiselectable) &&
+ GetDelegate()->GetFocus() == GetNativeViewAccessible()) {
+ event_type = ax::mojom::Event::kFocus;
+ }
+ }
+ } else if (parent) {
+ int parent_role = parent->MSAARole();
+ if (parent_role == ROLE_SYSTEM_MENUPOPUP ||
+ parent_role == ROLE_SYSTEM_LIST) {
+ event_type = ax::mojom::Event::kFocus;
+ }
}
}
@@ -636,6 +652,18 @@ int AXPlatformNodeWin::GetIndexInParent() {
LONG child_count = 0;
if (S_OK != parent_accessible->get_accChildCount(&child_count))
return -1;
+
+ // Ask the delegate for the index in parent, and return it if it's plausible.
+ //
+ // Delegates are allowed to not implement this (AXPlatformNodeDelegateBase
+ // returns -1). Also, delegates may not know the correct answer if this
+ // node is the root of a tree that's embedded in another tree, in which
+ // case the delegate should return -1 and we'll compute it.
+ int index_in_parent = GetDelegate()->GetIndexInParent();
+ if (index_in_parent >= 0 && index_in_parent < child_count)
+ return index_in_parent;
+
+ // Otherwise, search the parent's children.
for (LONG index = 1; index <= child_count; ++index) {
base::win::ScopedVariant childid_index(index);
Microsoft::WRL::ComPtr<IDispatch> child_dispatch;
@@ -1169,16 +1197,14 @@ IFACEMETHODIMP AXPlatformNodeWin::get_accSelection(VARIANT* selected) {
// Multiple items are selected.
LONG selected_count = static_cast<LONG>(selected_nodes.size());
- auto* enum_variant = new base::win::EnumVariant(selected_count);
- enum_variant->AddRef();
+ Microsoft::WRL::ComPtr<base::win::EnumVariant> enum_variant =
+ Microsoft::WRL::Make<base::win::EnumVariant>(selected_count);
for (LONG i = 0; i < selected_count; ++i) {
enum_variant->ItemAt(i)->vt = VT_DISPATCH;
enum_variant->ItemAt(i)->pdispVal = selected_nodes[i].Detach();
}
selected->vt = VT_UNKNOWN;
- HRESULT hr = enum_variant->QueryInterface(IID_PPV_ARGS(&V_UNKNOWN(selected)));
- enum_variant->Release();
- return hr;
+ return enum_variant.CopyTo(IID_PPV_ARGS(&V_UNKNOWN(selected)));
}
IFACEMETHODIMP AXPlatformNodeWin::accSelect(LONG flagsSelect, VARIANT var_id) {
@@ -1750,10 +1776,20 @@ IFACEMETHODIMP AXPlatformNodeWin::get_ExpandCollapseState(
WIN_ACCESSIBILITY_API_HISTOGRAM(
UMA_API_EXPANDCOLLAPSE_GET_EXPANDCOLLAPSESTATE);
UIA_VALIDATE_CALL_1_ARG(result);
+
const AXNodeData& data = GetData();
- if (data.HasState(ax::mojom::State::kExpanded)) {
+ const bool is_menu_button = data.GetHasPopup() == ax::mojom::HasPopup::kMenu;
+ const bool is_expanded_menu_button =
+ is_menu_button &&
+ data.GetCheckedState() == ax::mojom::CheckedState::kTrue;
+ const bool is_collapsed_menu_button =
+ is_menu_button &&
+ data.GetCheckedState() != ax::mojom::CheckedState::kTrue;
+
+ if (data.HasState(ax::mojom::State::kExpanded) || is_expanded_menu_button) {
*result = ExpandCollapseState_Expanded;
- } else if (data.HasState(ax::mojom::State::kCollapsed)) {
+ } else if (data.HasState(ax::mojom::State::kCollapsed) ||
+ is_collapsed_menu_button) {
*result = ExpandCollapseState_Collapsed;
} else {
*result = ExpandCollapseState_LeafNode;
@@ -2025,9 +2061,6 @@ IFACEMETHODIMP AXPlatformNodeWin::get_VerticalViewSize(double* result) {
HRESULT AXPlatformNodeWin::ISelectionItemProviderSetSelected(bool selected) {
UIA_VALIDATE_CALL();
- if (!IsSelectionItemSupported())
- return UIA_E_INVALIDOPERATION;
-
int restriction;
if (GetIntAttribute(ax::mojom::IntAttribute::kRestriction, &restriction)) {
if (restriction == static_cast<int>(ax::mojom::Restriction::kDisabled))
@@ -2065,38 +2098,23 @@ IFACEMETHODIMP AXPlatformNodeWin::Select() {
IFACEMETHODIMP AXPlatformNodeWin::get_IsSelected(BOOL* result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_SELECTIONITEM_GET_ISSELECTED);
UIA_VALIDATE_CALL_1_ARG(result);
-
- if (!IsSelectionItemSupported())
- return UIA_E_INVALIDOPERATION;
-
// https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table
- // SelectionItem.IsSelected is exposed when aria-checked is True or False,
- // for 'radio' and 'menuitemradio' roles
+ // SelectionItem.IsSelected is set according to the True or False value of
+ // aria-checked for 'radio' and 'menuitemradio' roles.
if (GetData().role == ax::mojom::Role::kRadioButton ||
GetData().role == ax::mojom::Role::kMenuItemRadio) {
- const auto checked_state = GetData().GetCheckedState();
- switch (checked_state) {
- case ax::mojom::CheckedState::kTrue:
- case ax::mojom::CheckedState::kFalse: {
- *result = (checked_state == ax::mojom::CheckedState::kTrue);
- return S_OK;
- }
- case ax::mojom::CheckedState::kMixed:
- case ax::mojom::CheckedState::kNone: {
- return UIA_E_INVALIDOPERATION;
- }
- }
+ *result = (GetData().GetCheckedState() == ax::mojom::CheckedState::kTrue);
+ return S_OK;
}
// https://www.w3.org/TR/wai-aria-1.1/#aria-selected
- // Elements are not selectable when aria-selected is undefined
+ // SelectionItem.IsSelected is set according to the True or False value of
+ // aria-selected.
bool is_selected;
- if (GetBoolAttribute(ax::mojom::BoolAttribute::kSelected, &is_selected)) {
+ if (GetBoolAttribute(ax::mojom::BoolAttribute::kSelected, &is_selected))
*result = is_selected;
- return S_OK;
- }
- return UIA_E_INVALIDOPERATION;
+ return S_OK;
}
IFACEMETHODIMP AXPlatformNodeWin::get_SelectionContainer(
@@ -3470,10 +3488,41 @@ IFACEMETHODIMP AXPlatformNodeWin::get_offsetAtPoint(
LONG y,
enum IA2CoordinateType coord_type,
LONG* offset) {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_OFFSET_AT_POINT);
COM_OBJECT_VALIDATE_1_ARG(offset);
- // We don't support this method, but we have to return something
- // rather than E_NOTIMPL or screen readers will complain.
- *offset = 0;
+
+ *offset = -1;
+
+ if (coord_type == IA2CoordinateType::IA2_COORDTYPE_PARENT_RELATIVE) {
+ // We don't support when the IA2 coordinate type is parent relative, but
+ // we have to return something rather than E_NOTIMPL or screen readers
+ // will complain.
+ NOTIMPLEMENTED_LOG_ONCE() << "See http://crbug.com/1010726";
+ return S_FALSE;
+ }
+
+ // We currently only handle IA2 screen relative coord type.
+ DCHECK_EQ(coord_type, IA2_COORDTYPE_SCREEN_RELATIVE);
+
+ const AXPlatformNodeWin* hit_child = static_cast<AXPlatformNodeWin*>(
+ FromNativeViewAccessible(GetDelegate()->HitTestSync(x, y)));
+
+ if (!hit_child || !hit_child->IsTextOnlyObject()) {
+ return S_FALSE;
+ }
+
+ for (int i = 0, text_length = hit_child->GetInnerText().length();
+ i < text_length; ++i) {
+ gfx::Rect char_bounds =
+ hit_child->GetDelegate()->GetInnerTextRangeBoundsRect(
+ i, i + 1, AXCoordinateSystem::kScreen,
+ AXClippingBehavior::kUnclipped);
+ if (char_bounds.Contains(x, y)) {
+ *offset = i;
+ break;
+ }
+ }
+
return S_OK;
}
@@ -3731,7 +3780,7 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PROPERTY_VALUE);
constexpr LONG kFirstKnownUiaPropertyId = UIA_RuntimeIdPropertyId;
- constexpr LONG kLastKnownUiaPropertyId = UIA_HeadingLevelPropertyId;
+ constexpr LONG kLastKnownUiaPropertyId = UIA_IsDialogPropertyId;
if (property_id >= kFirstKnownUiaPropertyId &&
property_id <= kLastKnownUiaPropertyId) {
base::UmaHistogramSparse("Accessibility.WinAPIs.GetPropertyValue",
@@ -4475,6 +4524,13 @@ int AXPlatformNodeWin::MSAARole() {
case ax::mojom::Role::kAnchor:
return ROLE_SYSTEM_LINK;
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
+ return ROLE_SYSTEM_GROUPING;
+
case ax::mojom::Role::kApplication:
return ROLE_SYSTEM_APPLICATION;
@@ -4485,6 +4541,7 @@ int AXPlatformNodeWin::MSAARole() {
return ROLE_SYSTEM_GROUPING;
case ax::mojom::Role::kBanner:
+ case ax::mojom::Role::kHeader:
return ROLE_SYSTEM_GROUPING;
case ax::mojom::Role::kBlockquote:
@@ -4636,6 +4693,10 @@ int AXPlatformNodeWin::MSAARole() {
case ax::mojom::Role::kFeed:
return ROLE_SYSTEM_GROUPING;
+ case ax::mojom::Role::kFooterAsNonLandmark:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
+ return ROLE_SYSTEM_GROUPING;
+
case ax::mojom::Role::kForm:
return ROLE_SYSTEM_GROUPING;
@@ -4785,19 +4846,8 @@ int AXPlatformNodeWin::MSAARole() {
case ax::mojom::Role::kRadioGroup:
return ROLE_SYSTEM_GROUPING;
- case ax::mojom::Role::kRegion: {
- std::string html_tag =
- GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
- if (html_tag == "section" &&
- GetData()
- .GetString16Attribute(ax::mojom::StringAttribute::kName)
- .empty()) {
- // Do not use ARIA mapping for nameless <section>.
- return ROLE_SYSTEM_GROUPING;
- }
- // Use ARIA mapping.
+ case ax::mojom::Role::kRegion:
return ROLE_SYSTEM_PANE;
- }
case ax::mojom::Role::kRow: {
// Role changes depending on whether row is inside a treegrid
@@ -4811,6 +4861,17 @@ int AXPlatformNodeWin::MSAARole() {
case ax::mojom::Role::kRuby:
return ROLE_SYSTEM_TEXT;
+ case ax::mojom::Role::kSection: {
+ if (GetData()
+ .GetString16Attribute(ax::mojom::StringAttribute::kName)
+ .empty()) {
+ // Do not use ARIA mapping for nameless <section>.
+ return ROLE_SYSTEM_GROUPING;
+ }
+ // Use ARIA mapping.
+ return ROLE_SYSTEM_PANE;
+ }
+
case ax::mojom::Role::kScrollBar:
return ROLE_SYSTEM_SCROLLBAR;
@@ -4832,7 +4893,7 @@ int AXPlatformNodeWin::MSAARole() {
case ax::mojom::Role::kSwitch:
return ROLE_SYSTEM_CHECKBUTTON;
- case ax::mojom::Role::kAnnotation:
+ case ax::mojom::Role::kRubyAnnotation:
case ax::mojom::Role::kListMarker:
case ax::mojom::Role::kStaticText:
return ROLE_SYSTEM_STATICTEXT;
@@ -5056,7 +5117,14 @@ int32_t AXPlatformNodeWin::ComputeIA2Role() {
int32_t ia2_role = 0;
switch (GetData().role) {
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
+ return IA2_ROLE_SECTION;
case ax::mojom::Role::kBanner:
+ case ax::mojom::Role::kHeader:
// CORE-AAM recommends LANDMARK instead of HEADER.
ia2_role = IA2_ROLE_LANDMARK;
break;
@@ -5148,6 +5216,10 @@ int32_t AXPlatformNodeWin::ComputeIA2Role() {
case ax::mojom::Role::kFigcaption:
ia2_role = IA2_ROLE_CAPTION;
break;
+ case ax::mojom::Role::kFooterAsNonLandmark:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
+ ia2_role = IA2_ROLE_SECTION;
+ break;
case ax::mojom::Role::kForm:
ia2_role = IA2_ROLE_FORM;
break;
@@ -5197,11 +5269,17 @@ int32_t AXPlatformNodeWin::ComputeIA2Role() {
case ax::mojom::Role::kPre:
ia2_role = IA2_ROLE_PARAGRAPH;
break;
- case ax::mojom::Role::kRegion: {
- std::string html_tag =
- GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
- if (html_tag == "section" &&
- GetData()
+ case ax::mojom::Role::kRegion:
+ ia2_role = IA2_ROLE_LANDMARK;
+ break;
+ case ax::mojom::Role::kRuby:
+ ia2_role = IA2_ROLE_TEXT_FRAME;
+ break;
+ case ax::mojom::Role::kSearch:
+ ia2_role = IA2_ROLE_LANDMARK;
+ break;
+ case ax::mojom::Role::kSection: {
+ if (GetData()
.GetString16Attribute(ax::mojom::StringAttribute::kName)
.empty()) {
// Do not use ARIA mapping for nameless <section>.
@@ -5212,12 +5290,6 @@ int32_t AXPlatformNodeWin::ComputeIA2Role() {
}
break;
}
- case ax::mojom::Role::kRuby:
- ia2_role = IA2_ROLE_TEXT_FRAME;
- break;
- case ax::mojom::Role::kSearch:
- ia2_role = IA2_ROLE_LANDMARK;
- break;
case ax::mojom::Role::kSwitch:
ia2_role = IA2_ROLE_TOGGLE_BUTTON;
break;
@@ -5264,6 +5336,13 @@ base::string16 AXPlatformNodeWin::UIAAriaRole() {
case ax::mojom::Role::kAnchor:
return L"link";
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
+ return L"group";
+
case ax::mojom::Role::kApplication:
return L"application";
@@ -5274,6 +5353,7 @@ base::string16 AXPlatformNodeWin::UIAAriaRole() {
return L"group";
case ax::mojom::Role::kBanner:
+ case ax::mojom::Role::kHeader:
return L"banner";
case ax::mojom::Role::kBlockquote:
@@ -5425,6 +5505,10 @@ base::string16 AXPlatformNodeWin::UIAAriaRole() {
case ax::mojom::Role::kFigure:
return L"group";
+ case ax::mojom::Role::kFooterAsNonLandmark:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
+ return L"group";
+
case ax::mojom::Role::kForm:
return L"form";
@@ -5577,19 +5661,8 @@ base::string16 AXPlatformNodeWin::UIAAriaRole() {
case ax::mojom::Role::kRadioGroup:
return L"radiogroup";
- case ax::mojom::Role::kRegion: {
- std::string html_tag =
- GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
- if (html_tag == "section" &&
- GetData()
- .GetString16Attribute(ax::mojom::StringAttribute::kName)
- .empty()) {
- // Do not use ARIA mapping for nameless <section>.
- return L"group";
- }
- // Use ARIA mapping.
+ case ax::mojom::Role::kRegion:
return L"region";
- }
case ax::mojom::Role::kRow: {
// Role changes depending on whether row is inside a treegrid
@@ -5603,6 +5676,17 @@ base::string16 AXPlatformNodeWin::UIAAriaRole() {
case ax::mojom::Role::kRuby:
return L"region";
+ case ax::mojom::Role::kSection: {
+ if (GetData()
+ .GetString16Attribute(ax::mojom::StringAttribute::kName)
+ .empty()) {
+ // Do not use ARIA mapping for nameless <section>.
+ return L"group";
+ }
+ // Use ARIA mapping.
+ return L"region";
+ }
+
case ax::mojom::Role::kScrollBar:
return L"scrollbar";
@@ -5624,7 +5708,7 @@ base::string16 AXPlatformNodeWin::UIAAriaRole() {
case ax::mojom::Role::kSwitch:
return L"checkbox";
- case ax::mojom::Role::kAnnotation:
+ case ax::mojom::Role::kRubyAnnotation:
case ax::mojom::Role::kListMarker:
case ax::mojom::Role::kStaticText:
return L"description";
@@ -5905,6 +5989,13 @@ LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
case ax::mojom::Role::kAnchor:
return UIA_HyperlinkControlTypeId;
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
+ return ROLE_SYSTEM_GROUPING;
+
case ax::mojom::Role::kApplication:
return UIA_PaneControlTypeId;
@@ -5915,6 +6006,7 @@ LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
return UIA_GroupControlTypeId;
case ax::mojom::Role::kBanner:
+ case ax::mojom::Role::kHeader:
return UIA_GroupControlTypeId;
case ax::mojom::Role::kBlockquote:
@@ -6066,6 +6158,10 @@ LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
case ax::mojom::Role::kFigure:
return UIA_GroupControlTypeId;
+ case ax::mojom::Role::kFooterAsNonLandmark:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
+ return UIA_GroupControlTypeId;
+
case ax::mojom::Role::kForm:
return UIA_GroupControlTypeId;
@@ -6215,19 +6311,8 @@ LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
case ax::mojom::Role::kRadioGroup:
return UIA_GroupControlTypeId;
- case ax::mojom::Role::kRegion: {
- std::string html_tag =
- GetData().GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
- if (html_tag == "section" &&
- GetData()
- .GetString16Attribute(ax::mojom::StringAttribute::kName)
- .empty()) {
- // Do not use ARIA mapping for nameless <section>.
- return UIA_GroupControlTypeId;
- }
- // Use ARIA mapping.
- return UIA_PaneControlTypeId;
- }
+ case ax::mojom::Role::kRegion:
+ return UIA_GroupControlTypeId;
case ax::mojom::Role::kRow: {
// Role changes depending on whether row is inside a treegrid
@@ -6242,6 +6327,9 @@ LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
case ax::mojom::Role::kRuby:
return UIA_PaneControlTypeId;
+ case ax::mojom::Role::kSection:
+ return UIA_GroupControlTypeId;
+
case ax::mojom::Role::kScrollBar:
return UIA_ScrollBarControlTypeId;
@@ -6263,7 +6351,7 @@ LONG AXPlatformNodeWin::ComputeUIAControlType() { // NOLINT(runtime/int)
case ax::mojom::Role::kSwitch:
return UIA_CheckBoxControlTypeId;
- case ax::mojom::Role::kAnnotation:
+ case ax::mojom::Role::kRubyAnnotation:
case ax::mojom::Role::kListMarker:
case ax::mojom::Role::kStaticText:
return UIA_TextControlTypeId;
@@ -6414,6 +6502,7 @@ base::Optional<LONG> AXPlatformNodeWin::ComputeUIALandmarkType() const {
case ax::mojom::Role::kComplementary:
case ax::mojom::Role::kContentInfo:
case ax::mojom::Role::kFooter:
+ case ax::mojom::Role::kHeader:
return UIA_CustomLandmarkTypeId;
case ax::mojom::Role::kForm:
@@ -6429,6 +6518,7 @@ base::Optional<LONG> AXPlatformNodeWin::ComputeUIALandmarkType() const {
return UIA_SearchLandmarkTypeId;
case ax::mojom::Role::kRegion:
+ case ax::mojom::Role::kSection:
if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
return UIA_CustomLandmarkTypeId;
FALLTHROUGH;
@@ -6438,6 +6528,34 @@ base::Optional<LONG> AXPlatformNodeWin::ComputeUIALandmarkType() const {
}
}
+bool AXPlatformNodeWin::IsInaccessibleDueToAncestor() const {
+ AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
+ AXPlatformNode::FromNativeViewAccessible(GetParent()));
+ while (parent) {
+ if (parent->ShouldHideChildren())
+ return true;
+ parent = static_cast<AXPlatformNodeWin*>(
+ FromNativeViewAccessible(parent->GetParent()));
+ }
+ return false;
+}
+
+bool AXPlatformNodeWin::ShouldHideChildren() const {
+ switch (GetData().role) {
+ case ax::mojom::Role::kButton:
+ case ax::mojom::Role::kImage:
+ case ax::mojom::Role::kGraphicsSymbol:
+ case ax::mojom::Role::kLink:
+ case ax::mojom::Role::kMath:
+ case ax::mojom::Role::kProgressIndicator:
+ case ax::mojom::Role::kScrollBar:
+ case ax::mojom::Role::kSlider:
+ return true;
+ default:
+ return false;
+ }
+}
+
base::string16 AXPlatformNodeWin::GetValue() const {
base::string16 value = AXPlatformNodeBase::GetValue();
@@ -7167,4 +7285,40 @@ void AXPlatformNodeWin::FireLiveRegionChangeRecursive() {
}
}
+AXPlatformNodeWin* AXPlatformNodeWin::GetLowestAccessibleElement() {
+ if (!IsInaccessibleDueToAncestor())
+ return this;
+
+ AXPlatformNodeWin* parent = static_cast<AXPlatformNodeWin*>(
+ AXPlatformNode::FromNativeViewAccessible(GetParent()));
+ while (parent) {
+ if (parent->ShouldHideChildren())
+ return parent;
+ parent = static_cast<AXPlatformNodeWin*>(
+ AXPlatformNode::FromNativeViewAccessible(parent->GetParent()));
+ }
+
+ NOTREACHED();
+ return nullptr;
+}
+
+void AXPlatformNodeWin::SanitizeTextAttributeValue(const std::string& input,
+ std::string* output) const {
+ SanitizeStringAttributeForIA2(input, output);
+}
+
+// static
+void AXPlatformNodeWin::SanitizeStringAttributeForIA2(const std::string& input,
+ std::string* output) {
+ DCHECK(output);
+ // According to the IA2 Spec, these characters need to be escaped with a
+ // backslash: backslash, colon, comma, equals and semicolon.
+ // Note that backslash must be replaced first.
+ base::ReplaceChars(input, "\\", "\\\\", output);
+ base::ReplaceChars(*output, ":", "\\:", output);
+ base::ReplaceChars(*output, ",", "\\,", output);
+ base::ReplaceChars(*output, "=", "\\=", output);
+ base::ReplaceChars(*output, ";", "\\;", output);
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.h b/chromium/ui/accessibility/platform/ax_platform_node_win.h
index 0b0189c75ce..92ce5752ad3 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.h
@@ -18,6 +18,7 @@
#include <vector>
#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/observer_list.h"
#include "base/win/atl.h"
@@ -1031,6 +1032,9 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Helper to recursively find live-regions and fire a change event on them
void FireLiveRegionChangeRecursive();
+ // Returns the parent node that makes this node inaccessible.
+ AXPlatformNodeWin* GetLowestAccessibleElement();
+
// Convert a mojo event to an MSAA event. Exposed for testing.
static base::Optional<DWORD> MojoEventToMSAAEvent(ax::mojom::Event event);
@@ -1070,6 +1074,10 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
base::Optional<LONG> ComputeUIALandmarkType() const;
+ bool IsInaccessibleDueToAncestor() const;
+
+ bool ShouldHideChildren() const;
+
// AXPlatformNodeBase overrides.
void Dispose() override;
@@ -1102,6 +1110,17 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
const char* value,
PlatformAttributeList* attributes) override;
+ // Escape special characters as specified by the IA2 spec.
+ void SanitizeTextAttributeValue(const std::string& input,
+ std::string* output) const override;
+
+ // Escapes characters in string attributes as required by the IA2 Spec.
+ // It's okay for input to be the same as output.
+ static void SanitizeStringAttributeForIA2(const std::string& input,
+ std::string* output);
+ FRIEND_TEST_ALL_PREFIXES(AXPlatformNodeWinTest,
+ TestSanitizeStringAttributeForIA2);
+
private:
bool IsWebAreaForPresentationalIframe();
bool ShouldNodeHaveFocusableState(const AXNodeData& data) const;
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 0540c6687ec..d6078808784 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -9,6 +9,7 @@
#include <memory>
+#include "base/auto_reset.h"
#include "base/stl_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/win/atl.h"
@@ -18,6 +19,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/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
@@ -444,7 +446,7 @@ TEST_F(AXPlatformNodeWinTest, TestIAccessibleDetachedObject) {
TEST_F(AXPlatformNodeWinTest, TestIAccessibleHitTest) {
AXNodeData root;
root.id = 1;
- root.relative_bounds.bounds = gfx::RectF(0, 0, 30, 30);
+ root.relative_bounds.bounds = gfx::RectF(0, 0, 40, 40);
AXNodeData node1;
node1.id = 2;
@@ -454,7 +456,7 @@ TEST_F(AXPlatformNodeWinTest, TestIAccessibleHitTest) {
AXNodeData node2;
node2.id = 3;
- node2.relative_bounds.bounds = gfx::RectF(20, 20, 10, 10);
+ node2.relative_bounds.bounds = gfx::RectF(20, 20, 20, 20);
node2.SetName("Name2");
root.child_ids.push_back(node2.id);
@@ -462,17 +464,23 @@ TEST_F(AXPlatformNodeWinTest, TestIAccessibleHitTest) {
ComPtr<IAccessible> root_obj(GetRootIAccessible());
- ScopedVariant obj;
+ // This is way outside of the root node.
+ ScopedVariant obj_1;
+ EXPECT_EQ(S_FALSE, root_obj->accHitTest(50, 50, obj_1.Receive()));
+ EXPECT_EQ(VT_EMPTY, obj_1.type());
- // This is way outside of the root node
- EXPECT_EQ(S_FALSE, root_obj->accHitTest(50, 50, obj.Receive()));
- EXPECT_EQ(VT_EMPTY, obj.type());
+ // This is directly on node 1.
+ EXPECT_EQ(S_OK, root_obj->accHitTest(5, 5, obj_1.Receive()));
+ ASSERT_NE(nullptr, obj_1.ptr());
+ CheckVariantHasName(obj_1, L"Name1");
- // this is directly on node 1.
- EXPECT_EQ(S_OK, root_obj->accHitTest(5, 5, obj.Receive()));
- ASSERT_NE(nullptr, obj.ptr());
-
- CheckVariantHasName(obj, L"Name1");
+ // This is directly on node 2 with a scale factor of 1.5.
+ ScopedVariant obj_2;
+ std::unique_ptr<base::AutoReset<float>> scale_factor_reset =
+ TestAXNodeWrapper::SetScaleFactor(1.5);
+ EXPECT_EQ(S_OK, root_obj->accHitTest(38, 38, obj_2.Receive()));
+ ASSERT_NE(nullptr, obj_2.ptr());
+ CheckVariantHasName(obj_2, L"Name2");
}
TEST_F(AXPlatformNodeWinTest, TestIAccessibleName) {
@@ -2870,6 +2878,154 @@ TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextGetNCharacters) {
EXPECT_EQ(4, count);
}
+TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextGetOffsetAtPoint) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kWebArea;
+ root.relative_bounds.bounds = gfx::RectF(0, 0, 300, 200);
+ root.child_ids = {2, 3};
+
+ AXNodeData button;
+ button.id = 2;
+ button.role = ax::mojom::Role::kButton;
+ button.SetName("button");
+ button.relative_bounds.bounds = gfx::RectF(20, 0, 10, 10);
+
+ AXNodeData static_text1;
+ static_text1.id = 3;
+ static_text1.role = ax::mojom::Role::kStaticText;
+ static_text1.SetName("line 1");
+ static_text1.relative_bounds.bounds = gfx::RectF(0, 20, 30, 10);
+ static_text1.child_ids = {4};
+
+ AXNodeData inline_box1;
+ inline_box1.id = 4;
+ inline_box1.role = ax::mojom::Role::kInlineTextBox;
+ inline_box1.SetName("line 1");
+ inline_box1.relative_bounds.bounds = gfx::RectF(0, 20, 30, 10);
+ std::vector<int32_t> character_offsets1;
+ // The width of each character is 5px, height is 10px.
+ character_offsets1.push_back(5); // "l" {0, 20, 5x10}
+ character_offsets1.push_back(10); // "i" {5, 20, 5x10}
+ character_offsets1.push_back(15); // "n" {10, 20, 5x10}
+ character_offsets1.push_back(20); // "e" {15, 20, 5x10}
+ character_offsets1.push_back(25); // " " {20, 20, 5x10}
+ character_offsets1.push_back(30); // "1" {25, 20, 5x10}
+
+ inline_box1.AddIntListAttribute(
+ ax::mojom::IntListAttribute::kCharacterOffsets, character_offsets1);
+ inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
+ std::vector<int32_t>{0, 5});
+ inline_box1.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
+ std::vector<int32_t>{4, 6});
+
+ Init(root, button, static_text1, inline_box1);
+
+ LONG offset_result;
+ ComPtr<IAccessible> root_iaccessible(IAccessibleFromNode(GetRootNode()));
+ ASSERT_NE(nullptr, root_iaccessible.Get());
+
+ ComPtr<IAccessibleText> root_text;
+ root_iaccessible.As(&root_text);
+ ASSERT_NE(nullptr, root_text.Get());
+
+ // Test point(0, 0) with coordinate parent relative, which is not supported.
+ // Expected result: S_FALSE.
+ EXPECT_EQ(S_FALSE, root_text->get_offsetAtPoint(
+ 0, 0, IA2CoordinateType::IA2_COORDTYPE_PARENT_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(-1, offset_result);
+
+ // Test point(0, 0) retrieved from IAccessibleText of the root web area is
+ // outside of any text. Expected result: S_FALSE.
+ EXPECT_EQ(S_FALSE, root_text->get_offsetAtPoint(
+ 0, 0, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(-1, offset_result);
+
+ // Test point(25, 5) retrieved from IAccessibleText of the root web area is
+ // on button, and outside of any text. Expected result: S_FALSE.
+ EXPECT_EQ(S_FALSE,
+ root_text->get_offsetAtPoint(
+ 25, 5, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(-1, offset_result);
+
+ // Test point(0, 20) retrieved from IAccessibleText of the root web area is
+ // on "line 1", character="l". Expected result: S_OK, offset=0.
+ EXPECT_EQ(S_OK, root_text->get_offsetAtPoint(
+ 0, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(0, offset_result);
+
+ AXNode* static_text1_node = GetRootNode()->children()[1];
+ ComPtr<IAccessible> text1_iaccessible(IAccessibleFromNode(static_text1_node));
+ ASSERT_NE(nullptr, text1_iaccessible.Get());
+
+ ComPtr<IAccessibleText> text1;
+ text1_iaccessible.As(&text1);
+ ASSERT_NE(nullptr, text1.Get());
+
+ // "l" 4 points of bounds {(0, 20), (5, 20), (0, 30), (5, 30)}
+ // "i" 4 points of bounds {(5, 20), (10, 20), (5, 30), (10, 30)}
+ // "n" 4 points of bounds {(10, 20), (15, 20), (10, 30), (15, 30)}
+ // "e" 4 points of bounds {(15, 20), (20, 20), (15, 30), (20, 30)}
+ // " " 4 points of bounds {(20, 20), (25, 20), (20, 30), (25, 30)}
+ // "1" 4 points of bounds {(25, 20), (30, 20), (25, 30), (30, 30)}
+
+ // Test point(0, 0) outside of any character bounds and text.
+ EXPECT_EQ(S_FALSE, text1->get_offsetAtPoint(
+ 0, 0, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(-1, offset_result);
+
+ // Test point(30, 30) outside of any character bounds but on the text.
+ EXPECT_EQ(S_OK, text1->get_offsetAtPoint(
+ 30, 30, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(-1, offset_result);
+
+ // Test point(0, 20) inside bounds of "l", text offset=0
+ // character bounds={(0, 20), (5, 20), (0, 30), (5, 30)}
+ EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
+ 0, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE, &offset_result));
+ EXPECT_EQ(0, offset_result);
+
+ // Test point(9, 20) inside bounds of "i", text offset=1
+ // character bounds={(5, 20), (10, 20), (5, 30), (10, 30)}
+ EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
+ 9, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE, &offset_result));
+ EXPECT_EQ(1, offset_result);
+
+ // Test point(10, 30) inside bounds of "n", text offset=2
+ // character bounds={(10, 20), (15, 20), (10, 30), (15, 30)}
+ EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
+ 10, 29, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(2, offset_result);
+
+ // Test point(19, 29) inside bounds of "e", text offset=3
+ // character bounds={(15, 20), (20, 20), (15, 30), (20, 30)
+ EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
+ 19, 29, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(3, offset_result);
+
+ // Test point(23, 25) inside bounds of " ", text offset=4
+ // character bounds={(20, 20), (25, 20), (20, 30), (25, 30)}
+ EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
+ 23, 25, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(4, offset_result);
+
+ // Test point(25, 20) inside bounds of "1", text offset=5
+ // character bounds={(25, 20), (30, 20), (25, 30), (30, 30)}
+ EXPECT_HRESULT_SUCCEEDED(text1->get_offsetAtPoint(
+ 25, 20, IA2CoordinateType::IA2_COORDTYPE_SCREEN_RELATIVE,
+ &offset_result));
+ EXPECT_EQ(5, offset_result);
+}
+
TEST_F(AXPlatformNodeWinTest, TestIAccessibleTextTextFieldRemoveSelection) {
Init(BuildTextFieldWithSelectionRange(1, 2));
@@ -5185,16 +5341,25 @@ TEST_F(AXPlatformNodeWinTest, TestIExpandCollapsePatternProviderAction) {
ui::AXNodeData combo_box_grouping_expanded;
ui::AXNodeData combo_box_grouping_collapsed;
ui::AXNodeData combo_box_grouping_disabled;
+ ui::AXNodeData button_has_popup_menu;
+ ui::AXNodeData button_has_popup_menu_pressed;
+ ui::AXNodeData button_has_popup_true;
combo_box_grouping_has_popup.id = 2;
combo_box_grouping_expanded.id = 3;
combo_box_grouping_collapsed.id = 4;
combo_box_grouping_disabled.id = 5;
+ button_has_popup_menu.id = 6;
+ button_has_popup_menu_pressed.id = 7;
+ button_has_popup_true.id = 8;
root.child_ids.push_back(combo_box_grouping_has_popup.id);
root.child_ids.push_back(combo_box_grouping_expanded.id);
root.child_ids.push_back(combo_box_grouping_collapsed.id);
root.child_ids.push_back(combo_box_grouping_disabled.id);
+ root.child_ids.push_back(button_has_popup_menu.id);
+ root.child_ids.push_back(button_has_popup_menu_pressed.id);
+ root.child_ids.push_back(button_has_popup_true.id);
// combo_box_grouping HasPopup set to true, can collapse, can expand.
// state is ExpandCollapseState_LeafNode.
@@ -5216,9 +5381,23 @@ TEST_F(AXPlatformNodeWinTest, TestIExpandCollapsePatternProviderAction) {
combo_box_grouping_disabled.role = ax::mojom::Role::kComboBoxGrouping;
combo_box_grouping_disabled.SetRestriction(ax::mojom::Restriction::kDisabled);
+ // button_has_popup_menu HasPopup set to kMenu and is not STATE_PRESSED.
+ // state is ExpandCollapseState_Collapsed.
+ button_has_popup_menu.SetHasPopup(ax::mojom::HasPopup::kMenu);
+
+ // button_has_popup_menu_pressed HasPopup set to kMenu and is STATE_PRESSED.
+ // state is ExpandCollapseState_Expanded.
+ button_has_popup_menu_pressed.SetHasPopup(ax::mojom::HasPopup::kMenu);
+ button_has_popup_menu_pressed.SetCheckedState(ax::mojom::CheckedState::kTrue);
+
+ // button_has_popup_true HasPopup set to true but is not a menu.
+ // state is ExpandCollapseState_LeafNode.
+ button_has_popup_true.SetHasPopup(ax::mojom::HasPopup::kTrue);
+
Init(root, combo_box_grouping_has_popup, combo_box_grouping_disabled,
combo_box_grouping_expanded, combo_box_grouping_collapsed,
- combo_box_grouping_disabled);
+ combo_box_grouping_disabled, button_has_popup_menu,
+ button_has_popup_menu_pressed, button_has_popup_true);
// combo_box_grouping HasPopup set to true, can collapse, can expand.
// state is ExpandCollapseState_LeafNode.
@@ -5270,6 +5449,36 @@ TEST_F(AXPlatformNodeWinTest, TestIExpandCollapsePatternProviderAction) {
EXPECT_HRESULT_SUCCEEDED(
expandcollapse_provider->get_ExpandCollapseState(&state));
EXPECT_EQ(ExpandCollapseState_LeafNode, state);
+
+ // button_has_popup_menu HasPopup set to kMenu and is not STATE_PRESSED.
+ // state is ExpandCollapseState_Collapsed.
+ raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(4);
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_ExpandCollapsePatternId, &expandcollapse_provider));
+ EXPECT_NE(nullptr, expandcollapse_provider.Get());
+ EXPECT_HRESULT_SUCCEEDED(
+ expandcollapse_provider->get_ExpandCollapseState(&state));
+ EXPECT_EQ(ExpandCollapseState_Collapsed, state);
+
+ // button_has_popup_menu_pressed HasPopup set to kMenu and is STATE_PRESSED.
+ // state is ExpandCollapseState_Expanded.
+ raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(5);
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_ExpandCollapsePatternId, &expandcollapse_provider));
+ EXPECT_NE(nullptr, expandcollapse_provider.Get());
+ EXPECT_HRESULT_SUCCEEDED(
+ expandcollapse_provider->get_ExpandCollapseState(&state));
+ EXPECT_EQ(ExpandCollapseState_Expanded, state);
+
+ // button_has_popup_true HasPopup set to true but is not a menu.
+ // state is ExpandCollapseState_LeafNode.
+ raw_element_provider_simple = GetIRawElementProviderSimpleFromChildIndex(6);
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_ExpandCollapsePatternId, &expandcollapse_provider));
+ EXPECT_NE(nullptr, expandcollapse_provider.Get());
+ EXPECT_HRESULT_SUCCEEDED(
+ expandcollapse_provider->get_ExpandCollapseState(&state));
+ EXPECT_EQ(ExpandCollapseState_LeafNode, state);
}
TEST_F(AXPlatformNodeWinTest, TestIInvokeProviderInvoke) {
@@ -5320,15 +5529,12 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderNotSupported) {
Init(root);
- ComPtr<ISelectionItemProvider> provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(GetRootNode());
-
- BOOL selected;
-
- EXPECT_UIA_INVALIDOPERATION(provider->AddToSelection());
- EXPECT_UIA_INVALIDOPERATION(provider->RemoveFromSelection());
- EXPECT_UIA_INVALIDOPERATION(provider->Select());
- EXPECT_UIA_INVALIDOPERATION(provider->get_IsSelected(&selected));
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
+ GetRootIRawElementProviderSimple();
+ ComPtr<ISelectionItemProvider> selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &selection_item_provider));
+ ASSERT_EQ(nullptr, selection_item_provider.Get());
}
TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderDisabled) {
@@ -5341,15 +5547,19 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderDisabled) {
Init(root);
- ComPtr<ISelectionItemProvider> provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(GetRootNode());
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
+ GetRootIRawElementProviderSimple();
+ ComPtr<ISelectionItemProvider> selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &selection_item_provider));
+ ASSERT_NE(nullptr, selection_item_provider.Get());
BOOL selected;
- EXPECT_UIA_ELEMENTNOTENABLED(provider->AddToSelection());
- EXPECT_UIA_ELEMENTNOTENABLED(provider->RemoveFromSelection());
- EXPECT_UIA_ELEMENTNOTENABLED(provider->Select());
- EXPECT_HRESULT_SUCCEEDED(provider->get_IsSelected(&selected));
+ EXPECT_UIA_ELEMENTNOTENABLED(selection_item_provider->AddToSelection());
+ EXPECT_UIA_ELEMENTNOTENABLED(selection_item_provider->RemoveFromSelection());
+ EXPECT_UIA_ELEMENTNOTENABLED(selection_item_provider->Select());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_TRUE(selected);
}
@@ -5360,15 +5570,12 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderNotSelectable) {
Init(root);
- ComPtr<ISelectionItemProvider> item_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(GetRootNode());
-
- BOOL selected;
-
- EXPECT_UIA_INVALIDOPERATION(item_provider->AddToSelection());
- EXPECT_UIA_INVALIDOPERATION(item_provider->RemoveFromSelection());
- EXPECT_UIA_INVALIDOPERATION(item_provider->Select());
- EXPECT_UIA_INVALIDOPERATION(item_provider->get_IsSelected(&selected));
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
+ GetRootIRawElementProviderSimple();
+ ComPtr<ISelectionItemProvider> selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &selection_item_provider));
+ ASSERT_EQ(nullptr, selection_item_provider.Get());
}
TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderSimple) {
@@ -5384,9 +5591,12 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderSimple) {
Init(root, option1);
- const auto* root_node = GetRootNode();
- ComPtr<ISelectionItemProvider> option1_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(root_node->children()[0]);
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
+ GetIRawElementProviderSimpleFromChildIndex(0);
+ ComPtr<ISelectionItemProvider> option1_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &option1_provider));
+ ASSERT_NE(nullptr, option1_provider.Get());
BOOL selected;
@@ -5467,31 +5677,41 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderRadioButtonIsSelected) {
Init(root, option1, option2, option3, option4);
- const auto* root_node = GetRootNode();
- // CheckedState::kNone
- ComPtr<ISelectionItemProvider> option1_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(root_node->children()[0]);
- // CheckedState::kFalse
- ComPtr<ISelectionItemProvider> option2_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(root_node->children()[1]);
- // CheckedState::kTrue
- ComPtr<ISelectionItemProvider> option3_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(root_node->children()[2]);
- // CheckedState::kMixed
- ComPtr<ISelectionItemProvider> option4_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(root_node->children()[3]);
-
BOOL selected;
- EXPECT_UIA_INVALIDOPERATION(option1_provider->get_IsSelected(&selected));
+ // CheckedState::kNone
+ ComPtr<ISelectionItemProvider> option1_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ GetIRawElementProviderSimpleFromChildIndex(0)->GetPatternProvider(
+ UIA_SelectionItemPatternId, &option1_provider));
+ ASSERT_EQ(nullptr, option1_provider.Get());
+
+ // CheckedState::kFalse
+ ComPtr<ISelectionItemProvider> option2_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ GetIRawElementProviderSimpleFromChildIndex(1)->GetPatternProvider(
+ UIA_SelectionItemPatternId, &option2_provider));
+ ASSERT_NE(nullptr, option2_provider.Get());
EXPECT_HRESULT_SUCCEEDED(option2_provider->get_IsSelected(&selected));
EXPECT_FALSE(selected);
+ // CheckedState::kTrue
+ ComPtr<ISelectionItemProvider> option3_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ GetIRawElementProviderSimpleFromChildIndex(2)->GetPatternProvider(
+ UIA_SelectionItemPatternId, &option3_provider));
+ ASSERT_NE(nullptr, option3_provider.Get());
+
EXPECT_HRESULT_SUCCEEDED(option3_provider->get_IsSelected(&selected));
EXPECT_TRUE(selected);
- EXPECT_UIA_INVALIDOPERATION(option4_provider->get_IsSelected(&selected));
+ // CheckedState::kMixed
+ ComPtr<ISelectionItemProvider> option4_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ GetIRawElementProviderSimpleFromChildIndex(3)->GetPatternProvider(
+ UIA_SelectionItemPatternId, &option4_provider));
+ ASSERT_EQ(nullptr, option4_provider.Get());
}
TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderTable) {
@@ -5512,16 +5732,11 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderTable) {
Init(root, row1, cell1);
- const auto* row = GetRootNode()->children()[0];
- ComPtr<ISelectionItemProvider> item_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(row->children()[0]);
-
- BOOL selected;
-
- EXPECT_UIA_INVALIDOPERATION(item_provider->AddToSelection());
- EXPECT_UIA_INVALIDOPERATION(item_provider->RemoveFromSelection());
- EXPECT_UIA_INVALIDOPERATION(item_provider->Select());
- EXPECT_UIA_INVALIDOPERATION(item_provider->get_IsSelected(&selected));
+ ComPtr<ISelectionItemProvider> selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(
+ GetIRawElementProviderSimpleFromChildIndex(0)->GetPatternProvider(
+ UIA_SelectionItemPatternId, &selection_item_provider));
+ ASSERT_EQ(nullptr, selection_item_provider.Get());
}
TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderGrid) {
@@ -5543,8 +5758,13 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderGrid) {
Init(root, row1, cell1);
const auto* row = GetRootNode()->children()[0];
- ComPtr<ISelectionItemProvider> item_provider =
- QueryInterfaceFromNode<ISelectionItemProvider>(row->children()[0]);
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(row->children()[0]);
+
+ ComPtr<ISelectionItemProvider> selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &selection_item_provider));
+ ASSERT_NE(nullptr, selection_item_provider.Get());
BOOL selected;
@@ -5552,37 +5772,37 @@ TEST_F(AXPlatformNodeWinTest, TestISelectionItemProviderGrid) {
// flip kSelected for kCell when the kDoDefault action is fired.
// Initial State
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_FALSE(selected);
// AddToSelection should fire event when not selected
- EXPECT_HRESULT_SUCCEEDED(item_provider->AddToSelection());
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->AddToSelection());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_TRUE(selected);
// AddToSelection should not fire event when selected
- EXPECT_HRESULT_SUCCEEDED(item_provider->AddToSelection());
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->AddToSelection());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_TRUE(selected);
// RemoveFromSelection should fire event when selected
- EXPECT_HRESULT_SUCCEEDED(item_provider->RemoveFromSelection());
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->RemoveFromSelection());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_FALSE(selected);
// RemoveFromSelection should not fire event when not selected
- EXPECT_HRESULT_SUCCEEDED(item_provider->RemoveFromSelection());
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->RemoveFromSelection());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_FALSE(selected);
// Select should fire event when not selected
- EXPECT_HRESULT_SUCCEEDED(item_provider->Select());
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->Select());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_TRUE(selected);
// Select should not fire event when selected
- EXPECT_HRESULT_SUCCEEDED(item_provider->Select());
- EXPECT_HRESULT_SUCCEEDED(item_provider->get_IsSelected(&selected));
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->Select());
+ EXPECT_HRESULT_SUCCEEDED(selection_item_provider->get_IsSelected(&selected));
EXPECT_TRUE(selected);
}
@@ -5836,4 +6056,11 @@ TEST_F(AXPlatformNodeWinTest, IScrollProviderSetScrollPercent) {
EXPECT_EQ(y_scroll_percent, 34);
}
+TEST_F(AXPlatformNodeWinTest, TestSanitizeStringAttributeForIA2) {
+ std::string input("\\:=,;");
+ std::string output;
+ AXPlatformNodeWin::SanitizeStringAttributeForIA2(input, &output);
+ EXPECT_EQ("\\\\\\:\\=\\,\\;", output);
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc b/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
index 7b4fc903580..c5c58bd5ca6 100644
--- a/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -7,6 +7,7 @@
#include <unordered_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"
@@ -25,6 +26,9 @@ std::unordered_map<AXNode*, TestAXNodeWrapper*> g_node_to_wrapper_map;
// A global coordinate offset.
gfx::Vector2d g_offset;
+// A global scale factor.
+float g_scale_factor = 1.0;
+
// A global map that stores which node is focused on a determined tree.
// - If a tree has no node being focused, there shouldn't be any entry on the
// map associated with such tree, i.e. a pair {tree, nullptr} is invalid.
@@ -87,6 +91,12 @@ const AXNode* TestAXNodeWrapper::GetNodeFromLastDefaultAction() {
return g_node_from_last_default_action;
}
+// static
+std::unique_ptr<base::AutoReset<float>> TestAXNodeWrapper::SetScaleFactor(
+ float value) {
+ return std::make_unique<base::AutoReset<float>>(&g_scale_factor, value);
+}
+
TestAXNodeWrapper::~TestAXNodeWrapper() {
platform_node_->Destroy();
}
@@ -110,8 +120,13 @@ AXNodePosition::AXPositionInstance TestAXNodeWrapper::CreateTextPositionAt(
ax::mojom::TextAffinity::kDownstream);
}
+gfx::NativeViewAccessible TestAXNodeWrapper::GetNativeViewAccessible() {
+ return ax_platform_node()->GetNativeViewAccessible();
+}
+
gfx::NativeViewAccessible TestAXNodeWrapper::GetParent() {
- TestAXNodeWrapper* parent_wrapper = GetOrCreate(tree_, node_->parent());
+ TestAXNodeWrapper* parent_wrapper =
+ GetOrCreate(tree_, node_->GetUnignoredParent());
return parent_wrapper ?
parent_wrapper->ax_platform_node()->GetNativeViewAccessible() :
nullptr;
@@ -137,6 +152,14 @@ gfx::Rect TestAXNodeWrapper::GetBoundsRect(
// We could optionally add clipping here if ever needed.
gfx::RectF bounds = GetLocation();
bounds.Offset(g_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:
@@ -171,6 +194,14 @@ gfx::Rect TestAXNodeWrapper::GetInnerTextRangeBoundsRect(
}
bounds.Offset(g_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:
@@ -222,7 +253,8 @@ TestAXNodeWrapper* TestAXNodeWrapper::HitTestSyncInternal(int x, int y) {
}
gfx::NativeViewAccessible TestAXNodeWrapper::HitTestSync(int x, int y) {
- TestAXNodeWrapper* wrapper = HitTestSyncInternal(x, y);
+ TestAXNodeWrapper* wrapper =
+ HitTestSyncInternal(x / g_scale_factor, y / g_scale_factor);
return wrapper ? wrapper->ax_platform_node()->GetNativeViewAccessible()
: nullptr;
}
@@ -269,6 +301,15 @@ AXPlatformNode* TestAXNodeWrapper::GetFromNodeID(int32_t id) {
return nullptr;
}
+AXPlatformNode* TestAXNodeWrapper::GetFromTreeIDAndNodeID(
+ const ui::AXTreeID& ax_tree_id,
+ int32_t id) {
+ // TestAXNodeWrapper only supports one accessibility tree.
+ // Additional work would need to be done to support multiple trees.
+ CHECK_EQ(GetTreeData().tree_id, ax_tree_id);
+ return GetFromNodeID(id);
+}
+
int TestAXNodeWrapper::GetIndexInParent() {
return node_ ? int{node_->index_in_parent()} : -1;
}
@@ -486,9 +527,9 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
int scroll_y_max =
GetData().GetIntAttribute(ax::mojom::IntAttribute::kScrollYMax);
int scroll_x =
- std::max(scroll_x_min, std::min(data.target_point.x(), scroll_x_max));
+ base::ClampToRange(data.target_point.x(), scroll_x_min, scroll_x_max);
int scroll_y =
- std::max(scroll_y_min, std::min(data.target_point.y(), scroll_y_max));
+ base::ClampToRange(data.target_point.y(), scroll_y_min, scroll_y_max);
ReplaceIntAttribute(node_->id(), ax::mojom::IntAttribute::kScrollX,
scroll_x);
@@ -556,6 +597,7 @@ base::string16 TestAXNodeWrapper::GetLocalizedStringForLandmarkType() const {
const AXNodeData& data = GetData();
switch (data.role) {
case ax::mojom::Role::kBanner:
+ case ax::mojom::Role::kHeader:
return base::ASCIIToUTF16("banner");
case ax::mojom::Role::kComplementary:
@@ -566,6 +608,7 @@ base::string16 TestAXNodeWrapper::GetLocalizedStringForLandmarkType() const {
return base::ASCIIToUTF16("content information");
case ax::mojom::Role::kRegion:
+ case ax::mojom::Role::kSection:
if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
return base::ASCIIToUTF16("region");
FALLTHROUGH;
@@ -577,6 +620,7 @@ base::string16 TestAXNodeWrapper::GetLocalizedStringForLandmarkType() const {
base::string16 TestAXNodeWrapper::GetLocalizedStringForRoleDescription() const {
const AXNodeData& data = GetData();
+
switch (data.role) {
case ax::mojom::Role::kArticle:
return base::ASCIIToUTF16("article");
@@ -612,12 +656,30 @@ base::string16 TestAXNodeWrapper::GetLocalizedStringForRoleDescription() const {
case ax::mojom::Role::kFigure:
return base::ASCIIToUTF16("figure");
+ case ax::mojom::Role::kFooter:
+ case ax::mojom::Role::kFooterAsNonLandmark:
+ return base::ASCIIToUTF16("footer");
+
+ case ax::mojom::Role::kHeader:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
+ return base::ASCIIToUTF16("header");
+
+ case ax::mojom::Role::kMark:
+ return base::ASCIIToUTF16("highlight");
+
case ax::mojom::Role::kMeter:
return base::ASCIIToUTF16("meter");
case ax::mojom::Role::kSearchBox:
return base::ASCIIToUTF16("search box");
+ case ax::mojom::Role::kSection: {
+ if (data.HasStringAttribute(ax::mojom::StringAttribute::kName))
+ return base::ASCIIToUTF16("section");
+
+ return {};
+ }
+
case ax::mojom::Role::kStatus:
return base::ASCIIToUTF16("output");
@@ -747,13 +809,13 @@ TestAXNodeWrapper* TestAXNodeWrapper::InternalGetChild(int index) const {
void TestAXNodeWrapper::Descendants(
const AXNode* node,
std::vector<gfx::NativeViewAccessible>* descendants) const {
- std::vector<AXNode*> child_nodes = node->children();
- for (AXNode* child : child_nodes) {
+ for (auto it = node->UnignoredChildrenBegin();
+ it != node->UnignoredChildrenEnd(); ++it) {
descendants->emplace_back(ax_platform_node()
->GetDelegate()
- ->GetFromNodeID(child->id())
+ ->GetFromNodeID(it->id())
->GetNativeViewAccessible());
- Descendants(child, descendants);
+ Descendants(it.get(), descendants);
}
}
@@ -792,4 +854,28 @@ gfx::RectF TestAXNodeWrapper::GetInlineTextRect(const int start_offset,
return bounds;
}
+AXOffscreenResult TestAXNodeWrapper::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/platform/test_ax_node_wrapper.h b/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
index eff377809eb..acb54628444 100644
--- a/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -9,6 +9,7 @@
#include <string>
#include <vector>
+#include "base/auto_reset.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_node.h"
#include "ui/accessibility/ax_tree.h"
@@ -40,6 +41,9 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
// called from for testing.
static const AXNode* GetNodeFromLastDefaultAction();
+ // Set a global scale factor for testing.
+ static std::unique_ptr<base::AutoReset<float>> SetScaleFactor(float value);
+
~TestAXNodeWrapper() override;
AXPlatformNode* ax_platform_node() const { return platform_node_; }
@@ -55,6 +59,7 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
const AXTree::Selection GetUnignoredSelection() const override;
AXNodePosition::AXPositionInstance CreateTextPositionAt(
int offset) const override;
+ gfx::NativeViewAccessible GetNativeViewAccessible() override;
gfx::NativeViewAccessible GetParent() override;
int GetChildCount() override;
gfx::NativeViewAccessible ChildAtIndex(int index) override;
@@ -77,6 +82,8 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
gfx::NativeViewAccessible GetFocus() override;
bool IsMinimized() const override;
AXPlatformNode* GetFromNodeID(int32_t id) override;
+ AXPlatformNode* GetFromTreeIDAndNodeID(const ui::AXTreeID& ax_tree_id,
+ int32_t id) override;
int GetIndexInParent() override;
bool IsTable() const override;
base::Optional<int> GetTableRowCount() const override;
@@ -149,6 +156,9 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
gfx::RectF GetInlineTextRect(const int start_offset,
const int end_offset) const;
+ // Determine the offscreen status of a particular element given its bounds..
+ AXOffscreenResult DetermineOffscreenResult(gfx::RectF bounds) const;
+
AXTree* tree_;
AXNode* node_;
ui::AXUniqueId unique_id_;