| // 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 "chrome/browser/ui/views/accessibility/uia_accessibility_event_waiter.h" |
| |
| #include <algorithm> |
| #include <numeric> |
| #include <utility> |
| |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/win/scoped_bstr.h" |
| #include "base/win/scoped_com_initializer.h" |
| #include "base/win/scoped_variant.h" |
| #include "ui/accessibility/platform/ax_platform_node_win.h" |
| #include "ui/base/win/atl_module.h" |
| |
| UiaAccessibilityEventWaiter::UiaAccessibilityEventWaiter( |
| UiaAccessibilityWaiterInfo info) { |
| // Create the event thread, and pump messages via |initialization_loop| until |
| // initialization is complete. |
| base::RunLoop initialization_loop; |
| base::PlatformThread::Create(0, &thread_, &thread_handle_); |
| thread_.Init(this, info, initialization_loop.QuitClosure(), |
| shutdown_loop_.QuitClosure()); |
| initialization_loop.Run(); |
| } |
| |
| UiaAccessibilityEventWaiter::~UiaAccessibilityEventWaiter() = default; |
| |
| void UiaAccessibilityEventWaiter::Wait() { |
| // Pump messages via |shutdown_loop_| until the thread is complete. |
| shutdown_loop_.Run(); |
| base::PlatformThread::Join(thread_handle_); |
| } |
| |
| void UiaAccessibilityEventWaiter::WaitWithTimeout(base::TimeDelta timeout) { |
| // Pump messages via |shutdown_loop_| until the thread is complete. |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, shutdown_loop_.QuitClosure(), timeout); |
| shutdown_loop_.Run(); |
| base::PlatformThread::Join(thread_handle_); |
| } |
| |
| UiaAccessibilityEventWaiter::Thread::Thread() = default; |
| |
| UiaAccessibilityEventWaiter::Thread::~Thread() = default; |
| |
| void UiaAccessibilityEventWaiter::Thread::SendShutdownSignal() { |
| shutdown_signal_.Signal(); |
| } |
| |
| void UiaAccessibilityEventWaiter::Thread::Init( |
| UiaAccessibilityEventWaiter* owner, |
| const UiaAccessibilityWaiterInfo& info, |
| base::OnceClosure initialization, |
| base::OnceClosure shutdown) { |
| owner_ = owner; |
| info_ = info; |
| initialization_complete_ = std::move(initialization); |
| shutdown_complete_ = std::move(shutdown); |
| } |
| |
| void UiaAccessibilityEventWaiter::Thread::ThreadMain() { |
| // UIA calls must be made on an MTA thread to prevent random timeouts. |
| base::win::ScopedCOMInitializer com_init{ |
| base::win::ScopedCOMInitializer::kMTA}; |
| |
| // Create an instance of the CUIAutomation class. |
| CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, |
| IID_PPV_ARGS(&uia_)); |
| CHECK(uia_.Get()); |
| |
| // Find the IUIAutomationElement for the root content window. |
| uia_->ElementFromHandle(info_.hwnd, &root_); |
| CHECK(root_.Get()); |
| |
| // Create the event handler. |
| ui::win::CreateATLModuleIfNeeded(); |
| CHECK( |
| SUCCEEDED(CComObject<EventHandler>::CreateInstance(&uia_event_handler_))); |
| uia_event_handler_->AddRef(); |
| uia_event_handler_->Init(this, root_); |
| |
| // Create a cache request to avoid cross-thread issues when logging. |
| CHECK(SUCCEEDED(uia_->CreateCacheRequest(&cache_request_))); |
| CHECK(cache_request_.Get()); |
| CHECK(SUCCEEDED(cache_request_->AddProperty(UIA_NamePropertyId))); |
| CHECK(SUCCEEDED(cache_request_->AddProperty(UIA_AriaRolePropertyId))); |
| |
| // Match AccEvent by using Raw View |
| Microsoft::WRL::ComPtr<IUIAutomationCondition> pRawCond; |
| CHECK(SUCCEEDED(uia_->get_RawViewCondition(&pRawCond))); |
| CHECK(SUCCEEDED(cache_request_->put_TreeFilter(pRawCond.Get()))); |
| |
| // Subscribe to focus events. |
| uia_->AddFocusChangedEventHandler(cache_request_.Get(), |
| uia_event_handler_.Get()); |
| |
| // Subscribe to all property-change events. |
| constexpr PROPERTYID kMinProp = UIA_RuntimeIdPropertyId; |
| constexpr PROPERTYID kMaxProp = UIA_HeadingLevelPropertyId; |
| std::array<PROPERTYID, (kMaxProp - kMinProp) + 1> property_list; |
| std::iota(property_list.begin(), property_list.end(), kMinProp); |
| uia_->AddPropertyChangedEventHandlerNativeArray( |
| root_.Get(), TreeScope::TreeScope_Subtree, cache_request_.Get(), |
| uia_event_handler_.Get(), &property_list[0], property_list.size()); |
| |
| // Subscribe to all structure-change events. |
| uia_->AddStructureChangedEventHandler(root_.Get(), TreeScope_Subtree, |
| cache_request_.Get(), |
| uia_event_handler_.Get()); |
| |
| // Subscribe to all automation events (except structure-change events and |
| // live-region events, which are handled elsewhere). |
| constexpr EVENTID kMinEvent = UIA_ToolTipOpenedEventId; |
| constexpr EVENTID kMaxEvent = UIA_NotificationEventId; |
| for (EVENTID event_id = kMinEvent; event_id <= kMaxEvent; ++event_id) { |
| if (event_id != UIA_StructureChangedEventId && |
| event_id != UIA_LiveRegionChangedEventId) { |
| uia_->AddAutomationEventHandler( |
| event_id, root_.Get(), TreeScope::TreeScope_Subtree, |
| cache_request_.Get(), uia_event_handler_.Get()); |
| } |
| } |
| |
| // Subscribe to live-region change events. This must be the last event we |
| // subscribe to, because |AXFragmentRootWin| will fire events when advised of |
| // the subscription, and this can hang the test-process (on Windows 19H1+) if |
| // we're simultaneously trying to subscribe to other events. |
| uia_->AddAutomationEventHandler( |
| UIA_LiveRegionChangedEventId, root_.Get(), TreeScope::TreeScope_Subtree, |
| cache_request_.Get(), uia_event_handler_.Get()); |
| |
| // Signal that initialization is complete; this will wake the main thread to |
| // start executing the test code after this waiter has been constructed. |
| std::move(initialization_complete_).Run(); |
| |
| // Wait for shutdown signal. |
| shutdown_signal_.Wait(); |
| |
| // Cleanup |
| uia_->RemoveAllEventHandlers(); |
| uia_event_handler_->CleanUp(); |
| uia_event_handler_.Reset(); |
| cache_request_.Reset(); |
| root_.Reset(); |
| uia_.Reset(); |
| |
| std::move(shutdown_complete_).Run(); |
| } |
| |
| UiaAccessibilityEventWaiter::Thread::EventHandler::EventHandler() = default; |
| |
| UiaAccessibilityEventWaiter::Thread::EventHandler::~EventHandler() = default; |
| |
| void UiaAccessibilityEventWaiter::Thread::EventHandler::Init( |
| UiaAccessibilityEventWaiter::Thread* owner, |
| Microsoft::WRL::ComPtr<IUIAutomationElement> root) { |
| owner_ = owner; |
| root_ = root; |
| } |
| |
| void UiaAccessibilityEventWaiter::Thread::EventHandler::CleanUp() { |
| owner_ = nullptr; |
| root_.Reset(); |
| } |
| |
| HRESULT |
| UiaAccessibilityEventWaiter::Thread::EventHandler::HandleFocusChangedEvent( |
| IUIAutomationElement* sender) { |
| // Add focus changed event handling code here. |
| return S_OK; |
| } |
| |
| HRESULT |
| UiaAccessibilityEventWaiter::Thread::EventHandler::HandlePropertyChangedEvent( |
| IUIAutomationElement* sender, |
| PROPERTYID property_id, |
| VARIANT new_value) { |
| if (owner_ && |
| property_id == |
| ui::AXPlatformNodeWin::MojoEventToUIAProperty(owner_->info_.event) && |
| MatchesNameRole(sender)) { |
| owner_->SendShutdownSignal(); |
| } |
| return S_OK; |
| } |
| |
| HRESULT |
| UiaAccessibilityEventWaiter::Thread::EventHandler::HandleStructureChangedEvent( |
| IUIAutomationElement* sender, |
| StructureChangeType change_type, |
| SAFEARRAY* runtime_id) { |
| // Add structure changed handling code here. |
| return S_OK; |
| } |
| |
| HRESULT |
| UiaAccessibilityEventWaiter::Thread::EventHandler::HandleAutomationEvent( |
| IUIAutomationElement* sender, |
| EVENTID event_id) { |
| if (owner_ && |
| event_id == |
| ui::AXPlatformNodeWin::MojoEventToUIAEvent(owner_->info_.event) && |
| MatchesNameRole(sender)) { |
| owner_->SendShutdownSignal(); |
| } |
| return S_OK; |
| } |
| |
| bool UiaAccessibilityEventWaiter::Thread::EventHandler::MatchesNameRole( |
| IUIAutomationElement* sender) { |
| base::win::ScopedBstr aria_role; |
| base::win::ScopedBstr name; |
| sender->get_CachedAriaRole(aria_role.Receive()); |
| sender->get_CachedName(name.Receive()); |
| |
| if (std::wstring(aria_role.Get(), SysStringLen(aria_role.Get())) == |
| owner_->info_.role && |
| std::wstring(name.Get(), SysStringLen(name.Get())) == |
| owner_->info_.name) { |
| return true; |
| } |
| return false; |
| } |