| // Copyright 2019 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "ui/accessibility/platform/ax_fragment_root_win.h" | |
| #include <limits> | |
| #include "base/memory/raw_ptr.h" | |
| #include "base/no_destructor.h" | |
| #include "base/strings/string_number_conversions.h" | |
| #include "base/trace_event/memory_allocator_dump.h" | |
| #include "base/trace_event/memory_dump_manager.h" | |
| #include "base/trace_event/memory_dump_provider.h" | |
| #include "base/trace_event/process_memory_dump.h" | |
| #include "base/trace_event/typed_macros.h" | |
| #include "base/win/scoped_safearray.h" | |
| #include "third_party/abseil-cpp/absl/container/flat_hash_map.h" | |
| #include "ui/accessibility/platform/ax_fragment_root_delegate_win.h" | |
| #include "ui/accessibility/platform/ax_platform.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) | |
| END_COM_MAP() | |
| static Pointer Create(AXFragmentRootWin& delegate) { | |
| // Make sure ATL is initialized in this module. | |
| win::CreateATLModuleIfNeeded(); | |
| CComObject<AXFragmentRootPlatformNodeWin>* instance = nullptr; | |
| HRESULT hr = | |
| CComObject<AXFragmentRootPlatformNodeWin>::CreateInstance(&instance); | |
| CHECK(SUCCEEDED(hr)); | |
| instance->Init(delegate); | |
| instance->AddRef(); | |
| return Pointer(instance); | |
| } | |
| // | |
| // 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. | |
| if (property_id == UiaRegistrarWin::GetInstance().GetUniqueIdPropertyId() && | |
| value.vt == VT_BSTR) { | |
| 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* result_platform_node = | |
| static_cast<AXPlatformNodeWin*>(GetFromUniqueId(-ax_unique_id))) { | |
| if (start_after_element) { | |
| Microsoft::WRL::ComPtr<AXPlatformNodeWin> start_after_platform_node; | |
| if (!SUCCEEDED(start_after_element->QueryInterface( | |
| IID_PPV_ARGS(&start_after_platform_node)))) | |
| return E_INVALIDARG; | |
| // We want |result| to be nullptr if it comes before or is equal to | |
| // |start_after_element|. | |
| if (start_after_platform_node->CompareTo(*result_platform_node) >= 0) | |
| return S_OK; | |
| } | |
| return result_platform_node->QueryInterface(IID_PPV_ARGS(result)); | |
| } | |
| } | |
| 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) { | |
| default: | |
| // UIA has a built-in provider that will expose values for several | |
| // properties based on the HWND. This information is useful to someone | |
| // examining the accessibility tree using tools such as Inspect. Return | |
| // VT_EMPTY for most properties so that we don't override values from | |
| // the default provider with blank data. | |
| result->vt = VT_EMPTY; | |
| break; | |
| case UIA_IsControlElementPropertyId: | |
| case UIA_IsContentElementPropertyId: | |
| // Override IsControlElement and IsContentElement to fine tune which | |
| // fragment roots appear in the control and content views. | |
| result->vt = VT_BOOL; | |
| result->boolVal = | |
| static_cast<AXFragmentRootWin*>(GetDelegate())->IsControlElement() | |
| ? VARIANT_TRUE | |
| : VARIANT_FALSE; | |
| break; | |
| } | |
| return S_OK; | |
| } | |
| // | |
| // IRawElementProviderFragment methods. | |
| // | |
| 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)); | |
| return S_OK; | |
| } | |
| // | |
| // IRawElementProviderFragmentRoot methods. | |
| // | |
| IFACEMETHODIMP ElementProviderFromPoint( | |
| double screen_physical_pixel_x, | |
| double screen_physical_pixel_y, | |
| IRawElementProviderFragment** element_provider) override { | |
| UIA_VALIDATE_CALL_1_ARG(element_provider); | |
| WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ELEMENT_PROVIDER_FROM_POINT); | |
| WIN_ACCESSIBILITY_API_PERF_HISTOGRAM(UMA_API_ELEMENT_PROVIDER_FROM_POINT); | |
| WIN_ACCESSIBILITY_SOURCE_API_PERF_HISTOGRAM( | |
| UMA_API_ELEMENT_PROVIDER_FROM_POINT); | |
| *element_provider = nullptr; | |
| gfx::NativeViewAccessible hit_element = nullptr; | |
| // Descend the tree until we get a non-hit or can't go any further. | |
| AXPlatformNode* node_to_test = this; | |
| do { | |
| gfx::NativeViewAccessible test_result = | |
| node_to_test->GetDelegate()->HitTestSync(screen_physical_pixel_x, | |
| screen_physical_pixel_y); | |
| if (test_result != nullptr && test_result != hit_element) { | |
| hit_element = test_result; | |
| node_to_test = AXPlatformNode::FromNativeViewAccessible(test_result); | |
| } else { | |
| node_to_test = nullptr; | |
| } | |
| } while (node_to_test); | |
| if (hit_element) | |
| hit_element->QueryInterface(element_provider); | |
| return S_OK; | |
| } | |
| IFACEMETHODIMP GetFocus(IRawElementProviderFragment** focus) override { | |
| WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FOCUS); | |
| UIA_VALIDATE_CALL_1_ARG(focus); | |
| *focus = nullptr; | |
| gfx::NativeViewAccessible focused_element = nullptr; | |
| // GetFocus() can return a node at the root of a subtree, for example when | |
| // transitioning from Views into web content. In such cases we want to | |
| // continue drilling to retrieve the actual focused element. | |
| AXPlatformNode* node_to_test = this; | |
| do { | |
| gfx::NativeViewAccessible test_result = | |
| node_to_test->GetDelegate()->GetFocus(); | |
| if (test_result != nullptr && test_result != focused_element) { | |
| focused_element = test_result; | |
| node_to_test = | |
| AXPlatformNode::FromNativeViewAccessible(focused_element); | |
| } else { | |
| node_to_test = nullptr; | |
| } | |
| } while (node_to_test); | |
| if (focused_element) | |
| focused_element->QueryInterface(IID_PPV_ARGS(focus)); | |
| return S_OK; | |
| } | |
| // | |
| // IRawElementProviderAdviseEvents methods. | |
| // | |
| IFACEMETHODIMP AdviseEventAdded(EVENTID event_id, | |
| SAFEARRAY* property_ids) override { | |
| WIN_ACCESSIBILITY_API_TRACE_EVENT("AdviseEventAdded"); | |
| UIA_VALIDATE_CALL(); | |
| WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_ADDED); | |
| AXFragmentRootWin* root = static_cast<AXFragmentRootWin*>(GetDelegate()); | |
| base::win::ScopedSafearray safe_array(property_ids); | |
| absl::Cleanup release_safe_array( | |
| [&safe_array]() { (void)safe_array.Release(); }); | |
| auto lock = safe_array.CreateLockScope<VT_I4>(); | |
| root->OnEventListenerAdded(event_id, | |
| lock ? *lock : base::span<PROPERTYID>()); | |
| return S_OK; | |
| } | |
| IFACEMETHODIMP AdviseEventRemoved(EVENTID event_id, | |
| SAFEARRAY* property_ids) override { | |
| WIN_ACCESSIBILITY_API_TRACE_EVENT("AdviseEventRemoved"); | |
| UIA_VALIDATE_CALL(); | |
| WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_REMOVED); | |
| AXFragmentRootWin* root = static_cast<AXFragmentRootWin*>(GetDelegate()); | |
| base::win::ScopedSafearray safe_array(property_ids); | |
| absl::Cleanup release_safe_array( | |
| [&safe_array]() { (void)safe_array.Release(); }); | |
| auto lock = safe_array.CreateLockScope<VT_I4>(); | |
| root->OnEventListenerRemoved(event_id, | |
| lock ? *lock : base::span<PROPERTYID>()); | |
| return S_OK; | |
| } | |
| }; | |
| class AXFragmentRootMapWin { | |
| public: | |
| static AXFragmentRootMapWin& GetInstance() { | |
| static base::NoDestructor<AXFragmentRootMapWin> instance; | |
| return *instance; | |
| } | |
| void AddFragmentRoot(gfx::AcceleratedWidget widget, | |
| AXFragmentRootWin* fragment_root) { | |
| map_[widget] = fragment_root; | |
| } | |
| void RemoveFragmentRoot(gfx::AcceleratedWidget widget) { map_.erase(widget); } | |
| AXFragmentRootWin* GetFragmentRoot(gfx::AcceleratedWidget widget) const { | |
| const auto& entry = map_.find(widget); | |
| if (entry != map_.end()) | |
| return entry->second; | |
| return nullptr; | |
| } | |
| AXFragmentRootWin* GetFragmentRootParentOf( | |
| gfx::NativeViewAccessible accessible) const { | |
| for (const auto& entry : map_) { | |
| AXPlatformNodeDelegate* child = entry.second->GetChildNodeDelegate(); | |
| if (child && (child->GetNativeViewAccessible() == accessible)) | |
| return entry.second; | |
| } | |
| return nullptr; | |
| } | |
| private: | |
| absl::flat_hash_map<gfx::AcceleratedWidget, | |
| raw_ptr<AXFragmentRootWin, CtnExperimental>> | |
| map_; | |
| }; | |
| AXFragmentRootWin::AXFragmentRootWin(gfx::AcceleratedWidget widget, | |
| AXFragmentRootDelegateWin* delegate) | |
| : widget_(widget), | |
| delegate_(delegate), | |
| platform_node_(AXFragmentRootPlatformNodeWin::Create(*this)) { | |
| AXFragmentRootMapWin::GetInstance().AddFragmentRoot(widget, this); | |
| } | |
| AXFragmentRootWin::~AXFragmentRootWin() { | |
| AXFragmentRootMapWin::GetInstance().RemoveFragmentRoot(widget_); | |
| } | |
| AXFragmentRootWin* AXFragmentRootWin::GetForAcceleratedWidget( | |
| gfx::AcceleratedWidget widget) { | |
| return AXFragmentRootMapWin::GetInstance().GetFragmentRoot(widget); | |
| } | |
| // static | |
| AXFragmentRootWin* AXFragmentRootWin::GetFragmentRootParentOf( | |
| gfx::NativeViewAccessible accessible) { | |
| return AXFragmentRootMapWin::GetInstance().GetFragmentRootParentOf( | |
| accessible); | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::GetNativeViewAccessible() { | |
| if (!AXPlatformNodeWin::AreAXModeChangesPaused()) { | |
| // The fragment root is the entry point from the operating system for UI | |
| // Automation. Signal when we're asked for a platform object on it. | |
| AXPlatform::GetInstance().OnPropertiesUsedInBrowserUI(); | |
| } | |
| return static_cast<AXFragmentRootPlatformNodeWin*>(platform_node_.get()); | |
| } | |
| bool AXFragmentRootWin::IsControlElement() { | |
| return delegate_->IsAXFragmentRootAControlElement(); | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::GetParent() const { | |
| return delegate_->GetParentOfAXFragmentRoot(); | |
| } | |
| size_t AXFragmentRootWin::GetChildCount() const { | |
| return delegate_->GetChildOfAXFragmentRoot() ? 1 : 0; | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::ChildAtIndex(size_t index) const { | |
| if (index == 0) { | |
| return delegate_->GetChildOfAXFragmentRoot(); | |
| } | |
| return nullptr; | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::GetNextSibling() const { | |
| size_t child_index = GetIndexInParentOfChild(); | |
| AXPlatformNodeDelegate* parent = GetParentNodeDelegate(); | |
| if (parent && (child_index + 1) < parent->GetChildCount()) | |
| return GetParentNodeDelegate()->ChildAtIndex(child_index + 1); | |
| return nullptr; | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::GetPreviousSibling() const { | |
| size_t child_index = GetIndexInParentOfChild(); | |
| if (child_index > 0) | |
| return GetParentNodeDelegate()->ChildAtIndex(child_index - 1); | |
| return nullptr; | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::HitTestSync(int x, int y) const { | |
| AXPlatformNodeDelegate* child_delegate = GetChildNodeDelegate(); | |
| if (child_delegate) | |
| return child_delegate->HitTestSync(x, y); | |
| return nullptr; | |
| } | |
| gfx::NativeViewAccessible AXFragmentRootWin::GetFocus() const { | |
| AXPlatformNodeDelegate* child_delegate = GetChildNodeDelegate(); | |
| if (child_delegate) | |
| return child_delegate->GetFocus(); | |
| return nullptr; | |
| } | |
| AXPlatformNodeId AXFragmentRootWin::GetUniqueId() const { | |
| return unique_id_; | |
| } | |
| gfx::AcceleratedWidget | |
| AXFragmentRootWin::GetTargetForNativeAccessibilityEvent() { | |
| return widget_; | |
| } | |
| AXPlatformNode* AXFragmentRootWin::GetFromTreeIDAndNodeID( | |
| const AXTreeID& ax_tree_id, | |
| int32_t node_id) { | |
| AXPlatformNodeDelegate* child_delegate = GetChildNodeDelegate(); | |
| if (child_delegate) | |
| return child_delegate->GetFromTreeIDAndNodeID(ax_tree_id, node_id); | |
| return nullptr; | |
| } | |
| AXPlatformNodeDelegate* AXFragmentRootWin::GetParentNodeDelegate() const { | |
| gfx::NativeViewAccessible parent = delegate_->GetParentOfAXFragmentRoot(); | |
| if (parent) | |
| return AXPlatformNode::FromNativeViewAccessible(parent)->GetDelegate(); | |
| return nullptr; | |
| } | |
| AXPlatformNodeDelegate* AXFragmentRootWin::GetChildNodeDelegate() const { | |
| gfx::NativeViewAccessible child = delegate_->GetChildOfAXFragmentRoot(); | |
| if (child) | |
| return AXPlatformNode::FromNativeViewAccessible(child)->GetDelegate(); | |
| return nullptr; | |
| } | |
| void AXFragmentRootWin::OnEventListenerAdded( | |
| EVENTID event_id, | |
| base::span<const PROPERTYID> property_ids) { | |
| CHECK_LT(event_listener_count_[event_id], std::numeric_limits<int>::max()); | |
| ++event_listener_count_[event_id]; | |
| for (PROPERTYID property_id : property_ids) { | |
| CHECK_LT(property_listener_count_[property_id], | |
| std::numeric_limits<int>::max()); | |
| ++property_listener_count_[property_id]; | |
| } | |
| } | |
| void AXFragmentRootWin::OnEventListenerRemoved( | |
| EVENTID event_id, | |
| base::span<const PROPERTYID> property_ids) { | |
| auto event_it = event_listener_count_.find(event_id); | |
| if (event_it != event_listener_count_.end() && (--event_it->second) <= 0) { | |
| event_listener_count_.erase(event_it); | |
| } | |
| for (PROPERTYID property_id : property_ids) { | |
| auto prop_it = property_listener_count_.find(property_id); | |
| if (prop_it != property_listener_count_.end() && (--prop_it->second) <= 0) { | |
| property_listener_count_.erase(prop_it); | |
| } | |
| } | |
| } | |
| bool AXFragmentRootWin::HasEventListenerForEvent(EVENTID event_id) { | |
| return event_listener_count_.contains(event_id); | |
| } | |
| bool AXFragmentRootWin::HasEventListenerForProperty(PROPERTYID property_id) { | |
| return property_listener_count_.contains(property_id); | |
| } | |
| IRawElementProviderSimple* AXFragmentRootWin::GetProvider() { | |
| return static_cast<AXFragmentRootPlatformNodeWin*>(platform_node_.get()); | |
| } | |
| size_t AXFragmentRootWin::GetIndexInParentOfChild() const { | |
| AXPlatformNodeDelegate* parent = GetParentNodeDelegate(); | |
| if (!parent) | |
| return 0; | |
| AXPlatformNodeDelegate* child = GetChildNodeDelegate(); | |
| if (child) { | |
| size_t child_count = parent->GetChildCount(); | |
| for (size_t child_index = 0; child_index < child_count; child_index++) { | |
| if (AXPlatformNode::FromNativeViewAccessible( | |
| parent->ChildAtIndex(child_index)) | |
| ->GetDelegate() == child) { | |
| return child_index; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| } // namespace ui |