summaryrefslogtreecommitdiff
path: root/chromium/ui/accessibility/platform
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/ui/accessibility/platform
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/accessibility/platform')
-rw-r--r--chromium/ui/accessibility/platform/BUILD.gn142
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win.cc66
-rw-r--r--chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc104
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node.h2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc169
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux.h3
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc19
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.cc108
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_base.h54
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate.h30
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc56
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h12
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_mac.h1
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_mac.mm21
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc2
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc18
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc43
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h10
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc27
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_unittest.cc8
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.cc173
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win.h35
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc496
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h3
-rw-r--r--chromium/ui/accessibility/platform/ax_platform_relation_win.cc6
-rw-r--r--chromium/ui/accessibility/platform/ichromeaccessible.idl64
-rw-r--r--chromium/ui/accessibility/platform/test_ax_node_wrapper.cc54
-rw-r--r--chromium/ui/accessibility/platform/test_ax_node_wrapper.h4
-rw-r--r--chromium/ui/accessibility/platform/uia_registrar_win.cc50
-rw-r--r--chromium/ui/accessibility/platform/uia_registrar_win.h45
30 files changed, 1548 insertions, 277 deletions
diff --git a/chromium/ui/accessibility/platform/BUILD.gn b/chromium/ui/accessibility/platform/BUILD.gn
new file mode 100644
index 00000000000..691a701457d
--- /dev/null
+++ b/chromium/ui/accessibility/platform/BUILD.gn
@@ -0,0 +1,142 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/features.gni")
+import("//build/config/jumbo.gni")
+import("//build/config/linux/pkg_config.gni")
+import("//build/config/ui.gni")
+import("//mojo/public/tools/bindings/mojom.gni")
+import("//testing/libfuzzer/fuzzer_test.gni")
+import("//testing/test.gni")
+import("//tools/json_schema_compiler/json_schema_api.gni")
+import("//ui/base/ui_features.gni")
+
+if (is_win) {
+ import("//build/toolchain/win/midl.gni")
+}
+
+if (is_win) {
+ midl("ichromeaccessible") {
+ sources = [ "ichromeaccessible.idl" ]
+ }
+}
+
+if (is_android) {
+ import("//build/config/android/rules.gni")
+}
+
+source_set("platform") {
+ defines = [ "AX_IMPLEMENTATION" ]
+
+ visibility = [ "//ui/accessibility" ]
+
+ sources = [
+ # Used by by browser_accessibility_state_impl.cc.
+ "ax_platform_node.cc",
+ "ax_platform_node.h",
+ "ax_platform_node_delegate.h",
+
+ # Used by browser_accessibility.cc.
+ "ax_unique_id.cc",
+ "ax_unique_id.h",
+
+ # Used by accessibility_tree_formatter_blink.cc.
+ "compute_attributes.cc",
+ "compute_attributes.h",
+
+ # Used by //ui/accessibility:ax_assistant.
+ "ax_android_constants.cc",
+ "ax_android_constants.h",
+
+ # Used by //ui/views/views/ax_virtual_view.h.
+ "ax_platform_node_base.cc",
+ "ax_platform_node_base.h",
+ "ax_platform_node_delegate_base.cc",
+ "ax_platform_node_delegate_base.h",
+
+ # Used by //chrome/test/browser_tests/browser_view_browsertest.cc
+ "ax_platform_node_test_helper.cc",
+ "ax_platform_node_test_helper.h",
+ ]
+
+ public_deps = [
+ "//ui/accessibility:ax_base",
+ "//ui/display",
+ ]
+
+ if (has_native_accessibility) {
+ sources += [
+ "ax_fragment_root_delegate_win.h",
+ "ax_fragment_root_win.cc",
+ "ax_fragment_root_win.h",
+ "ax_platform_node_delegate_utils_win.cc",
+ "ax_platform_node_delegate_utils_win.h",
+ "ax_platform_node_textchildprovider_win.cc",
+ "ax_platform_node_textchildprovider_win.h",
+ "ax_platform_node_textprovider_win.cc",
+ "ax_platform_node_textprovider_win.h",
+ "ax_platform_node_textrangeprovider_win.cc",
+ "ax_platform_node_textrangeprovider_win.h",
+ "ax_platform_node_win.cc",
+ "ax_platform_node_win.h",
+ "ax_platform_relation_win.cc",
+ "ax_platform_relation_win.h",
+ "ax_platform_text_boundary.cc",
+ "ax_platform_text_boundary.h",
+ "ax_system_caret_win.cc",
+ "ax_system_caret_win.h",
+ "uia_registrar_win.cc",
+ "uia_registrar_win.h",
+ ]
+
+ if (is_win) {
+ public_deps += [
+ "//third_party/iaccessible2",
+ "//ui/accessibility/platform:ichromeaccessible",
+ ]
+
+ libs = [
+ "oleacc.lib",
+ "uiautomationcore.lib",
+ ]
+ }
+
+ if (is_mac) {
+ sources += [
+ "ax_platform_node_mac.h",
+ "ax_platform_node_mac.mm",
+ ]
+
+ libs = [
+ "AppKit.framework",
+ "Foundation.framework",
+ ]
+ }
+
+ if (use_atk) {
+ sources += [
+ "atk_util_auralinux.cc",
+ "atk_util_auralinux.h",
+ "atk_util_auralinux_gtk.cc",
+ "ax_platform_atk_hyperlink.cc",
+ "ax_platform_atk_hyperlink.h",
+ "ax_platform_node_auralinux.cc",
+ "ax_platform_node_auralinux.h",
+ ]
+
+ # ax_platform_text_boundary.h includes atk.h, so ATK is needed
+ # as a public config to ensure anything that includes this is
+ # able to find atk.h.
+ public_configs = [ "//build/config/linux/atk" ]
+
+ if (use_glib) {
+ configs += [ "//build/config/linux:glib" ]
+ }
+
+ if (use_x11) {
+ public_deps += [ "//ui/gfx/x" ]
+ }
+ }
+ }
+}
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
index f1925832840..163fb8446dd 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win.cc
@@ -7,17 +7,21 @@
#include <unordered_map>
#include "base/no_destructor.h"
+#include "base/strings/string_number_conversions.h"
#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/base/win/atl_module.h"
namespace ui {
class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
+ public IItemContainerProvider,
public IRawElementProviderFragmentRoot,
public IRawElementProviderAdviseEvents {
public:
BEGIN_COM_MAP(AXFragmentRootPlatformNodeWin)
+ COM_INTERFACE_ENTRY(IItemContainerProvider)
COM_INTERFACE_ENTRY(IRawElementProviderFragmentRoot)
COM_INTERFACE_ENTRY(IRawElementProviderAdviseEvents)
COM_INTERFACE_ENTRY_CHAIN(AXPlatformNodeWin)
@@ -38,19 +42,76 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
}
//
+ // IItemContainerProvider methods.
+ //
+ IFACEMETHODIMP FindItemByProperty(
+ IRawElementProviderSimple* start_after_element,
+ PROPERTYID property_id,
+ VARIANT value,
+ IRawElementProviderSimple** result) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ITEMCONTAINER_FINDITEMBYPROPERTY);
+ UIA_VALIDATE_CALL_1_ARG(result);
+ *result = nullptr;
+
+ // We currently only support the custom UIA property ID for unique id and we
+ // ignore |start_after_element|.
+ if (property_id ==
+ UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId() &&
+ value.vt == VT_BSTR) {
+ // TODO: We should support the case when |start_after_element| isn't
+ // nullptr for unique id (https://crbug.com/1098160).
+ if (start_after_element)
+ return E_INVALIDARG;
+
+ int32_t ax_unique_id;
+ if (!base::StringToInt(value.bstrVal, &ax_unique_id))
+ return S_OK;
+
+ // In the Windows accessibility platform implementation, id 0 represents
+ // self; a positive id represents the immediate descendants; and a
+ // negative id represents a unique id that can be mapped to any node.
+ if (AXPlatformNodeWin* node_win =
+ static_cast<AXPlatformNodeWin*>(GetFromUniqueId(-ax_unique_id))) {
+ node_win->QueryInterface(IID_PPV_ARGS(result));
+ }
+
+ return S_OK;
+ }
+
+ return E_INVALIDARG;
+ }
+
+ //
// IRawElementProviderSimple methods.
//
IFACEMETHODIMP get_HostRawElementProvider(
IRawElementProviderSimple** host_element_provider) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_HOST_RAW_ELEMENT_PROVIDER);
UIA_VALIDATE_CALL_1_ARG(host_element_provider);
HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
return UiaHostProviderFromHwnd(hwnd, host_element_provider);
}
+ IFACEMETHODIMP GetPatternProvider(PATTERNID pattern_id,
+ IUnknown** result) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PATTERN_PROVIDER);
+ UIA_VALIDATE_CALL_1_ARG(result);
+ *result = nullptr;
+
+ if (pattern_id == UIA_ItemContainerPatternId) {
+ AddRef();
+ *result = static_cast<IItemContainerProvider*>(this);
+ return S_OK;
+ }
+
+ return AXPlatformNodeWin::GetPatternProviderImpl(pattern_id, result);
+ }
+
IFACEMETHODIMP GetPropertyValue(PROPERTYID property_id,
VARIANT* result) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PROPERTY_VALUE);
UIA_VALIDATE_CALL_1_ARG(result);
switch (property_id) {
@@ -84,6 +145,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
IFACEMETHODIMP get_FragmentRoot(
IRawElementProviderFragmentRoot** fragment_root) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FRAGMENTROOT);
UIA_VALIDATE_CALL_1_ARG(fragment_root);
QueryInterface(IID_PPV_ARGS(fragment_root));
@@ -97,6 +159,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
double screen_physical_pixel_x,
double screen_physical_pixel_y,
IRawElementProviderFragment** element_provider) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ELEMENT_PROVIDER_FROM_POINT);
UIA_VALIDATE_CALL_1_ARG(element_provider);
*element_provider = nullptr;
@@ -124,6 +187,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
}
IFACEMETHODIMP GetFocus(IRawElementProviderFragment** focus) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FOCUS);
UIA_VALIDATE_CALL_1_ARG(focus);
*focus = nullptr;
@@ -157,6 +221,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
//
IFACEMETHODIMP AdviseEventAdded(EVENTID event_id,
SAFEARRAY* property_ids) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_ADDED);
if (event_id == UIA_LiveRegionChangedEventId) {
live_region_change_listeners_++;
@@ -179,6 +244,7 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
IFACEMETHODIMP AdviseEventRemoved(EVENTID event_id,
SAFEARRAY* property_ids) override {
+ WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_REMOVED);
if (event_id == UIA_LiveRegionChangedEventId) {
DCHECK(live_region_change_listeners_ > 0);
live_region_change_listeners_--;
diff --git a/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc b/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
index 7691d12cece..e26ad0446c1 100644
--- a/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "ui/accessibility/platform/ax_fragment_root_win.h"
+#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/accessibility/platform/ax_platform_node_win_unittest.h"
#include "ui/accessibility/platform/test_ax_node_wrapper.h"
@@ -14,11 +15,26 @@
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
+using base::win::ScopedVariant;
using Microsoft::WRL::ComPtr;
namespace ui {
+#define EXPECT_UIA_BSTR_EQ(node, property_id, expected) \
+ { \
+ ScopedVariant expectedVariant(expected); \
+ ASSERT_EQ(VT_BSTR, expectedVariant.type()); \
+ ASSERT_NE(nullptr, expectedVariant.ptr()->bstrVal); \
+ ScopedVariant actual; \
+ ASSERT_HRESULT_SUCCEEDED( \
+ node->GetPropertyValue(property_id, actual.Receive())); \
+ ASSERT_EQ(VT_BSTR, actual.type()); \
+ ASSERT_NE(nullptr, actual.ptr()->bstrVal); \
+ EXPECT_STREQ(expectedVariant.ptr()->bstrVal, actual.ptr()->bstrVal); \
+ }
+
class AXFragmentRootTest : public AXPlatformNodeWinTest {
public:
AXFragmentRootTest() = default;
@@ -27,6 +43,92 @@ class AXFragmentRootTest : public AXPlatformNodeWinTest {
AXFragmentRootTest& operator=(const AXFragmentRootTest&) = delete;
};
+TEST_F(AXFragmentRootTest, UIAFindItemByProperty) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("root");
+ root.child_ids = {2, 3};
+
+ AXNodeData text1;
+ text1.id = 2;
+ text1.role = ax::mojom::Role::kStaticText;
+ text1.SetName("text1");
+
+ AXNodeData button;
+ button.id = 3;
+ button.role = ax::mojom::Role::kButton;
+ button.SetName("button");
+ button.child_ids = {4};
+
+ AXNodeData text2;
+ text2.id = 4;
+ text2.role = ax::mojom::Role::kStaticText;
+ text2.SetName("text2");
+
+ Init(root, text1, button, text2);
+ InitFragmentRoot();
+
+ ComPtr<IRawElementProviderSimple> raw_element_provider_simple;
+ ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
+ IID_PPV_ARGS(&raw_element_provider_simple));
+
+ ComPtr<IItemContainerProvider> item_container_provider;
+ EXPECT_HRESULT_SUCCEEDED(raw_element_provider_simple->GetPatternProvider(
+ UIA_ItemContainerPatternId, &item_container_provider));
+ ASSERT_NE(nullptr, item_container_provider.Get());
+
+ // Fetch the AxUniqueId of "root", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ ScopedVariant unique_id_variant;
+ int32_t unique_id = AXPlatformNodeFromNode(GetRootAsAXNode())->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ ComPtr<IRawElementProviderSimple> result;
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"root");
+ result.Reset();
+ unique_id_variant.Release();
+
+ // Fetch the AxUniqueId of "text1", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ unique_id =
+ AXPlatformNodeFromNode(GetRootAsAXNode()->children()[0])->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"text1");
+ result.Reset();
+ unique_id_variant.Release();
+
+ // Fetch the AxUniqueId of "button", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ AXNode* button_node = GetRootAsAXNode()->children()[1];
+ unique_id = AXPlatformNodeFromNode(button_node)->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"button");
+ result.Reset();
+ unique_id_variant.Release();
+
+ // Fetch the AxUniqueId of "text2", and verify we can retrieve its
+ // corresponding IRawElementProviderSimple through FindItemByProperty().
+ unique_id = AXPlatformNodeFromNode(button_node->children()[0])->GetUniqueId();
+ unique_id_variant.Set(
+ SysAllocString(base::NumberToString16(-unique_id).c_str()));
+ EXPECT_HRESULT_SUCCEEDED(item_container_provider->FindItemByProperty(
+ nullptr, UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId(),
+ unique_id_variant, &result));
+ EXPECT_UIA_BSTR_EQ(result, UIA_NamePropertyId, L"text2");
+}
+
TEST_F(AXFragmentRootTest, TestUIAGetFragmentRoot) {
AXNodeData root;
Init(root);
@@ -227,7 +329,7 @@ TEST_F(AXFragmentRootTest, TestGetPropertyValue) {
// IsControlElement and IsContentElement should follow the setting on the
// fragment root delegate.
test_fragment_root_delegate_->is_control_element_ = true;
- base::win::ScopedVariant result;
+ ScopedVariant result;
EXPECT_HRESULT_SUCCEEDED(root_provider->GetPropertyValue(
UIA_IsControlElementPropertyId, result.Receive()));
EXPECT_EQ(result.type(), VT_BOOL);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node.h b/chromium/ui/accessibility/platform/ax_platform_node.h
index c5198fc792e..9621fcb9a5c 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node.h
@@ -92,7 +92,7 @@ class AX_EXPORT AXPlatformNode {
// Return true if this object is equal to or a descendant of |ancestor|.
virtual bool IsDescendantOf(AXPlatformNode* ancestor) const = 0;
- // Return the unique ID
+ // Return the unique ID.
int32_t GetUniqueId() const;
// Creates a string representation of this node's data.
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 89309020096..a4554aaac25 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -17,6 +17,7 @@
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/debug/leak_annotations.h"
+#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/numerics/ranges.h"
#include "base/optional.h"
@@ -73,6 +74,29 @@ namespace ui {
namespace {
+// IMPORTANT!
+// These values are written to logs. Do not renumber or delete
+// existing items; add new entries to the end of the list.
+enum class UmaAtkApi {
+ kGetName = 0,
+ kGetDescription = 1,
+ kGetNChildren = 2,
+ kRefChild = 3,
+ kGetIndexInParent = 4,
+ kGetParent = 5,
+ kRefRelationSet = 6,
+ kGetAttributes = 7,
+ kGetRole = 8,
+ kRefStateSet = 9,
+ // This must always be the last enum. It's okay for its value to
+ // increase, but none of the other enum values may change.
+ kMaxValue = kRefStateSet,
+};
+
+void RecordAccessibilityAtkApi(UmaAtkApi enum_value) {
+ UMA_HISTOGRAM_ENUMERATION("Accessibility.ATK-APIs", enum_value);
+}
+
// When accepting input from clients calling the API, an ATK character offset
// of -1 can often represent the length of the string.
static const int kStringLengthOffset = -1;
@@ -186,13 +210,17 @@ void SetIntPointerValueIfNotNull(int* pointer, int value) {
*pointer = value;
}
+#if defined(ATK_230)
bool SupportsAtkComponentScrollingInterface() {
return dlsym(RTLD_DEFAULT, "atk_component_scroll_to_point");
}
+#endif
+#if defined(ATK_232)
bool SupportsAtkTextScrollingInterface() {
return dlsym(RTLD_DEFAULT, "atk_text_scroll_substring_to_point");
}
+#endif
AtkObject* FindAtkObjectParentFrame(AtkObject* atk_object) {
AXPlatformNodeAuraLinux* node =
@@ -1246,6 +1274,7 @@ char* GetStringAtOffset(AtkText* atk_text,
}
#endif
+#if defined(ATK_230)
gfx::Rect GetUnclippedParentHypertextRangeBoundsRect(
AXPlatformNodeDelegate* ax_platform_node_delegate,
const int start_offset,
@@ -1269,6 +1298,7 @@ gfx::Rect GetUnclippedParentHypertextRangeBoundsRect(
AXClippingBehavior::kClipped)
.OffsetFromOrigin();
}
+#endif
void GetCharacterExtents(AtkText* atk_text,
int offset,
@@ -2028,6 +2058,7 @@ const gchar* GetName(AtkObject* atk_object) {
}
const gchar* AtkGetName(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetName);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetName(atk_object);
}
@@ -2045,6 +2076,7 @@ const gchar* GetDescription(AtkObject* atk_object) {
}
const gchar* AtkGetDescription(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetDescription);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetDescription(atk_object);
}
@@ -2061,6 +2093,7 @@ gint GetNChildren(AtkObject* atk_object) {
}
gint AtkGetNChildren(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetNChildren);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetNChildren(atk_object);
}
@@ -2083,6 +2116,7 @@ AtkObject* RefChild(AtkObject* atk_object, gint index) {
}
AtkObject* AtkRefChild(AtkObject* atk_object, gint index) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kRefChild);
AXPlatformNodeAuraLinux::EnableAXMode();
return RefChild(atk_object, index);
}
@@ -2099,6 +2133,7 @@ gint GetIndexInParent(AtkObject* atk_object) {
}
gint AtkGetIndexInParent(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetIndexInParent);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetIndexInParent(atk_object);
}
@@ -2115,6 +2150,7 @@ AtkObject* GetParent(AtkObject* atk_object) {
}
AtkObject* AtkGetParent(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetParent);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetParent(atk_object);
}
@@ -2130,6 +2166,7 @@ AtkRelationSet* RefRelationSet(AtkObject* atk_object) {
}
AtkRelationSet* AtkRefRelationSet(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kRefRelationSet);
AXPlatformNodeAuraLinux::EnableAXMode();
return RefRelationSet(atk_object);
}
@@ -2146,6 +2183,7 @@ AtkAttributeSet* GetAttributes(AtkObject* atk_object) {
}
AtkAttributeSet* AtkGetAttributes(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetAttributes);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetAttributes(atk_object);
}
@@ -2161,6 +2199,7 @@ AtkRole GetRole(AtkObject* atk_object) {
}
AtkRole AtkGetRole(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kGetRole);
AXPlatformNodeAuraLinux::EnableAXMode();
return GetRole(atk_object);
}
@@ -2183,6 +2222,7 @@ AtkStateSet* RefStateSet(AtkObject* atk_object) {
}
AtkStateSet* AtkRefStateSet(AtkObject* atk_object) {
+ RecordAccessibilityAtkApi(UmaAtkApi::kRefStateSet);
AXPlatformNodeAuraLinux::EnableAXMode();
return RefStateSet(atk_object);
}
@@ -2468,6 +2508,8 @@ AtkObject* AXPlatformNodeAuraLinux::CreateAtkObject() {
if (GetData().role != ax::mojom::Role::kApplication &&
!GetAccessibilityMode().has_mode(AXMode::kNativeAPIs))
return nullptr;
+ if (GetDelegate()->IsChildOfLeaf())
+ return nullptr;
EnsureGTypeInit();
interface_mask_ = GetGTypeInterfaceMask(GetData());
GType type = GetAccessibilityGType();
@@ -3000,10 +3042,8 @@ void AXPlatformNodeAuraLinux::GetAtkState(AtkStateSet* atk_state_set) {
static_cast<int32_t>(ax::mojom::InvalidState::kFalse))
atk_state_set_add_state(atk_state_set, ATK_STATE_INVALID_ENTRY);
#if defined(ATK_216)
- if (data.HasIntAttribute(ax::mojom::IntAttribute::kCheckedState) &&
- data.role != ax::mojom::Role::kToggleButton) {
+ if (IsPlatformCheckable())
atk_state_set_add_state(atk_state_set, ATK_STATE_CHECKABLE);
- }
if (data.HasIntAttribute(ax::mojom::IntAttribute::kHasPopup))
atk_state_set_add_state(atk_state_set, ATK_STATE_HAS_POPUP);
#endif
@@ -3217,6 +3257,13 @@ void AXPlatformNodeAuraLinux::Init(AXPlatformNodeDelegate* delegate) {
GetOrCreateAtkObject();
}
+bool AXPlatformNodeAuraLinux::IsPlatformCheckable() const {
+ if (GetData().role == ax::mojom::Role::kToggleButton)
+ return false;
+
+ return AXPlatformNodeBase::IsPlatformCheckable();
+}
+
void AXPlatformNodeAuraLinux::EnsureAtkObjectIsValid() {
if (atk_object_) {
// If the object's role changes and that causes its
@@ -3343,7 +3390,7 @@ void AXPlatformNodeAuraLinux::OnMenuPopupStart() {
atk_object_notify_state_change(parent_frame, ATK_STATE_ACTIVE, TRUE);
}
-void AXPlatformNodeAuraLinux::OnMenuPopupHide() {
+void AXPlatformNodeAuraLinux::OnMenuPopupEnd() {
AtkObject* atk_object = GetOrCreateAtkObject();
AtkObject* parent_frame = FindAtkObjectParentFrame(atk_object);
if (!parent_frame)
@@ -3354,35 +3401,24 @@ void AXPlatformNodeAuraLinux::OnMenuPopupHide() {
// kMenuPopupHide may be called multiple times for the same menu, so only
// remove it if our parent frame matches the most recently opened menu.
std::vector<AtkObject*>& active_menus = GetActiveMenus();
- if (active_menus.empty())
- return;
-
- // When multiple levels of menu are closed at once, they may be hidden out
- // of order. When this happens, we just remove the open menu from the stack.
- if (active_menus.back() != atk_object) {
- auto it = std::find(active_menus.rbegin(), active_menus.rend(), atk_object);
- if (it != active_menus.rend()) {
- // We used a reverse iterator, so we need to convert it into a normal
- // iterator to use it for std::vector::erase(...).
- auto to_remove = --(it.base());
- active_menus.erase(to_remove);
- }
- return;
- }
+ DCHECK(!active_menus.empty())
+ << "Asymmetrical menupopupend events -- too many";
active_menus.pop_back();
-
- // We exit early if the newly activated menu has the same AtkWindow as the
- // previous one.
AtkObject* new_active_item = ComputeActiveTopLevelFrame();
- if (new_active_item == parent_frame)
- return;
- g_signal_emit_by_name(parent_frame, "deactivate");
- atk_object_notify_state_change(parent_frame, ATK_STATE_ACTIVE, FALSE);
- if (new_active_item) {
- g_signal_emit_by_name(new_active_item, "activate");
- atk_object_notify_state_change(new_active_item, ATK_STATE_ACTIVE, TRUE);
+ if (new_active_item != parent_frame) {
+ // Newly activated menu has the different AtkWindow as the previous one.
+ g_signal_emit_by_name(parent_frame, "deactivate");
+ atk_object_notify_state_change(parent_frame, ATK_STATE_ACTIVE, FALSE);
+ if (new_active_item) {
+ g_signal_emit_by_name(new_active_item, "activate");
+ atk_object_notify_state_change(new_active_item, ATK_STATE_ACTIVE, TRUE);
+ }
}
+
+ // All menus are closed.
+ if (active_menus.empty())
+ OnAllMenusEnded();
}
void AXPlatformNodeAuraLinux::ResendFocusSignalsForCurrentlyFocusedNode() {
@@ -3398,7 +3434,8 @@ void AXPlatformNodeAuraLinux::ResendFocusSignalsForCurrentlyFocusedNode() {
atk_object_notify_state_change(focused_node, ATK_STATE_FOCUSED, true);
}
-void AXPlatformNodeAuraLinux::OnMenuPopupEnd() {
+// All menus have closed.
+void AXPlatformNodeAuraLinux::OnAllMenusEnded() {
if (!GetActiveMenus().empty() && g_active_top_level_frame &&
ComputeActiveTopLevelFrame() != g_active_top_level_frame) {
g_signal_emit_by_name(g_active_top_level_frame, "activate");
@@ -3406,8 +3443,8 @@ void AXPlatformNodeAuraLinux::OnMenuPopupEnd() {
TRUE);
}
- ResendFocusSignalsForCurrentlyFocusedNode();
GetActiveMenus().clear();
+ ResendFocusSignalsForCurrentlyFocusedNode();
}
void AXPlatformNodeAuraLinux::OnWindowActivated() {
@@ -3512,16 +3549,25 @@ void AXPlatformNodeAuraLinux::OnFocused() {
SetActiveViewsDialog();
- if (g_current_focused) {
- g_signal_emit_by_name(g_current_focused, "focus-event", false);
- atk_object_notify_state_change(ATK_OBJECT(g_current_focused),
+ AtkObject* old_effective_focus = g_current_active_descendant
+ ? g_current_active_descendant
+ : g_current_focused;
+ if (old_effective_focus) {
+ g_signal_emit_by_name(old_effective_focus, "focus-event", false);
+ atk_object_notify_state_change(ATK_OBJECT(old_effective_focus),
ATK_STATE_FOCUSED, false);
}
SetWeakGPtrToAtkObject(&g_current_focused, atk_object);
- g_signal_emit_by_name(atk_object, "focus-event", true);
- atk_object_notify_state_change(ATK_OBJECT(atk_object), ATK_STATE_FOCUSED,
- true);
+ AtkObject* descendant = GetActiveDescendantOfCurrentFocused();
+ SetWeakGPtrToAtkObject(&g_current_active_descendant, descendant);
+
+ AtkObject* new_effective_focus = g_current_active_descendant
+ ? g_current_active_descendant
+ : g_current_focused;
+ g_signal_emit_by_name(new_effective_focus, "focus-event", true);
+ atk_object_notify_state_change(ATK_OBJECT(new_effective_focus),
+ ATK_STATE_FOCUSED, true);
}
void AXPlatformNodeAuraLinux::OnSelected() {
@@ -3874,21 +3920,15 @@ void AXPlatformNodeAuraLinux::NotifyAccessibilityEvent(
return;
AXPlatformNodeBase::NotifyAccessibilityEvent(event_type);
switch (event_type) {
- // There are three types of messages that we receive for popup menus. Each
- // time a popup menu is shown, we get a kMenuPopupStart message. This
- // includes if the menu is hidden and then re-shown. When a menu is hidden
- // we receive the kMenuPopupHide message. Finally, when the entire menu is
- // closed we receive the kMenuPopupEnd message for the parent menu and all
- // of the submenus that were opened when navigating through the menu.
- case ax::mojom::Event::kMenuPopupEnd:
- OnMenuPopupEnd();
- break;
- case ax::mojom::Event::kMenuPopupHide:
- OnMenuPopupHide();
- break;
+ // kMenuStart/kMenuEnd: the menu system has started / stopped.
+ // kMenuPopupStart/kMenuPopupEnd: an individual menu/submenu has
+ // opened/closed.
case ax::mojom::Event::kMenuPopupStart:
OnMenuPopupStart();
break;
+ case ax::mojom::Event::kMenuPopupEnd:
+ OnMenuPopupEnd();
+ break;
case ax::mojom::Event::kActiveDescendantChanged:
OnActiveDescendantChanged();
break;
@@ -4176,7 +4216,30 @@ gfx::NativeViewAccessible
AXPlatformNodeAuraLinux::HitTestSync(gint x, gint y, AtkCoordType coord_type) {
gfx::Point scroll_to(x, y);
scroll_to = ConvertPointToScreenCoordinates(scroll_to, coord_type);
- return delegate_->HitTestSync(scroll_to.x(), scroll_to.y());
+
+ AXPlatformNode* current_result = this;
+ while (true) {
+ gfx::NativeViewAccessible hit_child =
+ current_result->GetDelegate()->HitTestSync(scroll_to.x(),
+ scroll_to.y());
+ if (!hit_child)
+ return nullptr;
+ AXPlatformNode* hit_child_node =
+ AXPlatformNode::FromNativeViewAccessible(hit_child);
+ if (!hit_child_node || !hit_child_node->IsDescendantOf(current_result))
+ break;
+
+ // If we get the same node, we're done.
+ if (hit_child_node == current_result)
+ break;
+
+ // Continue to check recursively. That's because HitTestSync may have
+ // returned the best result within a particular accessibility tree,
+ // but we might need to recurse further in a tree of a different type
+ // (for example, from Views to Web).
+ current_result = hit_child_node;
+ }
+ return current_result->GetNativeViewAccessible();
}
bool AXPlatformNodeAuraLinux::GrabFocus() {
@@ -4303,9 +4366,9 @@ AtkAttributeSet* AXPlatformNodeAuraLinux::GetAtkAttributes() {
AtkStateType AXPlatformNodeAuraLinux::GetAtkStateTypeForCheckableNode() {
if (GetData().GetCheckedState() == ax::mojom::CheckedState::kMixed)
return ATK_STATE_INDETERMINATE;
- if (GetData().role == ax::mojom::Role::kToggleButton)
- return ATK_STATE_PRESSED;
- return ATK_STATE_CHECKED;
+ if (IsPlatformCheckable())
+ return ATK_STATE_CHECKED;
+ return ATK_STATE_PRESSED;
}
// AtkDocumentHelpers
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
index 6fde338d9e1..610b2041b97 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux.h
@@ -206,8 +206,8 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
void OnWindowActivated();
void OnWindowDeactivated();
void OnMenuPopupStart();
- void OnMenuPopupHide();
void OnMenuPopupEnd();
+ void OnAllMenusEnded();
void OnSelected();
void OnSelectedChildrenChanged();
void OnTextSelectionChanged();
@@ -237,6 +237,7 @@ class AX_EXPORT AXPlatformNodeAuraLinux : public AXPlatformNodeBase {
// AXPlatformNodeBase overrides.
void Init(AXPlatformNodeDelegate* delegate) override;
+ bool IsPlatformCheckable() const override;
bool IsNameExposed();
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index b120f746734..706471c0a84 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -201,6 +201,7 @@ static bool AtkObjectHasState(AtkObject* atk_object, AtkStateType state) {
TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectDetachedObject) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -231,6 +232,7 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectDetachedObject) {
TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectName) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -659,12 +661,14 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkComponentRefAtPoint) {
AXNodeData node1;
node1.id = 2;
+ node1.role = ax::mojom::Role::kGenericContainer;
node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
node1.SetName("Name1");
root.child_ids.push_back(node1.id);
AXNodeData node2;
node2.id = 3;
+ node2.role = ax::mojom::Role::kGenericContainer;
node2.relative_bounds.bounds = gfx::RectF(20, 20, 10, 10);
node2.SetName("Name2");
root.child_ids.push_back(node2.id);
@@ -1820,23 +1824,10 @@ TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkPopupWindowActive) {
{
ActivationTester tester(menu_atk_node);
GetPlatformNode(menu_node)->NotifyAccessibilityEvent(
- ax::mojom::Event::kMenuPopupHide);
- EXPECT_FALSE(tester.saw_activate_);
- EXPECT_TRUE(tester.saw_deactivate_);
- EXPECT_FALSE(tester.IsActivatedInStateSet());
- EXPECT_EQ(focus_events_on_original_node, 0);
- }
-
- {
- ActivationTester tester(menu_atk_node);
- GetPlatformNode(menu_node)->NotifyAccessibilityEvent(
ax::mojom::Event::kMenuPopupEnd);
EXPECT_FALSE(tester.saw_activate_);
- EXPECT_FALSE(tester.saw_deactivate_);
+ EXPECT_TRUE(tester.saw_deactivate_);
EXPECT_FALSE(tester.IsActivatedInStateSet());
-
- // The menu has closed so the original node should have received focus
- // again.
EXPECT_EQ(focus_events_on_original_node, 1);
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.cc b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
index 99de6603989..36081b70c14 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.cc
@@ -4,6 +4,9 @@
#include "ui/accessibility/platform/ax_platform_node_base.h"
+#include <algorithm>
+#include <limits>
+#include <set>
#include <string>
#include <unordered_map>
#include <utility>
@@ -26,6 +29,7 @@
namespace ui {
namespace {
+
// A function to call when focus changes, for testing only.
base::LazyInstance<std::map<ax::mojom::Event, base::RepeatingClosure>>::
DestructorAtExit g_on_notify_event_for_testing;
@@ -54,6 +58,7 @@ bool FindDescendantRoleWithMaxDepth(AXPlatformNodeBase* node,
return false;
}
+
} // namespace
const base::char16 AXPlatformNodeBase::kEmbeddedCharacter = L'\xfffc';
@@ -139,7 +144,7 @@ gfx::NativeViewAccessible AXPlatformNodeBase::ChildAtIndex(int index) const {
std::string AXPlatformNodeBase::GetName() const {
if (delegate_)
return delegate_->GetName();
- return base::EmptyString();
+ return std::string();
}
base::string16 AXPlatformNodeBase::GetNameAsString16() const {
@@ -495,11 +500,20 @@ bool AXPlatformNodeBase::IsDocument() const {
}
bool AXPlatformNodeBase::IsTextOnlyObject() const {
+ if (!delegate_)
+ return false;
+
+ // In Legacy Layout, a list marker has no children and is thus represented on
+ // all platforms as a leaf node that exposes the marker itself, i.e., it forms
+ // part of the AX tree's text representation. In contrast, in Layout NG, a
+ // list marker has a static text child.
+ if (GetData().role == ax::mojom::Role::kListMarker)
+ return !GetChildCount();
return ui::IsText(GetData().role);
}
bool AXPlatformNodeBase::IsTextField() const {
- return IsPlainTextField() || IsRichTextField();
+ return GetData().IsTextField();
}
bool AXPlatformNodeBase::IsPlainTextField() const {
@@ -507,16 +521,18 @@ bool AXPlatformNodeBase::IsPlainTextField() const {
}
bool AXPlatformNodeBase::IsRichTextField() const {
- return GetBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot) &&
- GetData().HasState(ax::mojom::State::kRichlyEditable);
+ return GetData().IsRichTextField();
}
base::string16 AXPlatformNodeBase::GetHypertext() const {
+ if (!delegate_)
+ return base::string16();
+
// Hypertext of platform leaves, which internally are composite objects, are
// represented with the inner text of the internal composite object. These
// don't exist on non-web content.
if (IsChildOfLeaf())
- return GetDelegate()->GetInnerText();
+ return GetInnerText();
if (hypertext_.needs_update)
UpdateComputedHypertext();
@@ -524,38 +540,9 @@ base::string16 AXPlatformNodeBase::GetHypertext() const {
}
base::string16 AXPlatformNodeBase::GetInnerText() const {
- // In order to get the inner text for web content, we potentially need access
- // to nodes that are not exposed to platform APIs, i.e. they are only visible
- // in the internal accessibility tree. For example, nodes representing the
- // shadow DOM inside a native text field.
- if (GetDelegate()->IsWebContent())
- return GetDelegate()->GetInnerText();
-
- // Allows us to get text even in non-web content, e.g. in the browser's UI
- // (AKA Views).
- //
- // Unlike in web content The "kValue" attribute takes precedence, because the
- // accessibility of Views controls are carefully crafted by hand, in contrast
- // to HTML pages, where any content that might be present in the shadow DOM
- // (i.e. in the internal accessibility tree) is actually used by the renderer.
- base::string16 value =
- GetString16Attribute(ax::mojom::StringAttribute::kValue);
- if (!value.empty())
- return value;
-
- // TODO(https://crbug.com/1030703): The check for IsInvisibleOrIgnored()
- // should not be needed. ChildAtIndex() and GetChildCount() are already
- // supposed to skip over nodes that are invisible or ignored, but
- // ViewAXPlatformNodeDelegate does not currently implement this behavior.
- if (!GetChildCount() && !IsInvisibleOrIgnored())
- return GetNameAsString16();
-
- base::string16 text;
- for (auto child_iter = AXPlatformNodeChildrenBegin();
- child_iter != AXPlatformNodeChildrenEnd(); ++child_iter) {
- text += child_iter->GetInnerText();
- }
- return text;
+ if (!delegate_)
+ return base::string16();
+ return delegate_->GetInnerText();
}
bool AXPlatformNodeBase::IsSelectionItemSupported() const {
@@ -847,48 +834,15 @@ bool AXPlatformNodeBase::HasCaret(
}
bool AXPlatformNodeBase::IsLeaf() const {
- if (!GetChildCount())
- return true;
-
- // These types of objects may have children that we use as internal
- // implementation details, but we want to expose them as leaves to platform
- // accessibility APIs because screen readers might be confused if they find
- // any children.
- if (IsPlainTextField() || IsTextOnlyObject())
- return true;
-
- // Roles whose children are only presentational according to the ARIA and
- // HTML5 Specs should be hidden from screen readers.
- // (Note that whilst ARIA buttons can have only presentational children, HTML5
- // buttons are allowed to have content.)
- switch (GetData().role) {
- case ax::mojom::Role::kImage:
- case ax::mojom::Role::kMeter:
- case ax::mojom::Role::kScrollBar:
- case ax::mojom::Role::kSlider:
- case ax::mojom::Role::kSplitter:
- case ax::mojom::Role::kProgressIndicator:
- return true;
- default:
- return false;
- }
+ return delegate_ && delegate_->IsLeaf();
}
bool AXPlatformNodeBase::IsChildOfLeaf() const {
- AXPlatformNodeBase* ancestor = FromNativeViewAccessible(GetParent());
-
- while (ancestor) {
- if (ancestor->IsLeaf())
- return true;
- ancestor = FromNativeViewAccessible(ancestor->GetParent());
- }
-
- return false;
+ return delegate_ && delegate_->IsChildOfLeaf();
}
bool AXPlatformNodeBase::IsInvisibleOrIgnored() const {
- const AXNodeData& data = GetData();
- return data.HasState(ax::mojom::State::kInvisible) || data.IsIgnored();
+ return GetData().IsInvisibleOrIgnored();
}
bool AXPlatformNodeBase::IsScrollable() const {
@@ -981,7 +935,7 @@ void AXPlatformNodeBase::ComputeAttributes(PlatformAttributeList* attributes) {
AddAttributeToList(ax::mojom::IntAttribute::kPosInSet, "posinset",
attributes);
- if (HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
+ if (IsPlatformCheckable())
AddAttributeToList("checkable", "true", attributes);
if (IsInvisibleOrIgnored()) // Note: NVDA prefers this over INVISIBLE state.
@@ -1280,6 +1234,8 @@ AXHypertext::AXHypertext(const AXHypertext& other) = default;
AXHypertext& AXHypertext::operator=(const AXHypertext& other) = default;
void AXPlatformNodeBase::UpdateComputedHypertext() const {
+ if (!delegate_)
+ return;
hypertext_ = AXHypertext();
if (IsLeaf()) {
@@ -1728,6 +1684,10 @@ bool AXPlatformNodeBase::IsText(const base::string16& text,
return ch != kEmbeddedCharacter;
}
+bool AXPlatformNodeBase::IsPlatformCheckable() const {
+ return delegate_ && GetData().HasCheckedState();
+}
+
void AXPlatformNodeBase::ComputeHypertextRemovedAndInserted(
const AXHypertext& old_hypertext,
size_t* start,
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_base.h b/chromium/ui/accessibility/platform/ax_platform_node_base.h
index 90fca62bb3f..c14c8c7e698 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_base.h
@@ -216,19 +216,13 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// Optionally accepts an unignored selection to avoid redundant computation.
bool HasCaret(const AXTree::Selection* unignored_selection = nullptr);
- // Returns true if an ancestor of this node (not including itself) is a
- // leaf node, meaning that this node is not actually exposed to the
- // platform.
+ // See AXPlatformNodeDelegate::IsChildOfLeaf().
bool IsChildOfLeaf() const;
- // Returns true if this is a leaf node on this platform, meaning any
- // children should not be exposed to this platform's native accessibility
- // layer. Each platform subclass should implement this itself.
- // The definition of a leaf may vary depending on the platform,
- // but a leaf node should never have children that are focusable or
- // that might send notifications.
+ // See AXPlatformNodeDelegate::IsLeaf().
bool IsLeaf() const;
+ // See AXPlatformNodeDelegate::IsInvisibleOrIgnored().
bool IsInvisibleOrIgnored() const;
// Returns true if this node can be scrolled either in the horizontal or the
@@ -241,34 +235,41 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
// Returns true if this node can be scrolled in the vertical direction.
bool IsVerticallyScrollable() const;
- // Returns true if this node has role of StaticText, LineBreak, or
+ // Returns true if this node has a role of StaticText, LineBreak, or
// InlineTextBox
bool IsTextOnlyObject() const;
- // A text field is any widget in which the user should be able to enter and
- // edit text.
- //
- // Examples include <input type="text">, <input type="password">, <textarea>,
- // <div contenteditable="true">, <div role="textbox">, <div role="searchbox">
- // and <div role="combobox">. Note that when an ARIA role that indicates that
- // the widget is editable is used, such as "role=textbox", the element doesn't
- // need to be contenteditable for this method to return true, as in theory
- // JavaScript could be used to implement editing functionality. In practice,
- // this situation should be rare.
+ // See AXNodeData::IsTextField().
bool IsTextField() const;
- // Returns true if the node is an editable text field.
+ // See AXNodeData::IsPlainTextField().
bool IsPlainTextField() const;
+ // See AXNodeData::IsRichTextField().
+ bool IsRichTextField() const;
+
+ // Determines whether an element should be exposed with checkable state, and
+ // possibly the checked state. Examples are check box and radio button.
+ // Objects that are exposed as toggle buttons use the platform pressed state
+ // in some platform APIs, and should not be exposed as checkable. They don't
+ // expose the platform equivalent of the internal checked state.
+ virtual bool IsPlatformCheckable() const;
+
bool HasFocus();
- // If this node is a leaf, returns the text of this node, otherwise represents
- // each child node with a special "embedded object" character. This is how
- // text is represented in ATK and IA2 APIs.
+ // If this node is a leaf, returns the visible accessible name of this node.
+ // Otherwise represents every non-leaf child node with a special "embedded
+ // object character", and every leaf child node with its visible accessible
+ // name. This is how displayed text and embedded objects are represented in
+ // ATK and IA2 APIs.
base::string16 GetHypertext() const;
// Returns the text of this node and all descendant nodes; including text
// found in embedded objects.
+ //
+ // Only text displayed on screen is included. Text from ARIA and HTML
+ // attributes that is either not displayed on screen, or outside this node,
+ // e.g. aria-label and HTML title, is not returned.
base::string16 GetInnerText() const;
virtual base::string16 GetValue() const;
@@ -344,11 +345,10 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
//
// Delegate. This is a weak reference which owns |this|.
//
- AXPlatformNodeDelegate* delegate_;
+ AXPlatformNodeDelegate* delegate_ = nullptr;
protected:
bool IsDocument() const;
- bool IsRichTextField() const;
bool IsSelectionItemSupported() const;
// Get the range value text, which might come from aria-valuetext or
@@ -491,7 +491,7 @@ class AX_EXPORT AXPlatformNodeBase : public AXPlatformNode {
mutable AXHypertext hypertext_;
private:
- // Return true if the index represents a text character.
+ // Returns true if the index represents a text character.
bool IsText(const base::string16& text,
size_t index,
bool is_indexed_from_end = false);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
index c6064c6e248..e8ff6876672 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate.h
@@ -78,6 +78,14 @@ class AX_EXPORT AXPlatformNodeDelegate {
// Get the accessibility tree data for this node.
virtual const AXTreeData& GetTreeData() const = 0;
+ // Returns the text of this node and all descendant nodes; including text
+ // found in embedded objects.
+ //
+ // Only text displayed on screen is included. Text from ARIA and HTML
+ // attributes that is either not displayed on screen, or outside this node,
+ // e.g. aria-label and HTML title, is not returned.
+ virtual base::string16 GetInnerText() const = 0;
+
// Get the unignored selection from the tree
virtual const AXTree::Selection GetUnignoredSelection() const = 0;
@@ -121,12 +129,22 @@ class AX_EXPORT AXPlatformNodeDelegate {
virtual gfx::NativeViewAccessible GetPreviousSibling() = 0;
// Returns true if an ancestor of this node (not including itself) is a
- // leaf node, meaning that this node is not actually exposed to the
- // platform.
+ // leaf node, meaning that this node is not actually exposed to any
+ // platform's accessibility layer.
virtual bool IsChildOfLeaf() const = 0;
- // If this object is exposed to the platform, returns this object. Otherwise,
- // returns the platform leaf under which this object is found.
+ // Returns true if this current node is editable and the root editable node is
+ // a plain text field.
+ virtual bool IsChildOfPlainTextField() const = 0;
+
+ // Returns true if this is a leaf node, meaning all its
+ // children should not be exposed to any platform's native accessibility
+ // layer.
+ virtual bool IsLeaf() const = 0;
+
+ // If this object is exposed to the platform's accessibility layer, returns
+ // this object. Otherwise, returns the platform leaf under which this object
+ // is found.
virtual gfx::NativeViewAccessible GetClosestPlatformObject() const = 0;
class ChildIterator {
@@ -172,10 +190,6 @@ class AX_EXPORT AXPlatformNodeDelegate {
// implementations.
virtual std::string GetInheritedFontFamilyName() const = 0;
- // Returns the text of this node and all descendant nodes; including text
- // found in embedded objects.
- virtual base::string16 GetInnerText() const = 0;
-
// Return the bounds of this node in the coordinate system indicated. If the
// clipping behavior is set to clipped, clipping is applied. If an offscreen
// result address is provided, it will be populated depending on whether the
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
index 8abc5a9300e..39d55498dbd 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.cc
@@ -31,6 +31,38 @@ const AXTreeData& AXPlatformNodeDelegateBase::GetTreeData() const {
return *empty_data;
}
+base::string16 AXPlatformNodeDelegateBase::GetInnerText() const {
+ // Unlike in web content The "kValue" attribute always takes precedence,
+ // because we assume that users of this base class, such as Views controls,
+ // are carefully crafted by hand, in contrast to HTML pages, where any content
+ // that might be present in the shadow DOM (AKA in the internal accessibility
+ // tree) is actually used by the renderer when assigning the "kValue"
+ // attribute, including any redundant white space.
+ base::string16 value =
+ GetData().GetString16Attribute(ax::mojom::StringAttribute::kValue);
+ if (!value.empty())
+ return value;
+
+ // TODO(https://crbug.com/1030703): The check for IsInvisibleOrIgnored()
+ // should not be needed. ChildAtIndex() and GetChildCount() are already
+ // supposed to skip over nodes that are invisible or ignored, but
+ // ViewAXPlatformNodeDelegate does not currently implement this behavior.
+ if (IsLeaf() && !GetData().IsInvisibleOrIgnored())
+ return GetData().GetString16Attribute(ax::mojom::StringAttribute::kName);
+
+ base::string16 inner_text;
+ for (int i = 0; i < GetChildCount(); ++i) {
+ // TODO(nektar): Add const to all tree traversal methods and remove
+ // const_cast.
+ const AXPlatformNode* child = AXPlatformNode::FromNativeViewAccessible(
+ const_cast<AXPlatformNodeDelegateBase*>(this)->ChildAtIndex(i));
+ if (!child || !child->GetDelegate())
+ continue;
+ inner_text += child->GetDelegate()->GetInnerText();
+ }
+ return inner_text;
+}
+
const AXTree::Selection AXPlatformNodeDelegateBase::GetUnignoredSelection()
const {
return AXTree::Selection{-1, -1, -1, ax::mojom::TextAffinity::kDownstream};
@@ -95,6 +127,21 @@ gfx::NativeViewAccessible AXPlatformNodeDelegateBase::GetPreviousSibling() {
}
bool AXPlatformNodeDelegateBase::IsChildOfLeaf() const {
+ // TODO(nektar): Make all tree traversal methods const and remove const_cast.
+ const AXPlatformNodeDelegate* parent =
+ const_cast<AXPlatformNodeDelegateBase*>(this)->GetParentDelegate();
+ if (!parent)
+ return false;
+ if (parent->IsLeaf())
+ return true;
+ return parent->IsChildOfLeaf();
+}
+
+bool AXPlatformNodeDelegateBase::IsLeaf() const {
+ return !GetChildCount();
+}
+
+bool AXPlatformNodeDelegateBase::IsChildOfPlainTextField() const {
return false;
}
@@ -200,10 +247,6 @@ bool AXPlatformNodeDelegateBase::SetHypertextSelection(int start_offset,
return AccessibilityPerformAction(action_data);
}
-base::string16 AXPlatformNodeDelegateBase::GetInnerText() const {
- return base::string16();
-}
-
gfx::Rect AXPlatformNodeDelegateBase::GetBoundsRect(
const AXCoordinateSystem coordinate_system,
const AXClippingBehavior clipping_behavior,
@@ -533,6 +576,11 @@ AXPlatformNodeDelegateBase::GetTargetNodesForRelation(
std::set<AXPlatformNode*> AXPlatformNodeDelegateBase::GetReverseRelations(
ax::mojom::IntAttribute attr) {
+ // TODO(accessibility) Implement these if views ever use relations more
+ // widely. The use so far has been for the Omnibox to the suggestion popup.
+ // If this is ever implemented, then the "popup for" to "controlled by"
+ // mapping in AXPlatformRelationWin can be removed, as it would be
+ // redundant with setting the controls relationship.
return std::set<AXPlatformNode*>();
}
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
index 350b9f6a44b..7dd68b928d7 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_delegate_base.h
@@ -35,7 +35,7 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
// Get the accessibility tree data for this node.
const AXTreeData& GetTreeData() const override;
- // Get the unignored selection from the tree
+ base::string16 GetInnerText() const override;
const AXTree::Selection GetUnignoredSelection() const override;
// Creates a text position rooted at this object.
@@ -67,13 +67,9 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
gfx::NativeViewAccessible GetNextSibling() override;
gfx::NativeViewAccessible GetPreviousSibling() override;
- // Returns true if an ancestor of this node (not including itself) is a
- // leaf node, meaning that this node is not actually exposed to the
- // platform.
bool IsChildOfLeaf() const override;
-
- // If this object is exposed to the platform, returns this object. Otherwise,
- // returns the platform leaf under which this object is found.
+ bool IsChildOfPlainTextField() const override;
+ bool IsLeaf() const override;
gfx::NativeViewAccessible GetClosestPlatformObject() const override;
class ChildIteratorBase : public ChildIterator {
@@ -107,8 +103,6 @@ class AX_EXPORT AXPlatformNodeDelegateBase : public AXPlatformNodeDelegate {
const TextAttributeList& default_attributes) const override;
std::string GetInheritedFontFamilyName() const override;
- base::string16 GetInnerText() const override;
-
gfx::Rect GetBoundsRect(const AXCoordinateSystem coordinate_system,
const AXClippingBehavior clipping_behavior,
AXOffscreenResult* offscreen_result) const override;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_mac.h b/chromium/ui/accessibility/platform/ax_platform_node_mac.h
index 920f0a05363..c28d1a9fdc5 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_mac.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_mac.h
@@ -27,6 +27,7 @@ class AXPlatformNodeMac : public AXPlatformNodeBase {
// AXPlatformNodeBase.
void Destroy() override;
+ bool IsPlatformCheckable() const override;
protected:
void AddAttributeToList(const char* name,
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
index 9a8f6f6d60a..911344e83a4 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
+++ b/chromium/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -497,6 +497,8 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
return nil;
for (id child in [[self AXChildren] reverseObjectEnumerator]) {
+ if (!NSPointInRect(point, [child accessibilityFrame]))
+ continue;
if (id foundChild = [child accessibilityHitTest:point])
return foundChild;
}
@@ -744,7 +746,7 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
if (ui::IsNameExposedInAXValueForRole(role))
return [self getName];
- if (_node->HasIntAttribute(ax::mojom::IntAttribute::kCheckedState)) {
+ if (_node->IsPlatformCheckable()) {
// Mixed checkbox state not currently supported in views, but could be.
// See browser_accessibility_cocoa.mm for details.
const auto checkedState = static_cast<ax::mojom::CheckedState>(
@@ -844,8 +846,10 @@ bool AlsoUseShowMenuActionForDefaultAction(const ui::AXNodeData& data) {
- (NSValue*)AXSelectedTextRange {
// Selection might not be supported. Return (NSRange){0,0} in that case.
int start = 0, end = 0;
- _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart, &start);
- _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, &end);
+ if (_node->IsPlainTextField()) {
+ start = _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart);
+ end = _node->GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd);
+ }
// NSRange cannot represent the direction the text was selected in.
return [NSValue valueWithRange:{std::min(start, end), abs(end - start)}];
@@ -1209,6 +1213,17 @@ void AXPlatformNodeMac::Destroy() {
AXPlatformNodeBase::Destroy();
}
+// On Mac, the checked state is mapped to AXValue.
+bool AXPlatformNodeMac::IsPlatformCheckable() const {
+ if (GetData().role == ax::mojom::Role::kTab) {
+ // On Mac, tabs are exposed as radio buttons, and are treated as checkable.
+ // Also, the internal State::kSelected is be mapped to checked via AXValue.
+ return true;
+ }
+
+ return AXPlatformNodeBase::IsPlatformCheckable();
+}
+
gfx::NativeViewAccessible AXPlatformNodeMac::GetNativeViewAccessible() {
if (!native_node_)
native_node_.reset([[AXPlatformNodeCocoa alloc] initWithNode:this]);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc
index 167e1af7f8e..ec5adb620d6 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textchildprovider_win_unittest.cc
@@ -60,7 +60,7 @@ class AXPlatformNodeTextChildProviderTest : public AXPlatformNodeWinTest {
ui::AXNodeData text_child_of_text;
text_child_of_text.id = 6;
- text_child_of_text.role = ax::mojom::Role::kStaticText;
+ text_child_of_text.role = ax::mojom::Role::kInlineTextBox;
text_child_of_text.SetName("text child of text.");
text_child_of_root.child_ids.push_back(text_child_of_text.id);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
index ec472458f9f..d86ee466120 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textprovider_win_unittest.cc
@@ -7,6 +7,8 @@
#include <UIAutomationClient.h>
#include <UIAutomationCoreApi.h>
+#include <vector>
+
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
#include "ui/accessibility/ax_action_data.h"
@@ -107,8 +109,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderRangeFromChild) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
root_data.child_ids.push_back(3);
@@ -196,8 +198,8 @@ TEST_F(AXPlatformNodeTextProviderTest,
ui::AXNodeData root;
root.id = ROOT_ID;
- root.SetName("Document");
root.role = ax::mojom::Role::kRootWebArea;
+ root.SetName("Document");
root.child_ids = {DIALOG_ID};
ui::AXNodeData dialog;
@@ -343,8 +345,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderDocumentRange) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
Init(root_data, text_data);
@@ -374,8 +376,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderDocumentRangeNested) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
Init(root_data, paragraph_data, text_data);
@@ -400,8 +402,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderSupportedSelection) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
Init(root_data, text_data);
@@ -433,8 +435,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetSelection) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
root_data.child_ids.push_back(3);
@@ -608,8 +610,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetActiveComposition) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
ui::AXTreeUpdate update;
@@ -668,8 +670,8 @@ TEST_F(AXPlatformNodeTextProviderTest, ITextProviderGetConversionTarget) {
ui::AXNodeData root_data;
root_data.id = 1;
- root_data.SetName("Document");
root_data.role = ax::mojom::Role::kRootWebArea;
+ root_data.SetName("Document");
root_data.child_ids.push_back(2);
ui::AXTreeUpdate update;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
index ee4026570b1..379fa000578 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -172,6 +172,11 @@ HRESULT AXPlatformNodeTextRangeProviderWin::CompareEndpoints(
HRESULT AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnit(
TextUnit unit) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_EXPANDTOENCLOSINGUNIT);
+ return ExpandToEnclosingUnitImpl(unit);
+}
+
+HRESULT AXPlatformNodeTextRangeProviderWin::ExpandToEnclosingUnitImpl(
+ TextUnit unit) {
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
NormalizeTextRange();
@@ -627,8 +632,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// Move the start of the text range forward or backward in the document by the
// requested number of text unit boundaries.
int start_units_moved = 0;
- HRESULT hr = MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, count,
- &start_units_moved);
+ HRESULT hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_Start, unit,
+ count, &start_units_moved);
bool succeeded_move = SUCCEEDED(hr) && start_units_moved != 0;
if (succeeded_move) {
@@ -640,8 +645,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// by one text unit to expand the text range from the degenerate range
// state.
int current_start_units_moved = 0;
- hr = MoveEndpointByUnit(TextPatternRangeEndpoint_Start, unit, -1,
- &current_start_units_moved);
+ hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_Start, unit, -1,
+ &current_start_units_moved);
start_units_moved -= 1;
succeeded_move = SUCCEEDED(hr) && current_start_units_moved == -1 &&
start_units_moved > 0;
@@ -650,8 +655,8 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// forward by one text unit to expand the text range from the degenerate
// state.
int end_units_moved = 0;
- hr = MoveEndpointByUnit(TextPatternRangeEndpoint_End, unit, 1,
- &end_units_moved);
+ hr = MoveEndpointByUnitImpl(TextPatternRangeEndpoint_End, unit, 1,
+ &end_units_moved);
succeeded_move = SUCCEEDED(hr) && end_units_moved == 1;
}
@@ -660,7 +665,7 @@ HRESULT AXPlatformNodeTextRangeProviderWin::Move(TextUnit unit,
// sure to bring back the end endpoint to the end of the start's anchor.
if (start_->anchor_id() != end_->anchor_id() &&
(unit == TextUnit_Character || unit == TextUnit_Word)) {
- ExpandToEnclosingUnit(unit);
+ ExpandToEnclosingUnitImpl(unit);
}
}
}
@@ -683,6 +688,14 @@ HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnit(
int count,
int* units_moved) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_TEXTRANGE_MOVEENDPOINTBYUNIT);
+ return MoveEndpointByUnitImpl(endpoint, unit, count, units_moved);
+}
+
+HRESULT AXPlatformNodeTextRangeProviderWin::MoveEndpointByUnitImpl(
+ TextPatternRangeEndpoint endpoint,
+ TextUnit unit,
+ int count,
+ int* units_moved) {
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(units_moved);
// Per MSDN, MoveEndpointByUnit with zero count has no effect.
@@ -1124,12 +1137,22 @@ void AXPlatformNodeTextRangeProviderWin::NormalizeTextRange() {
NormalizeAsUnignoredTextRange();
// Do not normalize text ranges when a cursor or selection is visible. ATs
- // may depend on the specific position that the caret or selection is at.
+ // may depend on the specific position that the caret or selection is at. This
+ // condition fixes issues when the caret is inside a plain text field, but
+ // causes more issues when used inside of a rich text field. For this reason,
+ // if we have a caret or a selection inside of an editable node, restrict this
+ // to a plain text field as we gain nothing from using it in a rich text
+ // field.
AXPlatformNodeDelegate* start_delegate = GetDelegate(start_.get());
AXPlatformNodeDelegate* end_delegate = GetDelegate(end_.get());
- if ((start_delegate && start_delegate->HasVisibleCaretOrSelection()) ||
- (start_delegate && end_delegate->HasVisibleCaretOrSelection()))
+ if ((start_delegate && start_delegate->HasVisibleCaretOrSelection() &&
+ (!start_delegate->GetData().HasState(ax::mojom::State::kEditable) ||
+ start_delegate->IsChildOfPlainTextField())) ||
+ (end_delegate && end_delegate->HasVisibleCaretOrSelection() &&
+ (!end_delegate->GetData().HasState(ax::mojom::State::kEditable) ||
+ end_delegate->IsChildOfPlainTextField()))) {
return;
+ }
AXPositionInstance normalized_start =
start_->AsLeafTextPositionBeforeCharacter();
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
index db595fdf424..c967b191124 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.h
@@ -99,6 +99,16 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
AXBoundaryBehavior boundary_behavior,
ax::mojom::MoveDirection boundary_direction);
+ // Prefer these *Impl methods when functionality is needed internally. We
+ // should avoid calling external APIs internally as it will cause the
+ // histograms to become innaccurate.
+ HRESULT MoveEndpointByUnitImpl(TextPatternRangeEndpoint endpoint,
+ TextUnit unit,
+ int count,
+ int* units_moved);
+
+ IFACEMETHODIMP ExpandToEnclosingUnitImpl(TextUnit unit);
+
base::string16 GetString(int max_count,
size_t* appended_newlines_count = nullptr);
AXPlatformNodeWin* owner() const;
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index 13617fa8a8c..a698ca3c021 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -7,6 +7,9 @@
#include <UIAutomationClient.h>
#include <UIAutomationCoreApi.h>
+#include <memory>
+#include <utility>
+
#include "base/win/atl.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
@@ -3074,24 +3077,12 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
update.tree_data = tree_data;
update.has_tree_data = true;
update.root_id = root_data.id;
- update.nodes.push_back(root_data);
- update.nodes.push_back(paragraph_data);
- update.nodes.push_back(static_text_data1);
- update.nodes.push_back(inline_text_data1);
- update.nodes.push_back(link_data);
- update.nodes.push_back(static_text_data2);
- update.nodes.push_back(inline_text_data2);
- update.nodes.push_back(link_data2);
- update.nodes.push_back(list_data);
- update.nodes.push_back(list_item_data);
- update.nodes.push_back(static_text_data3);
- update.nodes.push_back(inline_text_data3);
- update.nodes.push_back(search_box);
- update.nodes.push_back(search_text);
- update.nodes.push_back(pdf_highlight_data);
- update.nodes.push_back(static_text_data4);
- update.nodes.push_back(inline_text_data4);
-
+ update.nodes = {root_data, paragraph_data, static_text_data1,
+ inline_text_data1, link_data, static_text_data2,
+ inline_text_data2, link_data2, list_data,
+ list_item_data, static_text_data3, inline_text_data3,
+ search_box, search_text, pdf_highlight_data,
+ static_text_data4, inline_text_data4};
Init(update);
// Set up variables from the tree for testing.
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
index 480a7233253..1cc881f5af8 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_unittest.cc
@@ -359,31 +359,31 @@ AXTreeUpdate AXPlatformNodeTest::BuildListBox(
const std::vector<ax::mojom::State>& additional_state) {
AXNodeData listbox;
listbox.id = 1;
- listbox.SetName("ListBox");
listbox.role = ax::mojom::Role::kListBox;
+ listbox.SetName("ListBox");
for (auto state : additional_state)
listbox.AddState(state);
AXNodeData option_1;
option_1.id = 2;
- option_1.SetName("Option1");
option_1.role = ax::mojom::Role::kListBoxOption;
+ option_1.SetName("Option1");
if (option_1_is_selected)
option_1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
listbox.child_ids.push_back(option_1.id);
AXNodeData option_2;
option_2.id = 3;
- option_2.SetName("Option2");
option_2.role = ax::mojom::Role::kListBoxOption;
+ option_2.SetName("Option2");
if (option_2_is_selected)
option_2.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
listbox.child_ids.push_back(option_2.id);
AXNodeData option_3;
option_3.id = 4;
- option_3.SetName("Option3");
option_3.role = ax::mojom::Role::kListBoxOption;
+ option_3.SetName("Option3");
if (option_3_is_selected)
option_3.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
listbox.child_ids.push_back(option_3.id);
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.cc b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
index b9fbc55ae57..d0fbf20bcf0 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.cc
@@ -15,11 +15,14 @@
#include <utility>
#include <vector>
+#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
#include "base/win/enum_variant.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
@@ -27,6 +30,7 @@
#include "skia/ext/skia_utils_win.h"
#include "third_party/iaccessible2/ia2_api_all.h"
#include "third_party/skia/include/core/SkColor.h"
+#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_active_popup.h"
@@ -43,6 +47,7 @@
#include "ui/accessibility/platform/ax_platform_node_textchildprovider_win.h"
#include "ui/accessibility/platform/ax_platform_node_textprovider_win.h"
#include "ui/accessibility/platform/ax_platform_relation_win.h"
+#include "ui/accessibility/platform/uia_registrar_win.h"
#include "ui/base/win/atl_module.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/rect_conversions.h"
@@ -297,7 +302,7 @@ AXPlatformNode* AXPlatformNode::FromNativeViewAccessible(
// AXPlatformNodeWin
//
-AXPlatformNodeWin::AXPlatformNodeWin() : force_new_hypertext_(false) {}
+AXPlatformNodeWin::AXPlatformNodeWin() {}
AXPlatformNodeWin::~AXPlatformNodeWin() {
ClearOwnRelations();
@@ -305,7 +310,6 @@ AXPlatformNodeWin::~AXPlatformNodeWin() {
void AXPlatformNodeWin::Init(AXPlatformNodeDelegate* delegate) {
AXPlatformNodeBase::Init(delegate);
- force_new_hypertext_ = false;
}
void AXPlatformNodeWin::ClearOwnRelations() {
@@ -314,10 +318,6 @@ void AXPlatformNodeWin::ClearOwnRelations() {
relations_.clear();
}
-void AXPlatformNodeWin::ForceNewHypertext() {
- force_new_hypertext_ = true;
-}
-
// Static
void AXPlatformNodeWin::SanitizeStringAttributeForUIAAriaProperty(
const base::string16& input,
@@ -645,7 +645,7 @@ void AXPlatformNodeWin::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
::VariantInit(old_value.Receive());
base::win::ScopedVariant new_value;
::VariantInit(new_value.Receive());
- GetPropertyValue((*uia_property), new_value.Receive());
+ GetPropertyValueImpl((*uia_property), new_value.Receive());
::UiaRaiseAutomationPropertyChangedEvent(this, (*uia_property), old_value,
new_value);
}
@@ -1263,6 +1263,14 @@ IFACEMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) {
IFACEMETHODIMP AXPlatformNodeWin::get_uniqueID(LONG* id) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_UNIQUE_ID);
COM_OBJECT_VALIDATE_1_ARG(id);
+ // We want to negate the unique id for it to be consistent across different
+ // Windows accessiblity APIs. The negative unique id convention originated
+ // from ::NotifyWinEvent() takes an hwnd and a child id. A 0 child id means
+ // self, and a positive child id means child #n. In order to fire an event for
+ // an arbitrary descendant of the window, Firefox started the practice of
+ // using a negative unique id. We follow the same negative unique id
+ // convention here and when we fire events via
+ // ::NotifyWinEvent().
*id = -GetUniqueId();
return S_OK;
}
@@ -2075,7 +2083,23 @@ HRESULT AXPlatformNodeWin::ISelectionItemProviderSetSelected(
return UIA_E_ELEMENTNOTENABLED;
}
- if (selected == ISelectionItemProviderIsSelected())
+ // The platform implements selection follows focus for single-selection
+ // container elements. Focus action can change a node's accessibility selected
+ // state, but does not cause the actual control to be selected.
+ // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_selection_follows_focus
+ // https://www.w3.org/TR/core-aam-1.2/#mapping_events_selection
+ //
+ // We don't want to perform |Action::kDoDefault| for an ax node that has
+ // |kSelected=true| and |kSelectedFromFocus=false|, because perform
+ // |Action::kDoDefault| may cause the control to be unselected. However, if an
+ // ax node is selected due to focus, i.e. |kSelectedFromFocus=true|, we need
+ // to perform |Action::kDoDefault| on the ax node, since focus action only
+ // changes an ax node's accessibility selected state to |kSelected=true| and
+ // no |Action::kDoDefault| was performed on that node yet. So we need to
+ // perform |Action::kDoDefault| on the ax node to cause its associated control
+ // to be selected.
+ if (selected == ISelectionItemProviderIsSelected() &&
+ !GetBoolAttribute(ax::mojom::BoolAttribute::kSelectedFromFocus))
return S_OK;
AXActionData data;
@@ -3971,6 +3995,11 @@ IFACEMETHODIMP AXPlatformNodeWin::get_FragmentRoot(
IFACEMETHODIMP AXPlatformNodeWin::GetPatternProvider(PATTERNID pattern_id,
IUnknown** result) {
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_PATTERN_PROVIDER);
+ return GetPatternProviderImpl(pattern_id, result);
+}
+
+HRESULT AXPlatformNodeWin::GetPatternProviderImpl(PATTERNID pattern_id,
+ IUnknown** result) {
UIA_VALIDATE_CALL_1_ARG(result);
*result = nullptr;
@@ -3997,7 +4026,11 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
// Collapse all unknown property IDs into a single bucket.
base::UmaHistogramSparse("Accessibility.WinAPIs.GetPropertyValue", 0);
}
+ return GetPropertyValueImpl(property_id, result);
+}
+HRESULT AXPlatformNodeWin::GetPropertyValueImpl(PROPERTYID property_id,
+ VARIANT* result) {
UIA_VALIDATE_CALL_1_ARG(result);
result->vt = VT_EMPTY;
@@ -4005,6 +4038,7 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
int int_attribute;
const AXNodeData& data = GetData();
+ // Default UIA Property Ids.
switch (property_id) {
case UIA_AriaPropertiesPropertyId:
result->vt = VT_BSTR;
@@ -4044,9 +4078,7 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
break;
case UIA_CulturePropertyId:
- result->vt = VT_BSTR;
- GetStringAttributeAsBstr(ax::mojom::StringAttribute::kLanguage,
- &result->bstrVal);
+ return GetCultureAttributeAsVariant(result);
break;
case UIA_DescribedByPropertyId:
@@ -4366,6 +4398,21 @@ IFACEMETHODIMP AXPlatformNodeWin::GetPropertyValue(PROPERTYID property_id,
case UIA_ProviderDescriptionPropertyId:
case UIA_RuntimeIdPropertyId:
break;
+ } // End of default UIA property ids.
+
+ // Custom UIA Property Ids.
+ if (property_id ==
+ UiaRegistrarWin::GetInstance().GetUiaUniqueIdPropertyId()) {
+ // We want to negate the unique id for it to be consistent across different
+ // Windows accessiblity APIs. The negative unique id convention originated
+ // from ::NotifyWinEvent() takes an hwnd and a child id. A 0 child id means
+ // self, and a positive child id means child #n. In order to fire an event
+ // for an arbitrary descendant of the window, Firefox started the practice
+ // of using a negative unique id. We follow the same negative unique id
+ // convention here and when we fire events via ::NotifyWinEvent().
+ result->vt = VT_BSTR;
+ result->bstrVal =
+ SysAllocString(base::NumberToString16(-GetUniqueId()).c_str());
}
return S_OK;
@@ -4405,6 +4452,71 @@ IFACEMETHODIMP AXPlatformNodeWin::ShowContextMenu() {
}
//
+// IChromeAccessible implementation.
+//
+
+void SendBulkFetchResponse(
+ Microsoft::WRL::ComPtr<IChromeAccessibleDelegate> delegate,
+ LONG request_id,
+ std::string json_result) {
+ base::string16 json_result_utf16 = base::UTF8ToUTF16(json_result);
+ delegate->put_bulkFetchResult(request_id,
+ SysAllocString(json_result_utf16.c_str()));
+}
+
+IFACEMETHODIMP AXPlatformNodeWin::get_bulkFetch(
+ BSTR input_json,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) {
+ COM_OBJECT_VALIDATE();
+ if (!delegate)
+ return E_INVALIDARG;
+
+ // TODO(crbug.com/1083834): if parsing |input_json|, use
+ // DataDecoder because the json is untrusted. For now, this is just
+ // a stub that calls PostTask so that it's async, but it doesn't
+ // actually parse the input.
+
+ base::Value result(base::Value::Type::DICTIONARY);
+ result.SetKey("role", base::Value(ui::ToString(GetData().role)));
+
+ gfx::Rect bounds = GetDelegate()->GetBoundsRect(
+ AXCoordinateSystem::kScreenDIPs, AXClippingBehavior::kUnclipped);
+ result.SetKey("x", base::Value(bounds.x()));
+ result.SetKey("y", base::Value(bounds.y()));
+ result.SetKey("width", base::Value(bounds.width()));
+ result.SetKey("height", base::Value(bounds.height()));
+ std::string json_result;
+ base::JSONWriter::Write(result, &json_result);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &SendBulkFetchResponse,
+ Microsoft::WRL::ComPtr<IChromeAccessibleDelegate>(delegate),
+ request_id, json_result));
+ return S_OK;
+}
+
+IFACEMETHODIMP AXPlatformNodeWin::get_hitTest(
+ LONG screen_physical_pixel_x,
+ LONG screen_physical_pixel_y,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) {
+ COM_OBJECT_VALIDATE();
+
+ if (!delegate)
+ return E_INVALIDARG;
+
+ // TODO(crbug.com/1083834): Plumb through an actual async hit test.
+ AXPlatformNodeWin* hit_child = static_cast<AXPlatformNodeWin*>(
+ FromNativeViewAccessible(GetDelegate()->HitTestSync(
+ screen_physical_pixel_x, screen_physical_pixel_y)));
+
+ delegate->put_hitTestResult(request_id, static_cast<IAccessible*>(hit_child));
+ return S_OK;
+}
+
+//
// IServiceProvider implementation.
//
@@ -4430,6 +4542,12 @@ IFACEMETHODIMP AXPlatformNodeWin::QueryService(REFGUID guidService,
return QueryInterface(riid, object);
}
+ if (guidService == IID_IChromeAccessible) {
+ if (features::IsIChromeAccessibleEnabled()) {
+ return QueryInterface(riid, object);
+ }
+ }
+
// TODO(suproteem): Include IAccessibleEx in the list, potentially checking
// for version.
@@ -4468,6 +4586,10 @@ STDMETHODIMP AXPlatformNodeWin::InternalQueryInterface(
if (!accessible->GetData().IsRangeValueSupported()) {
return E_NOINTERFACE;
}
+ } else if (riid == IID_IChromeAccessible) {
+ if (!features::IsIChromeAccessibleEnabled()) {
+ return E_NOINTERFACE;
+ }
}
return CComObjectRootBase::InternalQueryInterface(this_ptr, entries, riid,
@@ -5254,7 +5376,7 @@ int32_t AXPlatformNodeWin::ComputeIA2State() {
const AXNodeData& data = GetData();
int32_t ia2_state = IA2_STATE_OPAQUE;
- if (HasIntAttribute(ax::mojom::IntAttribute::kCheckedState))
+ if (IsPlatformCheckable())
ia2_state |= IA2_STATE_CHECKABLE;
if (HasIntAttribute(ax::mojom::IntAttribute::kInvalidState) &&
@@ -6800,7 +6922,12 @@ bool AXPlatformNodeWin::IsUIAControl() const {
// UIA provides multiple "views": raw, content and control. We only want to
// populate the content and control views with items that make sense to
// traverse over.
+
if (GetDelegate()->IsWebContent()) {
+ // Invisible or ignored elements should not show up in control view at all.
+ if (IsInvisibleOrIgnored())
+ return false;
+
if (IsTextOnlyObject()) {
// A text leaf can be a UIAControl, but text inside of a heading, link,
// button, etc. where the role allows the name to be generated from the
@@ -6840,7 +6967,8 @@ bool AXPlatformNodeWin::IsUIAControl() const {
}
parent = FromNativeViewAccessible(parent->GetParent());
}
- }
+ } // end of text only case.
+
const AXNodeData& data = GetData();
// https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-treeoverview#control-view
// The control view also includes noninteractive UI items that contribute
@@ -6892,9 +7020,10 @@ bool AXPlatformNodeWin::IsUIAControl() const {
!data.HasState(ax::mojom::State::kFocusable) && !data.IsClickable()) {
return false;
}
+
return true;
- }
- // non web-content case.
+ } // end of web-content only case.
+
const AXNodeData& data = GetData();
return !((IsReadOnlySupported(data.role) && data.IsReadOnlyOrDisabled()) ||
data.HasState(ax::mojom::State::kInvisible) ||
@@ -6959,8 +7088,10 @@ bool AXPlatformNodeWin::IsInaccessibleDueToAncestor() const {
}
bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
- auto role = GetData().role;
+ if (IsPlainTextField())
+ return true;
+ auto role = GetData().role;
if (HasPresentationalChildren(role))
return true;
@@ -6982,7 +7113,6 @@ bool AXPlatformNodeWin::ShouldHideChildrenForUIA() const {
return only_child && only_child->IsTextOnlyObject();
}
return false;
- case ax::mojom::Role::kTextField:
case ax::mojom::Role::kPdfActionableHighlight:
return true;
default:
@@ -7004,6 +7134,13 @@ base::string16 AXPlatformNodeWin::GetValue() const {
return value;
}
+bool AXPlatformNodeWin::IsPlatformCheckable() const {
+ if (GetData().role == ax::mojom::Role::kToggleButton)
+ return false;
+
+ return AXPlatformNodeBase::IsPlatformCheckable();
+}
+
bool AXPlatformNodeWin::ShouldNodeHaveFocusableState(
const AXNodeData& data) const {
switch (data.role) {
@@ -7212,6 +7349,8 @@ base::Optional<DWORD> AXPlatformNodeWin::MojoEventToMSAAEvent(
switch (event) {
case ax::mojom::Event::kAlert:
return EVENT_SYSTEM_ALERT;
+ case ax::mojom::Event::kActiveDescendantChanged:
+ return IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
case ax::mojom::Event::kCheckedStateChanged:
case ax::mojom::Event::kExpandedChanged:
case ax::mojom::Event::kStateChanged:
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win.h b/chromium/ui/accessibility/platform/ax_platform_node_win.h
index 1055422d59c..9f4367393bc 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win.h
@@ -28,6 +28,7 @@
#include "ui/accessibility/ax_text_utils.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/accessibility/platform/ax_platform_text_boundary.h"
+#include "ui/accessibility/platform/ichromeaccessible.h"
#include "ui/gfx/range/range.h"
// IMPORTANT!
@@ -280,6 +281,11 @@ enum {
UMA_API_WINDOW_GET_WINDOWVISUALSTATE = 243,
UMA_API_WINDOW_GET_WINDOWINTERACTIONSTATE = 244,
UMA_API_WINDOW_GET_ISTOPMOST = 245,
+ UMA_API_ELEMENT_PROVIDER_FROM_POINT = 246,
+ UMA_API_GET_FOCUS = 247,
+ UMA_API_ADVISE_EVENT_ADDED = 248,
+ UMA_API_ADVISE_EVENT_REMOVED = 249,
+ UMA_API_ITEMCONTAINER_FINDITEMBYPROPERTY = 250,
// This must always be the last enum. It's okay for its value to
// increase, but none of the other enum values may change.
@@ -358,6 +364,7 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
public IToggleProvider,
public IValueProvider,
public IWindowProvider,
+ public IChromeAccessible,
public AXPlatformNodeBase {
using IDispatchImpl::Invoke;
@@ -381,6 +388,7 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
COM_INTERFACE_ENTRY(IAccessibleTable2)
COM_INTERFACE_ENTRY(IAccessibleTableCell)
COM_INTERFACE_ENTRY(IAccessibleValue)
+ COM_INTERFACE_ENTRY(IChromeAccessible)
COM_INTERFACE_ENTRY(IExpandCollapseProvider)
COM_INTERFACE_ENTRY(IGridItemProvider)
COM_INTERFACE_ENTRY(IGridProvider)
@@ -408,8 +416,6 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Clear any AXPlatformRelationWin nodes owned by this node.
void ClearOwnRelations();
- void ForceNewHypertext();
-
// AXPlatformNode overrides.
gfx::NativeViewAccessible GetNativeViewAccessible() override;
void NotifyAccessibilityEvent(ax::mojom::Event event_type) override;
@@ -417,6 +423,7 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// AXPlatformNodeBase overrides.
void Destroy() override;
base::string16 GetValue() const override;
+ bool IsPlatformCheckable() const override;
//
// IAccessible methods.
@@ -1023,6 +1030,19 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
IFACEMETHODIMP ShowContextMenu() override;
//
+ // IChromeAccessible methods.
+ //
+
+ IFACEMETHODIMP get_bulkFetch(BSTR input_json,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) override;
+
+ IFACEMETHODIMP get_hitTest(LONG screen_physical_pixel_x,
+ LONG screen_physical_pixel_y,
+ LONG request_id,
+ IChromeAccessibleDelegate* delegate) override;
+
+ //
// IServiceProvider methods.
//
@@ -1046,6 +1066,16 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// IRawElementProviderSimple support method.
bool IsPatternProviderSupported(PATTERNID pattern_id);
+ // Prefer GetPatternProviderImpl when calling internally. We should avoid
+ // calling external APIs internally as it will cause the histograms to become
+ // innaccurate.
+ HRESULT GetPatternProviderImpl(PATTERNID pattern_id, IUnknown** result);
+
+ // Prefer GetPropertyValueImpl when calling internally. We should avoid
+ // calling external APIs internally as it will cause the histograms to become
+ // innaccurate.
+ HRESULT GetPropertyValueImpl(PROPERTYID property_id, VARIANT* result);
+
// Helper to return the runtime id (without going through a SAFEARRAY)
using RuntimeIdArray = std::array<int, 2>;
void GetRuntimeIdArray(RuntimeIdArray& runtime_id);
@@ -1123,7 +1153,6 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
std::vector<Microsoft::WRL::ComPtr<AXPlatformRelationWin>> relations_;
AXHypertext old_hypertext_;
- bool force_new_hypertext_;
// These protected methods are still used by BrowserAccessibilityComWin. At
// some point post conversion, we can probably move these to be private
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index 50b78af9642..c7fce2de057 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -11,8 +11,11 @@
#include <memory>
#include "base/auto_reset.h"
+#include "base/json/json_reader.h"
+#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
#include "base/win/atl.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_safearray.h"
@@ -20,6 +23,7 @@
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/iaccessible2/ia2_api_all.h"
+#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
@@ -219,7 +223,10 @@ ScopedVariant SELF(CHILDID_SELF);
testing::UnorderedElementsAreArray(expected_property_values)); \
}
-AXPlatformNodeWinTest::AXPlatformNodeWinTest() {}
+AXPlatformNodeWinTest::AXPlatformNodeWinTest() {
+ scoped_feature_list_.InitAndEnableFeature(features::kIChromeAccessible);
+}
+
AXPlatformNodeWinTest::~AXPlatformNodeWinTest() {}
void AXPlatformNodeWinTest::SetUp() {
@@ -451,6 +458,7 @@ bool TestFragmentRootDelegate::IsAXFragmentRootAControlElement() {
TEST_F(AXPlatformNodeWinTest, IAccessibleDetachedObject) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -472,12 +480,14 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleHitTest) {
AXNodeData node1;
node1.id = 2;
+ node1.role = ax::mojom::Role::kGenericContainer;
node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
node1.SetName("Name1");
root.child_ids.push_back(node1.id);
AXNodeData node2;
node2.id = 3;
+ node2.role = ax::mojom::Role::kGenericContainer;
node2.relative_bounds.bounds = gfx::RectF(20, 20, 20, 20);
node2.SetName("Name2");
root.child_ids.push_back(node2.id);
@@ -512,6 +522,7 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleHitTestDoesNotLoopForever) {
AXNodeData node1;
node1.id = 2;
+ node1.role = ax::mojom::Role::kGenericContainer;
node1.relative_bounds.bounds = gfx::RectF(0, 0, 10, 10);
node1.SetName("Name1");
root.child_ids.push_back(node1.id);
@@ -535,6 +546,7 @@ TEST_F(AXPlatformNodeWinTest, IAccessibleHitTestDoesNotLoopForever) {
TEST_F(AXPlatformNodeWinTest, IAccessibleName) {
AXNodeData root;
root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
root.SetName("Name");
Init(root);
@@ -1928,6 +1940,103 @@ TEST_F(AXPlatformNodeWinTest, IAccessible2GetNRelations) {
// TODO(dougt): Try adding one more relation.
}
+TEST_F(AXPlatformNodeWinTest,
+ IAccessible2TestPopupForRelationMapsToControlledByRelation) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+
+ AXNodeData child1;
+ child1.id = 2;
+ child1.role = ax::mojom::Role::kTextField;
+ child1.AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, {3});
+ root.child_ids.push_back(2);
+
+ // Add listbox that is popup for the textfield.
+ AXNodeData child2;
+ child2.id = 3;
+ child2.role = ax::mojom::Role::kListBox;
+ child2.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, 2);
+ root.child_ids.push_back(3);
+
+ Init(root, child1, child2);
+ ComPtr<IAccessible> root_iaccessible(GetRootIAccessible());
+ ComPtr<IAccessible2> root_iaccessible2 = ToIAccessible2(root_iaccessible);
+
+ ComPtr<IDispatch> result;
+ EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(1), &result));
+ ComPtr<IAccessible2> ax_child1;
+ EXPECT_EQ(S_OK, result.As(&ax_child1));
+ result.Reset();
+
+ EXPECT_EQ(S_OK, root_iaccessible2->get_accChild(ScopedVariant(2), &result));
+ ComPtr<IAccessible2> ax_child2;
+ EXPECT_EQ(S_OK, result.As(&ax_child2));
+ result.Reset();
+
+ LONG n_relations = 0;
+ LONG n_targets = 0;
+ ScopedBstr relation_type;
+ ComPtr<IAccessibleRelation> controls_relation;
+ ComPtr<IAccessibleRelation> controlled_by_relation;
+ ComPtr<IUnknown> target;
+
+ EXPECT_HRESULT_SUCCEEDED(ax_child1->get_nRelations(&n_relations));
+ EXPECT_EQ(1, n_relations);
+
+ EXPECT_HRESULT_SUCCEEDED(ax_child1->get_relation(0, &controls_relation));
+
+ EXPECT_HRESULT_SUCCEEDED(
+ controls_relation->get_relationType(relation_type.Receive()));
+ EXPECT_EQ(L"controllerFor", base::string16(relation_type.Get()));
+
+ relation_type.Reset();
+
+ EXPECT_HRESULT_SUCCEEDED(controls_relation->get_nTargets(&n_targets));
+ EXPECT_EQ(1, n_targets);
+
+ EXPECT_HRESULT_SUCCEEDED(controls_relation->get_target(0, &target));
+ target.Reset();
+
+ controls_relation.Reset();
+
+ // Test the controlled by relation, mapped from the popup for relation.
+ EXPECT_HRESULT_SUCCEEDED(ax_child2->get_nRelations(&n_relations));
+ // The test is currently outsmarting us, and automatically mapping the
+ // reverse relation in addition to mapping the popup for -> controlled by.
+ // Therefore, the same relation will exist twice in this test, which
+ // actually shows that the popup for -> controlled by relation is working.
+ // As a result, both relations should have the same result in this test.
+ EXPECT_EQ(2, n_relations);
+
+ // Both relations should have the same result in this test.
+ EXPECT_HRESULT_SUCCEEDED(ax_child2->get_relation(0, &controlled_by_relation));
+ EXPECT_HRESULT_SUCCEEDED(
+ controlled_by_relation->get_relationType(relation_type.Receive()));
+ EXPECT_EQ(L"controlledBy", base::string16(relation_type.Get()));
+ relation_type.Reset();
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_nTargets(&n_targets));
+ EXPECT_EQ(1, n_targets);
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_target(0, &target));
+ target.Reset();
+ controlled_by_relation.Reset();
+
+ // Both relations should have the same result in this test.
+ EXPECT_HRESULT_SUCCEEDED(ax_child2->get_relation(1, &controlled_by_relation));
+ EXPECT_HRESULT_SUCCEEDED(
+ controlled_by_relation->get_relationType(relation_type.Receive()));
+ EXPECT_EQ(L"controlledBy", base::string16(relation_type.Get()));
+ relation_type.Reset();
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_nTargets(&n_targets));
+ EXPECT_EQ(1, n_targets);
+
+ EXPECT_HRESULT_SUCCEEDED(controlled_by_relation->get_target(0, &target));
+ target.Reset();
+}
+
TEST_F(AXPlatformNodeWinTest, DISABLED_TestRelationTargetsOfType) {
AXNodeData root;
root.id = 1;
@@ -3474,6 +3583,124 @@ TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeaders) {
EXPECT_EQ(nullptr, safearray.Get());
}
+TEST_F(AXPlatformNodeWinTest, ITableProviderGetColumnHeadersMultipleHeaders) {
+ // Build a table like this:
+ // header_r1c1 | header_r1c2 | header_r1c3
+ // cell_r2c1 | cell_r2c2 | cell_r2c3
+ // cell_r3c1 | header_r3c2 |
+
+ // <table>
+ // <tr aria-label="row1">
+ // <th>header_r1c1</th>
+ // <th>header_r1c2</th>
+ // <th>header_r1c3</th>
+ // </tr>
+ // <tr aria-label="row2">
+ // <td>cell_r2c1</td>
+ // <td>cell_r2c2</td>
+ // <td>cell_r2c3</td>
+ // </tr>
+ // <tr aria-label="row3">
+ // <td>cell_r3c1</td>
+ // <th>header_r3c2</th>
+ // </tr>
+ // </table>
+
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kTable;
+
+ AXNodeData row1;
+ row1.id = 2;
+ row1.role = ax::mojom::Role::kRow;
+ root.child_ids.push_back(row1.id);
+
+ AXNodeData row2;
+ row2.id = 3;
+ row2.role = ax::mojom::Role::kRow;
+ root.child_ids.push_back(row2.id);
+
+ AXNodeData row3;
+ row3.id = 4;
+ row3.role = ax::mojom::Role::kRow;
+ root.child_ids.push_back(row3.id);
+
+ // <tr aria-label="row1">
+ // <th>header_r1c1</th> <th>header_r1c2</th> <th>header_r1c3</th>
+ // </tr>
+ AXNodeData header_r1c1;
+ header_r1c1.id = 5;
+ header_r1c1.role = ax::mojom::Role::kColumnHeader;
+ header_r1c1.SetName(L"header_r1c1");
+ row1.child_ids.push_back(header_r1c1.id);
+
+ AXNodeData header_r1c2;
+ header_r1c2.id = 6;
+ header_r1c2.role = ax::mojom::Role::kColumnHeader;
+ header_r1c2.SetName(L"header_r1c2");
+ row1.child_ids.push_back(header_r1c2.id);
+
+ AXNodeData header_r1c3;
+ header_r1c3.id = 7;
+ header_r1c3.role = ax::mojom::Role::kColumnHeader;
+ header_r1c3.SetName(L"header_r1c3");
+ row1.child_ids.push_back(header_r1c3.id);
+
+ // <tr aria-label="row2">
+ // <td>cell_r2c1</td> <td>cell_r2c2</td> <td>cell_r2c3</td>
+ // </tr>
+ AXNodeData cell_r2c1;
+ cell_r2c1.id = 8;
+ cell_r2c1.role = ax::mojom::Role::kCell;
+ cell_r2c1.SetName(L"cell_r2c1");
+ row2.child_ids.push_back(cell_r2c1.id);
+
+ AXNodeData cell_r2c2;
+ cell_r2c2.id = 9;
+ cell_r2c2.role = ax::mojom::Role::kCell;
+ cell_r2c2.SetName(L"cell_r2c2");
+ row2.child_ids.push_back(cell_r2c2.id);
+
+ AXNodeData cell_r2c3;
+ cell_r2c3.id = 10;
+ cell_r2c3.role = ax::mojom::Role::kCell;
+ cell_r2c3.SetName(L"cell_r2c3");
+ row2.child_ids.push_back(cell_r2c3.id);
+
+ // <tr aria-label="row3">
+ // <td>cell_r3c1</td> <th>header_r3c2</th>
+ // </tr>
+ AXNodeData cell_r3c1;
+ cell_r3c1.id = 11;
+ cell_r3c1.role = ax::mojom::Role::kCell;
+ cell_r3c1.SetName(L"cell_r3c1");
+ row3.child_ids.push_back(cell_r3c1.id);
+
+ AXNodeData header_r3c2;
+ header_r3c2.id = 12;
+ header_r3c2.role = ax::mojom::Role::kColumnHeader;
+ header_r3c2.SetName(L"header_r3c2");
+ row3.child_ids.push_back(header_r3c2.id);
+
+ Init(root, row1, row2, row3, header_r1c1, header_r1c2, header_r1c3, cell_r2c1,
+ cell_r2c2, cell_r2c3, cell_r3c1, header_r3c2);
+
+ ComPtr<ITableProvider> root_itableprovider(
+ QueryInterfaceFromNode<ITableProvider>(GetRootAsAXNode()));
+
+ base::win::ScopedSafearray safearray;
+ EXPECT_HRESULT_SUCCEEDED(
+ root_itableprovider->GetColumnHeaders(safearray.Receive()));
+ EXPECT_NE(nullptr, safearray.Get());
+
+ // Validate that we retrieve all column headers of the table and in the order
+ // below.
+ std::vector<std::wstring> expected_names = {L"header_r1c1", L"header_r1c2",
+ L"header_r3c2", L"header_r1c3"};
+ EXPECT_UIA_ELEMENT_ARRAY_BSTR_EQ(safearray.Get(), UIA_NamePropertyId,
+ expected_names);
+}
+
TEST_F(AXPlatformNodeWinTest, ITableProviderGetRowHeaders) {
AXNodeData root;
root.id = 1;
@@ -3690,6 +3917,7 @@ TEST_F(AXPlatformNodeWinTest, IA2GetAttribute) {
TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
AXNodeData root;
+ root.role = ax::mojom::Role::kList;
root.SetName("fake name");
root.AddStringAttribute(ax::mojom::StringAttribute::kAccessKey, "Ctrl+Q");
root.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en-us");
@@ -3699,7 +3927,6 @@ TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
root.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 2);
root.AddIntAttribute(ax::mojom::IntAttribute::kInvalidState, 1);
root.id = 1;
- root.role = ax::mojom::Role::kList;
AXNodeData child1;
child1.id = 2;
@@ -3725,7 +3952,8 @@ TEST_F(AXPlatformNodeWinTest, UIAGetPropertySimple) {
EXPECT_UIA_BSTR_EQ(root_node, UIA_AriaPropertiesPropertyId,
L"readonly=true;expanded=false;multiline=false;"
L"multiselectable=false;required=false;setsize=2");
- EXPECT_UIA_BSTR_EQ(root_node, UIA_CulturePropertyId, L"en-us");
+ constexpr int en_us_lcid = 1033;
+ EXPECT_UIA_INT_EQ(root_node, UIA_CulturePropertyId, en_us_lcid);
EXPECT_UIA_BSTR_EQ(root_node, UIA_NamePropertyId, L"fake name");
EXPECT_UIA_INT_EQ(root_node, UIA_ControlTypePropertyId,
int{UIA_ListControlTypeId});
@@ -3814,6 +4042,93 @@ TEST_F(AXPlatformNodeWinTest, UIAGetPropertyValueIsDialog) {
UIA_IsDialogPropertyId, true);
}
+TEST_F(AXPlatformNodeWinTest,
+ UIAGetPropertyValueIsControlElementIgnoredInvisible) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kRootWebArea;
+ root.child_ids = {2, 3, 4, 5, 6, 7, 8};
+
+ AXNodeData normal_button;
+ normal_button.id = 2;
+ normal_button.role = ax::mojom::Role::kButton;
+
+ AXNodeData ignored_button;
+ ignored_button.id = 3;
+ ignored_button.role = ax::mojom::Role::kButton;
+ ignored_button.AddState(ax::mojom::State::kIgnored);
+
+ AXNodeData invisible_button;
+ invisible_button.id = 4;
+ invisible_button.role = ax::mojom::Role::kButton;
+ invisible_button.AddState(ax::mojom::State::kInvisible);
+
+ AXNodeData invisible_focusable_button;
+ invisible_focusable_button.id = 5;
+ invisible_focusable_button.role = ax::mojom::Role::kButton;
+ invisible_focusable_button.AddState(ax::mojom::State::kInvisible);
+ invisible_focusable_button.AddState(ax::mojom::State::kFocusable);
+
+ AXNodeData focusable_generic_container;
+ focusable_generic_container.id = 6;
+ focusable_generic_container.role = ax::mojom::Role::kGenericContainer;
+ focusable_generic_container.AddState(ax::mojom::State::kFocusable);
+
+ AXNodeData ignored_focusable_generic_container;
+ ignored_focusable_generic_container.id = 7;
+ ignored_focusable_generic_container.role = ax::mojom::Role::kGenericContainer;
+ ignored_focusable_generic_container.AddState(ax::mojom::State::kIgnored);
+ focusable_generic_container.AddState(ax::mojom::State::kFocusable);
+
+ AXNodeData invisible_focusable_generic_container;
+ invisible_focusable_generic_container.id = 8;
+ invisible_focusable_generic_container.role =
+ ax::mojom::Role::kGenericContainer;
+ invisible_focusable_generic_container.AddState(ax::mojom::State::kInvisible);
+ invisible_focusable_generic_container.AddState(ax::mojom::State::kFocusable);
+
+ Init(root, normal_button, ignored_button, invisible_button,
+ invisible_focusable_button, focusable_generic_container,
+ ignored_focusable_generic_container,
+ invisible_focusable_generic_container);
+
+ // Turn on web content mode for the AXTree.
+ TestAXNodeWrapper::SetGlobalIsWebContent(true);
+
+ // Normal button (id=2), no invisible or ignored state set. Should be a
+ // control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(0),
+ UIA_IsControlElementPropertyId, true);
+
+ // Button with ignored state (id=3). Should not be a control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(1),
+ UIA_IsControlElementPropertyId, false);
+
+ // Button with invisible state (id=4). Should not be a control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(2),
+ UIA_IsControlElementPropertyId, false);
+
+ // Button with invisible state, but focusable (id=5). Should not be a control
+ // element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(3),
+ UIA_IsControlElementPropertyId, false);
+
+ // Generic container, focusable (id=6). Should be a control
+ // element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(4),
+ UIA_IsControlElementPropertyId, true);
+
+ // Generic container, ignored but focusable (id=7). Should not be a control
+ // element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(5),
+ UIA_IsControlElementPropertyId, false);
+
+ // Generic container, invisible and ignored, but focusable (id=8). Should not
+ // be a control element.
+ EXPECT_UIA_BOOL_EQ(GetIRawElementProviderSimpleFromChildIndex(6),
+ UIA_IsControlElementPropertyId, false);
+}
+
TEST_F(AXPlatformNodeWinTest, UIAGetControllerForPropertyId) {
AXNodeData root;
root.id = 1;
@@ -6251,6 +6566,65 @@ TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderGetSelectionContainer) {
EXPECT_EQ(container, container_provider);
}
+TEST_F(AXPlatformNodeWinTest, ISelectionItemProviderSelectFollowFocus) {
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kTabList;
+
+ AXNodeData tab1;
+ tab1.id = 2;
+ tab1.role = ax::mojom::Role::kTab;
+ tab1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false);
+ tab1.SetDefaultActionVerb(ax::mojom::DefaultActionVerb::kClick);
+ root.child_ids.push_back(tab1.id);
+
+ Init(root, tab1);
+
+ auto* tab1_node = GetRootAsAXNode()->children()[0];
+ ComPtr<IRawElementProviderSimple> tab1_raw_element_provider_simple =
+ QueryInterfaceFromNode<IRawElementProviderSimple>(tab1_node);
+ ASSERT_NE(nullptr, tab1_raw_element_provider_simple.Get());
+
+ ComPtr<IRawElementProviderFragment> tab1_raw_element_provider_fragment =
+ IRawElementProviderFragmentFromNode(tab1_node);
+ ASSERT_NE(nullptr, tab1_raw_element_provider_fragment.Get());
+
+ ComPtr<ISelectionItemProvider> tab1_selection_item_provider;
+ EXPECT_HRESULT_SUCCEEDED(tab1_raw_element_provider_simple->GetPatternProvider(
+ UIA_SelectionItemPatternId, &tab1_selection_item_provider));
+ ASSERT_NE(nullptr, tab1_selection_item_provider.Get());
+
+ BOOL is_selected;
+ // Before setting focus to "tab1", validate that "tab1" has selected=false.
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_FALSE(is_selected);
+
+ // Setting focus on "tab1" will result in selected=true.
+ tab1_raw_element_provider_fragment->SetFocus();
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+
+ // Verify that we can still trigger action::kDoDefault through Select().
+ EXPECT_HRESULT_SUCCEEDED(tab1_selection_item_provider->Select());
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+ EXPECT_EQ(tab1_node, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
+ // Verify that after Select(), "tab1" is still selected.
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+
+ // Since last Select() performed |action::kDoDefault|, which set
+ // |kSelectedFromFocus| to false. Calling Select() again will not perform
+ // |action::kDoDefault| again.
+ TestAXNodeWrapper::SetNodeFromLastDefaultAction(nullptr);
+ EXPECT_HRESULT_SUCCEEDED(tab1_selection_item_provider->Select());
+ tab1_selection_item_provider->get_IsSelected(&is_selected);
+ EXPECT_TRUE(is_selected);
+ // Verify that after Select(),|action::kDoDefault| was not triggered on
+ // "tab1".
+ EXPECT_EQ(nullptr, TestAXNodeWrapper::GetNodeFromLastDefaultAction());
+}
+
TEST_F(AXPlatformNodeWinTest, IValueProvider_GetValue) {
AXNodeData root;
root.id = 1;
@@ -6512,4 +6886,120 @@ TEST_F(AXPlatformNodeWinTest, SanitizeStringAttributeForIA2) {
EXPECT_EQ("\\\\\\:\\=\\,\\;", output);
}
+//
+// IChromeAccessible tests
+//
+
+class TestIChromeAccessibleDelegate
+ : public CComObjectRootEx<CComMultiThreadModel>,
+ public IDispatchImpl<IChromeAccessibleDelegate> {
+ using IDispatchImpl::Invoke;
+
+ public:
+ BEGIN_COM_MAP(TestIChromeAccessibleDelegate)
+ COM_INTERFACE_ENTRY(IChromeAccessibleDelegate)
+ END_COM_MAP()
+
+ TestIChromeAccessibleDelegate() = default;
+ ~TestIChromeAccessibleDelegate() = default;
+
+ std::string WaitForBulkFetchResult(LONG expected_request_id) {
+ if (bulk_fetch_result_.empty())
+ WaitUsingRunLoop();
+ CHECK_EQ(expected_request_id, request_id_);
+ return bulk_fetch_result_;
+ }
+
+ IUnknown* WaitForHitTestResult(LONG expected_request_id) {
+ if (!hit_test_result_)
+ WaitUsingRunLoop();
+ CHECK_EQ(expected_request_id, request_id_);
+ return hit_test_result_.Get();
+ }
+
+ private:
+ void WaitUsingRunLoop() {
+ base::RunLoop run_loop;
+ run_loop_quit_closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+
+ IFACEMETHODIMP put_bulkFetchResult(LONG request_id, BSTR result) override {
+ bulk_fetch_result_ = base::WideToUTF8(result);
+ request_id_ = request_id;
+ if (run_loop_quit_closure_)
+ run_loop_quit_closure_.Run();
+ return S_OK;
+ }
+
+ IFACEMETHODIMP put_hitTestResult(LONG request_id, IUnknown* result) override {
+ hit_test_result_ = result;
+ request_id_ = request_id;
+ if (run_loop_quit_closure_)
+ run_loop_quit_closure_.Run();
+ return S_OK;
+ }
+
+ std::string bulk_fetch_result_;
+ ComPtr<IUnknown> hit_test_result_;
+ LONG request_id_ = 0;
+ base::RepeatingClosure run_loop_quit_closure_;
+};
+
+// http://crbug.com/1087206: failing on Win7 builders.
+TEST_F(AXPlatformNodeWinTest, DISABLED_BulkFetch) {
+ base::test::SingleThreadTaskEnvironment task_environment;
+ AXNodeData root;
+ root.id = 1;
+ root.role = ax::mojom::Role::kScrollBar;
+
+ Init(root);
+
+ ComPtr<IChromeAccessible> chrome_accessible =
+ QueryInterfaceFromNode<IChromeAccessible>(GetRootAsAXNode());
+
+ CComObject<TestIChromeAccessibleDelegate>* delegate = nullptr;
+ ASSERT_HRESULT_SUCCEEDED(
+ CComObject<TestIChromeAccessibleDelegate>::CreateInstance(&delegate));
+ ScopedBstr input_bstr(L"Potato");
+ chrome_accessible->get_bulkFetch(input_bstr.Get(), 99, delegate);
+ std::string response = delegate->WaitForBulkFetchResult(99);
+
+ // Note: base::JSONReader is fine for unit tests, but production code
+ // that parses untrusted JSON should always use DataDecoder instead.
+ base::Optional<base::Value> result =
+ base::JSONReader::Read(response, base::JSON_ALLOW_TRAILING_COMMAS);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result->FindKey("role"));
+ ASSERT_EQ("scrollBar", result->FindKey("role")->GetString());
+}
+
+TEST_F(AXPlatformNodeWinTest, AsyncHitTest) {
+ base::test::SingleThreadTaskEnvironment task_environment;
+ AXNodeData root;
+ root.id = 50;
+ root.role = ax::mojom::Role::kArticle;
+ root.relative_bounds.bounds = gfx::RectF(0, 0, 800, 600);
+
+ Init(root);
+
+ ComPtr<IChromeAccessible> chrome_accessible =
+ QueryInterfaceFromNode<IChromeAccessible>(GetRootAsAXNode());
+
+ CComObject<TestIChromeAccessibleDelegate>* delegate = nullptr;
+ ASSERT_HRESULT_SUCCEEDED(
+ CComObject<TestIChromeAccessibleDelegate>::CreateInstance(&delegate));
+ ScopedBstr input_bstr(L"Potato");
+ chrome_accessible->get_hitTest(400, 300, 12345, delegate);
+ ComPtr<IUnknown> result = delegate->WaitForHitTestResult(12345);
+ ComPtr<IAccessible2> accessible = ToIAccessible2(result);
+ LONG result_unique_id = 0;
+ ASSERT_HRESULT_SUCCEEDED(accessible->get_uniqueID(&result_unique_id));
+ ComPtr<IAccessible2> root_accessible =
+ QueryInterfaceFromNode<IAccessible2>(GetRootAsAXNode());
+ LONG root_unique_id = 0;
+ ASSERT_HRESULT_SUCCEEDED(root_accessible->get_uniqueID(&root_unique_id));
+ ASSERT_EQ(root_unique_id, result_unique_id);
+}
+
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
index 400be7c18c3..1fb54910d86 100644
--- a/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
+++ b/chromium/ui/accessibility/platform/ax_platform_node_win_unittest.h
@@ -10,6 +10,7 @@
#include <memory>
#include <unordered_set>
+#include "base/test/scoped_feature_list.h"
#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
#include "ui/base/win/accessibility_misc_utils.h"
@@ -98,6 +99,8 @@ class AXPlatformNodeWinTest : public AXPlatformNodeTest {
std::unique_ptr<AXFragmentRootWin> ax_fragment_root_;
std::unique_ptr<TestFragmentRootDelegate> test_fragment_root_delegate_;
+
+ base::test::ScopedFeatureList scoped_feature_list_;
};
} // namespace ui
diff --git a/chromium/ui/accessibility/platform/ax_platform_relation_win.cc b/chromium/ui/accessibility/platform/ax_platform_relation_win.cc
index 5f03ec806bf..eee92e3b9cb 100644
--- a/chromium/ui/accessibility/platform/ax_platform_relation_win.cc
+++ b/chromium/ui/accessibility/platform/ax_platform_relation_win.cc
@@ -43,6 +43,12 @@ base::string16 GetIA2RelationFromIntAttr(ax::mojom::IntAttribute attribute) {
return IA2_RELATION_MEMBER_OF;
case ax::mojom::IntAttribute::kErrormessageId:
return IA2_RELATION_ERROR;
+ case ax::mojom::IntAttribute::kPopupForId:
+ // Map "popup for" to "controlled by".
+ // Unlike ATK there is no special IA2 popup-for relationship, but it can
+ // be exposed via the controlled by relation, which is also computed for
+ // content as the reverse of the controls relationship.
+ return IA2_RELATION_CONTROLLED_BY;
default:
break;
}
diff --git a/chromium/ui/accessibility/platform/ichromeaccessible.idl b/chromium/ui/accessibility/platform/ichromeaccessible.idl
new file mode 100644
index 00000000000..f3567d1ba74
--- /dev/null
+++ b/chromium/ui/accessibility/platform/ichromeaccessible.idl
@@ -0,0 +1,64 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import "objidl.idl";
+import "oaidl.idl";
+
+const long DISPID_CHROME_BULK_FETCH = -1600;
+const long DISPID_CHROME_ON_BULK_FETCH_RESULT = -1601;
+const long DISPID_CHROME_HIT_TEST = -1602;
+const long DISPID_CHROME_ON_HIT_TEST_RESULT = -1603;
+
+// Interface to be implemented by the client that calls IChromeAccessible.
+// For every method in IChromeAccessible, there's a corresponding response
+// method in IChromeAccessibleDelegate.
+[object, uuid(0e3edc14-79f4-413f-b854-d3b6860d74a2), pointer_default(unique)]
+interface IChromeAccessibleDelegate : IUnknown
+{
+ [propput, id(DISPID_CHROME_ON_BULK_FETCH_RESULT)] HRESULT bulkFetchResult(
+ [in] LONG requestID,
+ [in] BSTR resultJson
+ );
+
+ [propput, id(DISPID_CHROME_ON_HIT_TEST_RESULT)] HRESULT hitTestResult(
+ [in] LONG requestID,
+ [in] IUnknown* result
+ );
+};
+
+// Chrome-specific interface exposed on every IAccessible object.
+//
+// This interface is EXPERIMENTAL and only available behind a flag.
+// Run Chrome with --enable-features=IChromeAccessible to use it.
+//
+// Do not depend on this interface remaining stable! It's only designed
+// for prototyping ideas, and anything that's stabilized should move to
+// an open standard API.
+[object, uuid(6175bd95-3b2e-4ebc-bc51-9cab782bec92), pointer_default(unique)]
+interface IChromeAccessible : IUnknown
+{
+ // TODO(crbug.com/1083834): Fully document this interface.
+ // Fetch multiple accessibility properties of one or more accessibility
+ // nodes as JSON. This method is asynchronous; the result is returned
+ // by calling put_bulkFetchResult on |delegate|. The client can pass any
+ // valid LONG as requestID and the same value will be passed to
+ // put_bulkFetchResult to enable matching of requests and responses.
+ [propget, id(DISPID_CHROME_BULK_FETCH)] HRESULT bulkFetch(
+ [in] BSTR inputJson,
+ [in] LONG requestID,
+ [in] IChromeAccessibleDelegate* delegate
+ );
+
+ // Hit-test the given pixel in screen physical pixel coordinates.
+ // This method is asynchronous; the result is returned
+ // by calling put_hitTestResult on |delegate|. The client can pass any
+ // valid LONG as requestID and the same value will be passed to
+ // put_hitTestResult to enable matching of requests and responses.
+ [propget, id(DISPID_CHROME_HIT_TEST)] HRESULT hitTest(
+ [in] LONG screenPhysicalPixelX,
+ [in] LONG screenPhysicalPixelY,
+ [in] LONG requestID,
+ [in] IChromeAccessibleDelegate* delegate
+ );
+};
diff --git a/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc b/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
index 85283d36f99..873ab5c1985 100644
--- a/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/chromium/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -99,6 +99,11 @@ const AXNode* TestAXNodeWrapper::GetNodeFromLastDefaultAction() {
}
// static
+void TestAXNodeWrapper::SetNodeFromLastDefaultAction(AXNode* node) {
+ g_node_from_last_default_action = node;
+}
+
+// static
std::unique_ptr<base::AutoReset<float>> TestAXNodeWrapper::SetScaleFactor(
float value) {
return std::make_unique<base::AutoReset<float>>(&g_scale_factor, value);
@@ -459,30 +464,22 @@ base::Optional<bool> TestAXNodeWrapper::GetTableHasColumnOrRowHeaderNode()
return node_->GetTableHasColumnOrRowHeaderNode();
}
-std::vector<int32_t> TestAXNodeWrapper::GetColHeaderNodeIds() const {
- std::vector<int32_t> header_ids;
- node_->GetTableCellColHeaderNodeIds(&header_ids);
- return header_ids;
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetColHeaderNodeIds() const {
+ return node_->GetTableColHeaderNodeIds();
}
-std::vector<int32_t> TestAXNodeWrapper::GetColHeaderNodeIds(
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetColHeaderNodeIds(
int col_index) const {
- std::vector<int32_t> header_ids;
- node_->GetTableColHeaderNodeIds(col_index, &header_ids);
- return header_ids;
+ return node_->GetTableColHeaderNodeIds(col_index);
}
-std::vector<int32_t> TestAXNodeWrapper::GetRowHeaderNodeIds() const {
- std::vector<int32_t> header_ids;
- node_->GetTableCellRowHeaderNodeIds(&header_ids);
- return header_ids;
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetRowHeaderNodeIds() const {
+ return node_->GetTableCellRowHeaderNodeIds();
}
-std::vector<int32_t> TestAXNodeWrapper::GetRowHeaderNodeIds(
+std::vector<AXNode::AXID> TestAXNodeWrapper::GetRowHeaderNodeIds(
int row_index) const {
- std::vector<int32_t> header_ids;
- node_->GetTableRowHeaderNodeIds(row_index, &header_ids);
- return header_ids;
+ return node_->GetTableRowHeaderNodeIds(row_index);
}
bool TestAXNodeWrapper::IsTableRow() const {
@@ -586,6 +583,15 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
}
case ax::mojom::Action::kDoDefault: {
+ // If a default action such as a click is performed on an element, it
+ // could result in a selected state change. In which case, the element's
+ // selected state no longer comes from focus action, so we should set
+ // |kSelectedFromFocus| to false.
+ if (GetData().HasBoolAttribute(
+ ax::mojom::BoolAttribute::kSelectedFromFocus))
+ ReplaceBoolAttribute(ax::mojom::BoolAttribute::kSelectedFromFocus,
+ false);
+
switch (GetData().role) {
case ax::mojom::Role::kListBoxOption:
case ax::mojom::Role::kCell: {
@@ -611,7 +617,7 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
default:
break;
}
- g_node_from_last_default_action = node_;
+ SetNodeFromLastDefaultAction(node_);
return true;
}
@@ -636,9 +642,21 @@ bool TestAXNodeWrapper::AccessibilityPerformAction(
return true;
}
- case ax::mojom::Action::kFocus:
+ case ax::mojom::Action::kFocus: {
g_focused_node_in_tree[tree_] = node_;
+
+ // The platform has select follows focus behavior:
+ // https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_selection_follows_focus
+ // For test purpose, we support select follows focus for all elements, and
+ // not just single-selection container elements.
+ if (SupportsSelected(GetData().role)) {
+ ReplaceBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
+ ReplaceBoolAttribute(ax::mojom::BoolAttribute::kSelectedFromFocus,
+ true);
+ }
+
return true;
+ }
case ax::mojom::Action::kShowContextMenu:
g_node_from_last_show_context_menu = node_;
diff --git a/chromium/ui/accessibility/platform/test_ax_node_wrapper.h b/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
index 2f0998c7bab..4cd51d5874c 100644
--- a/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/chromium/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -41,6 +41,10 @@ class TestAXNodeWrapper : public AXPlatformNodeDelegateBase {
// called from for testing.
static const AXNode* GetNodeFromLastDefaultAction();
+ // Set the last node which AccessibilityPerformAction default action was
+ // called for testing.
+ static void SetNodeFromLastDefaultAction(AXNode* node);
+
// Set a global scale factor for testing.
static std::unique_ptr<base::AutoReset<float>> SetScaleFactor(float value);
diff --git a/chromium/ui/accessibility/platform/uia_registrar_win.cc b/chromium/ui/accessibility/platform/uia_registrar_win.cc
new file mode 100644
index 00000000000..bd6ca8f56aa
--- /dev/null
+++ b/chromium/ui/accessibility/platform/uia_registrar_win.cc
@@ -0,0 +1,50 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/accessibility/platform/uia_registrar_win.h"
+#include <wrl/implements.h>
+#include "base/stl_util.h"
+
+namespace ui {
+
+UiaRegistrarWin::UiaRegistrarWin() {
+ // Create the registrar object and get the IUIAutomationRegistrar
+ // interface pointer.
+ Microsoft::WRL::ComPtr<IUIAutomationRegistrar> registrar;
+ if (FAILED(CoCreateInstance(CLSID_CUIAutomationRegistrar, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar,
+ &registrar)))
+ return;
+
+ // Register the custom UIA property that represents the unique id of an UIA
+ // element which also matches its corresponding IA2 element's unique id.
+ UIAutomationPropertyInfo unique_id_property_info = {
+ kUiaPropertyUniqueIdGuid, L"UniqueId", UIAutomationType_String};
+ registrar->RegisterProperty(&unique_id_property_info,
+ &uia_unique_id_property_id_);
+
+ // Register the custom UIA event that represents the test end event for the
+ // UIA test suite.
+ UIAutomationEventInfo test_complete_event_info = {
+ kUiaEventTestCompleteSentinelGuid, L"kUiaTestCompleteSentinel"};
+ registrar->RegisterEvent(&test_complete_event_info,
+ &uia_test_complete_event_id_);
+}
+
+UiaRegistrarWin::~UiaRegistrarWin() = default;
+
+PROPERTYID UiaRegistrarWin::GetUiaUniqueIdPropertyId() const {
+ return uia_unique_id_property_id_;
+}
+
+EVENTID UiaRegistrarWin::GetUiaTestCompleteEventId() const {
+ return uia_test_complete_event_id_;
+}
+
+const UiaRegistrarWin& UiaRegistrarWin::GetInstance() {
+ static base::NoDestructor<UiaRegistrarWin> instance;
+ return *instance;
+}
+
+} // namespace ui
diff --git a/chromium/ui/accessibility/platform/uia_registrar_win.h b/chromium/ui/accessibility/platform/uia_registrar_win.h
new file mode 100644
index 00000000000..53c8da4fe37
--- /dev/null
+++ b/chromium/ui/accessibility/platform/uia_registrar_win.h
@@ -0,0 +1,45 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_ACCESSIBILITY_PLATFORM_UIA_REGISTRAR_WIN_H_
+#define UI_ACCESSIBILITY_PLATFORM_UIA_REGISTRAR_WIN_H_
+
+#include <objbase.h>
+#include <uiautomation.h>
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "ui/accessibility/ax_export.h"
+
+namespace ui {
+// {3761326A-34B2-465A-835D-7A3D8F4EFB92}
+static const GUID kUiaEventTestCompleteSentinelGuid = {
+ 0x3761326a,
+ 0x34b2,
+ 0x465a,
+ {0x83, 0x5d, 0x7a, 0x3d, 0x8f, 0x4e, 0xfb, 0x92}};
+
+// {cc7eeb32-4b62-4f4c-aff6-1c2e5752ad8e}
+static const GUID kUiaPropertyUniqueIdGuid = {
+ 0xcc7eeb32,
+ 0x4b62,
+ 0x4f4c,
+ {0xaf, 0xf6, 0x1c, 0x2e, 0x57, 0x52, 0xad, 0x8e}};
+
+class AX_EXPORT UiaRegistrarWin {
+ public:
+ UiaRegistrarWin();
+ ~UiaRegistrarWin();
+ PROPERTYID GetUiaUniqueIdPropertyId() const;
+ EVENTID GetUiaTestCompleteEventId() const;
+
+ static const UiaRegistrarWin& GetInstance();
+
+ private:
+ PROPERTYID uia_unique_id_property_id_ = 0;
+ EVENTID uia_test_complete_event_id_ = 0;
+};
+
+} // namespace ui
+
+#endif // UI_ACCESSIBILITY_PLATFORM_UIA_REGISTRAR_WIN_H_