1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/accessibility/uia_accessibility_event_waiter.h"
6 
7 #include <algorithm>
8 #include <numeric>
9 #include <utility>
10 
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/thread_task_runner_handle.h"
15 #include "base/win/scoped_bstr.h"
16 #include "base/win/scoped_com_initializer.h"
17 #include "base/win/scoped_variant.h"
18 #include "ui/accessibility/platform/ax_platform_node_win.h"
19 #include "ui/base/win/atl_module.h"
20 
UiaAccessibilityEventWaiter(UiaAccessibilityWaiterInfo info)21 UiaAccessibilityEventWaiter::UiaAccessibilityEventWaiter(
22     UiaAccessibilityWaiterInfo info) {
23   // Create the event thread, and pump messages via |initialization_loop| until
24   // initialization is complete.
25   base::RunLoop initialization_loop;
26   base::PlatformThread::Create(0, &thread_, &thread_handle_);
27   thread_.Init(this, info, initialization_loop.QuitClosure(),
28                shutdown_loop_.QuitClosure());
29   initialization_loop.Run();
30 }
31 
~UiaAccessibilityEventWaiter()32 UiaAccessibilityEventWaiter::~UiaAccessibilityEventWaiter() {}
33 
Wait()34 void UiaAccessibilityEventWaiter::Wait() {
35   // Pump messages via |shutdown_loop_| until the thread is complete.
36   shutdown_loop_.Run();
37   base::PlatformThread::Join(thread_handle_);
38 }
39 
WaitWithTimeout(base::TimeDelta timeout)40 void UiaAccessibilityEventWaiter::WaitWithTimeout(base::TimeDelta timeout) {
41   // Pump messages via |shutdown_loop_| until the thread is complete.
42   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
43       FROM_HERE, shutdown_loop_.QuitClosure(), timeout);
44   shutdown_loop_.Run();
45   base::PlatformThread::Join(thread_handle_);
46 }
47 
Thread()48 UiaAccessibilityEventWaiter::Thread::Thread() {}
49 
~Thread()50 UiaAccessibilityEventWaiter::Thread::~Thread() {}
51 
SendShutdownSignal()52 void UiaAccessibilityEventWaiter::Thread::SendShutdownSignal() {
53   shutdown_signal_.Signal();
54 }
55 
Init(UiaAccessibilityEventWaiter * owner,const UiaAccessibilityWaiterInfo & info,base::OnceClosure initialization,base::OnceClosure shutdown)56 void UiaAccessibilityEventWaiter::Thread::Init(
57     UiaAccessibilityEventWaiter* owner,
58     const UiaAccessibilityWaiterInfo& info,
59     base::OnceClosure initialization,
60     base::OnceClosure shutdown) {
61   owner_ = owner;
62   info_ = info;
63   initialization_complete_ = std::move(initialization);
64   shutdown_complete_ = std::move(shutdown);
65 }
66 
ThreadMain()67 void UiaAccessibilityEventWaiter::Thread::ThreadMain() {
68   // UIA calls must be made on an MTA thread to prevent random timeouts.
69   base::win::ScopedCOMInitializer com_init{
70       base::win::ScopedCOMInitializer::kMTA};
71 
72   // Create an instance of the CUIAutomation class.
73   CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER,
74                    IID_PPV_ARGS(&uia_));
75   CHECK(uia_.Get());
76 
77   // Find the IUIAutomationElement for the root content window.
78   uia_->ElementFromHandle(info_.hwnd, &root_);
79   CHECK(root_.Get());
80 
81   // Create the event handler.
82   ui::win::CreateATLModuleIfNeeded();
83   CHECK(
84       SUCCEEDED(CComObject<EventHandler>::CreateInstance(&uia_event_handler_)));
85   uia_event_handler_->AddRef();
86   uia_event_handler_->Init(this, root_);
87 
88   // Create a cache request to avoid cross-thread issues when logging.
89   CHECK(SUCCEEDED(uia_->CreateCacheRequest(&cache_request_)));
90   CHECK(cache_request_.Get());
91   CHECK(SUCCEEDED(cache_request_->AddProperty(UIA_NamePropertyId)));
92   CHECK(SUCCEEDED(cache_request_->AddProperty(UIA_AriaRolePropertyId)));
93 
94   // Match AccEvent by using Raw View
95   Microsoft::WRL::ComPtr<IUIAutomationCondition> pRawCond;
96   CHECK(SUCCEEDED(uia_->get_RawViewCondition(&pRawCond)));
97   CHECK(SUCCEEDED(cache_request_->put_TreeFilter(pRawCond.Get())));
98 
99   // Subscribe to focus events.
100   uia_->AddFocusChangedEventHandler(cache_request_.Get(),
101                                     uia_event_handler_.Get());
102 
103   // Subscribe to all property-change events.
104   constexpr PROPERTYID kMinProp = UIA_RuntimeIdPropertyId;
105   constexpr PROPERTYID kMaxProp = UIA_HeadingLevelPropertyId;
106   std::array<PROPERTYID, (kMaxProp - kMinProp) + 1> property_list;
107   std::iota(property_list.begin(), property_list.end(), kMinProp);
108   uia_->AddPropertyChangedEventHandlerNativeArray(
109       root_.Get(), TreeScope::TreeScope_Subtree, cache_request_.Get(),
110       uia_event_handler_.Get(), &property_list[0], property_list.size());
111 
112   // Subscribe to all structure-change events.
113   uia_->AddStructureChangedEventHandler(root_.Get(), TreeScope_Subtree,
114                                         cache_request_.Get(),
115                                         uia_event_handler_.Get());
116 
117   // Subscribe to all automation events (except structure-change events and
118   // live-region events, which are handled elsewhere).
119   constexpr EVENTID kMinEvent = UIA_ToolTipOpenedEventId;
120   constexpr EVENTID kMaxEvent = UIA_NotificationEventId;
121   for (EVENTID event_id = kMinEvent; event_id <= kMaxEvent; ++event_id) {
122     if (event_id != UIA_StructureChangedEventId &&
123         event_id != UIA_LiveRegionChangedEventId) {
124       uia_->AddAutomationEventHandler(
125           event_id, root_.Get(), TreeScope::TreeScope_Subtree,
126           cache_request_.Get(), uia_event_handler_.Get());
127     }
128   }
129 
130   // Subscribe to live-region change events.  This must be the last event we
131   // subscribe to, because |AXFragmentRootWin| will fire events when advised of
132   // the subscription, and this can hang the test-process (on Windows 19H1+) if
133   // we're simultaneously trying to subscribe to other events.
134   uia_->AddAutomationEventHandler(
135       UIA_LiveRegionChangedEventId, root_.Get(), TreeScope::TreeScope_Subtree,
136       cache_request_.Get(), uia_event_handler_.Get());
137 
138   // Signal that initialization is complete; this will wake the main thread to
139   // start executing the test code after this waiter has been constructed.
140   std::move(initialization_complete_).Run();
141 
142   // Wait for shutdown signal.
143   shutdown_signal_.Wait();
144 
145   // Cleanup
146   uia_->RemoveAllEventHandlers();
147   uia_event_handler_->CleanUp();
148   uia_event_handler_.Reset();
149   cache_request_.Reset();
150   root_.Reset();
151   uia_.Reset();
152 
153   std::move(shutdown_complete_).Run();
154 }
155 
EventHandler()156 UiaAccessibilityEventWaiter::Thread::EventHandler::EventHandler() {}
157 
~EventHandler()158 UiaAccessibilityEventWaiter::Thread::EventHandler::~EventHandler() {}
159 
Init(UiaAccessibilityEventWaiter::Thread * owner,Microsoft::WRL::ComPtr<IUIAutomationElement> root)160 void UiaAccessibilityEventWaiter::Thread::EventHandler::Init(
161     UiaAccessibilityEventWaiter::Thread* owner,
162     Microsoft::WRL::ComPtr<IUIAutomationElement> root) {
163   owner_ = owner;
164   root_ = root;
165 }
166 
CleanUp()167 void UiaAccessibilityEventWaiter::Thread::EventHandler::CleanUp() {
168   owner_ = nullptr;
169   root_.Reset();
170 }
171 
172 HRESULT
HandleFocusChangedEvent(IUIAutomationElement * sender)173 UiaAccessibilityEventWaiter::Thread::EventHandler::HandleFocusChangedEvent(
174     IUIAutomationElement* sender) {
175   // Add focus changed event handling code here.
176   return S_OK;
177 }
178 
179 HRESULT
HandlePropertyChangedEvent(IUIAutomationElement * sender,PROPERTYID property_id,VARIANT new_value)180 UiaAccessibilityEventWaiter::Thread::EventHandler::HandlePropertyChangedEvent(
181     IUIAutomationElement* sender,
182     PROPERTYID property_id,
183     VARIANT new_value) {
184   if (owner_ &&
185       property_id ==
186           ui::AXPlatformNodeWin::MojoEventToUIAProperty(owner_->info_.event) &&
187       MatchesNameRole(sender)) {
188     owner_->SendShutdownSignal();
189   }
190   return S_OK;
191 }
192 
193 HRESULT
HandleStructureChangedEvent(IUIAutomationElement * sender,StructureChangeType change_type,SAFEARRAY * runtime_id)194 UiaAccessibilityEventWaiter::Thread::EventHandler::HandleStructureChangedEvent(
195     IUIAutomationElement* sender,
196     StructureChangeType change_type,
197     SAFEARRAY* runtime_id) {
198   // Add structure changed handling code here.
199   return S_OK;
200 }
201 
202 HRESULT
HandleAutomationEvent(IUIAutomationElement * sender,EVENTID event_id)203 UiaAccessibilityEventWaiter::Thread::EventHandler::HandleAutomationEvent(
204     IUIAutomationElement* sender,
205     EVENTID event_id) {
206   if (owner_ &&
207       event_id ==
208           ui::AXPlatformNodeWin::MojoEventToUIAEvent(owner_->info_.event) &&
209       MatchesNameRole(sender)) {
210     owner_->SendShutdownSignal();
211   }
212   return S_OK;
213 }
214 
MatchesNameRole(IUIAutomationElement * sender)215 bool UiaAccessibilityEventWaiter::Thread::EventHandler::MatchesNameRole(
216     IUIAutomationElement* sender) {
217   base::win::ScopedBstr aria_role;
218   base::win::ScopedBstr name;
219   sender->get_CachedAriaRole(aria_role.Receive());
220   sender->get_CachedName(name.Receive());
221 
222   if (base::string16(aria_role.Get(), SysStringLen(aria_role.Get())) ==
223           owner_->info_.role &&
224       base::string16(name.Get(), SysStringLen(name.Get())) ==
225           owner_->info_.name) {
226     return true;
227   }
228   return false;
229 }
230