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