1 // Copyright (c) 2013 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.
5 #include "win8/test/ui_automation_client.h"
10 #include <uiautomation.h>
14 #include "base/bind.h"
15 #include "base/callback.h"
16 #include "base/memory/ref_counted.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_util.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/win/scoped_comptr.h"
21 #include "base/win/scoped_variant.h"
26 // The guts of the UI automation client which runs on a dedicated thread in the
27 // multi-threaded COM apartment. An instance may be constructed on any thread,
28 // but Initialize() must be invoked on a thread in the MTA.
29 class UIAutomationClient::Context
{
31 // Returns a new instance ready for initialization and use on another thread.
32 static base::WeakPtr
<Context
> Create();
34 // Deletes the instance.
35 void DeleteOnAutomationThread();
37 // Initializes the context, invoking |init_callback| via |client_runner| when
38 // done. On success, |result_callback| will eventually be called after the
39 // window has been processed. On failure, this instance self-destructs after
40 // posting |init_callback|.
42 scoped_refptr
<base::SingleThreadTaskRunner
> client_runner
,
43 base::string16 class_name
,
44 base::string16 item_name
,
45 UIAutomationClient::InitializedCallback init_callback
,
46 UIAutomationClient::ResultCallback result_callback
);
48 // Methods invoked by event handlers via weak pointers.
49 void HandleAutomationEvent(
50 base::win::ScopedComPtr
<IUIAutomationElement
> sender
,
56 // The only and only method that may be called from outside of the automation
61 HRESULT
InstallWindowObserver();
62 HRESULT
RemoveWindowObserver();
64 void HandleWindowOpen(
65 const base::win::ScopedComPtr
<IUIAutomationElement
>& window
);
67 const base::win::ScopedComPtr
<IUIAutomationElement
>& window
);
68 HRESULT
InvokeDesiredItem(
69 const base::win::ScopedComPtr
<IUIAutomationElement
>& element
);
70 HRESULT
GetInvokableItems(
71 const base::win::ScopedComPtr
<IUIAutomationElement
>& element
,
72 std::vector
<base::string16
>* choices
);
73 void CloseWindow(const base::win::ScopedComPtr
<IUIAutomationElement
>& window
);
75 base::ThreadChecker thread_checker_
;
77 // The loop on which the client itself lives.
78 scoped_refptr
<base::SingleThreadTaskRunner
> client_runner_
;
80 // The class name of the window for which the client waits.
81 base::string16 class_name_
;
83 // The name of the item to invoke.
84 base::string16 item_name_
;
86 // The consumer's result callback.
87 ResultCallback result_callback_
;
89 // The automation client.
90 base::win::ScopedComPtr
<IUIAutomation
> automation_
;
92 // A handler of Window open events.
93 base::win::ScopedComPtr
<IUIAutomationEventHandler
> event_handler_
;
95 // Weak pointers to the context are given to event handlers.
96 base::WeakPtrFactory
<UIAutomationClient::Context
> weak_ptr_factory_
;
98 DISALLOW_COPY_AND_ASSIGN(Context
);
101 class UIAutomationClient::Context::EventHandler
102 : public CComObjectRootEx
<CComMultiThreadModel
>,
103 public IUIAutomationEventHandler
{
105 BEGIN_COM_MAP(UIAutomationClient::Context::EventHandler
)
106 COM_INTERFACE_ENTRY(IUIAutomationEventHandler
)
110 virtual ~EventHandler();
112 // Initializes the object with its parent UI automation client context's
113 // message loop and pointer. Events are dispatched back to the context on
116 const scoped_refptr
<base::SingleThreadTaskRunner
>& context_runner
,
117 const base::WeakPtr
<UIAutomationClient::Context
>& context
);
119 // IUIAutomationEventHandler methods.
120 STDMETHOD(HandleAutomationEvent
)(IUIAutomationElement
* sender
,
124 // The task runner for the UI automation client context.
125 scoped_refptr
<base::SingleThreadTaskRunner
> context_runner_
;
127 // The parent UI automation client context.
128 base::WeakPtr
<UIAutomationClient::Context
> context_
;
130 DISALLOW_COPY_AND_ASSIGN(EventHandler
);
133 UIAutomationClient::Context::EventHandler::EventHandler() {}
135 UIAutomationClient::Context::EventHandler::~EventHandler() {}
137 void UIAutomationClient::Context::EventHandler::Initialize(
138 const scoped_refptr
<base::SingleThreadTaskRunner
>& context_runner
,
139 const base::WeakPtr
<UIAutomationClient::Context
>& context
) {
140 context_runner_
= context_runner
;
144 HRESULT
UIAutomationClient::Context::EventHandler::HandleAutomationEvent(
145 IUIAutomationElement
* sender
,
147 // Event handlers are invoked on an arbitrary thread in the MTA. Send the
148 // event back to the main UI automation thread for processing.
149 context_runner_
->PostTask(
151 base::Bind(&UIAutomationClient::Context::HandleAutomationEvent
, context_
,
152 base::win::ScopedComPtr
<IUIAutomationElement
>(sender
),
158 base::WeakPtr
<UIAutomationClient::Context
>
159 UIAutomationClient::Context::Create() {
160 Context
* context
= new Context();
161 return context
->weak_ptr_factory_
.GetWeakPtr();
164 void UIAutomationClient::Context::DeleteOnAutomationThread() {
165 DCHECK(thread_checker_
.CalledOnValidThread());
169 UIAutomationClient::Context::Context() : weak_ptr_factory_(this) {}
171 UIAutomationClient::Context::~Context() {
172 DCHECK(thread_checker_
.CalledOnValidThread());
174 if (event_handler_
.get()) {
175 event_handler_
= NULL
;
176 HRESULT result
= automation_
->RemoveAllEventHandlers();
177 LOG_IF(ERROR
, FAILED(result
)) << std::hex
<< result
;
181 void UIAutomationClient::Context::Initialize(
182 scoped_refptr
<base::SingleThreadTaskRunner
> client_runner
,
183 base::string16 class_name
,
184 base::string16 item_name
,
185 UIAutomationClient::InitializedCallback init_callback
,
186 UIAutomationClient::ResultCallback result_callback
) {
187 // This and all other methods must be called on the automation thread.
188 DCHECK(!client_runner
->BelongsToCurrentThread());
189 // Bind the checker to this thread.
190 thread_checker_
.DetachFromThread();
191 DCHECK(thread_checker_
.CalledOnValidThread());
193 client_runner_
= client_runner
;
194 class_name_
= class_name
;
195 item_name_
= item_name
;
196 result_callback_
= result_callback
;
198 HRESULT result
= automation_
.CreateInstance(CLSID_CUIAutomation
, NULL
,
199 CLSCTX_INPROC_SERVER
);
200 if (FAILED(result
) || !automation_
.get())
201 LOG(ERROR
) << std::hex
<< result
;
203 result
= InstallWindowObserver();
205 // Tell the client that initialization is complete.
206 client_runner_
->PostTask(FROM_HERE
, base::Bind(init_callback
, result
));
208 // Self-destruct if the overall operation failed.
213 // Installs the window observer.
214 HRESULT
UIAutomationClient::Context::InstallWindowObserver() {
215 DCHECK(thread_checker_
.CalledOnValidThread());
216 DCHECK(automation_
.get());
217 DCHECK(!event_handler_
.get());
219 HRESULT result
= S_OK
;
220 base::win::ScopedComPtr
<IUIAutomationElement
> root_element
;
221 base::win::ScopedComPtr
<IUIAutomationCacheRequest
> cache_request
;
223 // Observe the opening of all windows.
224 result
= automation_
->GetRootElement(root_element
.Receive());
225 if (FAILED(result
)) {
226 LOG(ERROR
) << std::hex
<< result
;
230 // Cache Window class, HWND, and window pattern for opened windows.
231 result
= automation_
->CreateCacheRequest(cache_request
.Receive());
232 if (FAILED(result
)) {
233 LOG(ERROR
) << std::hex
<< result
;
236 cache_request
->AddProperty(UIA_ClassNamePropertyId
);
237 cache_request
->AddProperty(UIA_NativeWindowHandlePropertyId
);
239 // Create the observer.
240 CComObject
<EventHandler
>* event_handler_obj
= NULL
;
241 result
= CComObject
<EventHandler
>::CreateInstance(&event_handler_obj
);
242 if (FAILED(result
)) {
243 LOG(ERROR
) << std::hex
<< result
;
246 event_handler_obj
->Initialize(base::ThreadTaskRunnerHandle::Get(),
247 weak_ptr_factory_
.GetWeakPtr());
248 base::win::ScopedComPtr
<IUIAutomationEventHandler
> event_handler(
251 result
= automation_
->AddAutomationEventHandler(
252 UIA_Window_WindowOpenedEventId
,
254 TreeScope_Descendants
,
258 if (FAILED(result
)) {
259 LOG(ERROR
) << std::hex
<< result
;
263 event_handler_
= event_handler
;
267 // Removes this instance's window observer.
268 HRESULT
UIAutomationClient::Context::RemoveWindowObserver() {
269 DCHECK(thread_checker_
.CalledOnValidThread());
270 DCHECK(automation_
.get());
271 DCHECK(event_handler_
.get());
273 HRESULT result
= S_OK
;
274 base::win::ScopedComPtr
<IUIAutomationElement
> root_element
;
276 // The opening of all windows are observed.
277 result
= automation_
->GetRootElement(root_element
.Receive());
278 if (FAILED(result
)) {
279 LOG(ERROR
) << std::hex
<< result
;
283 result
= automation_
->RemoveAutomationEventHandler(
284 UIA_Window_WindowOpenedEventId
,
287 if (FAILED(result
)) {
288 LOG(ERROR
) << std::hex
<< result
;
292 event_handler_
= NULL
;
296 // Handles an automation event. If the event results in the processing for which
297 // this context was created, the context self-destructs after posting the
298 // results to the client.
299 void UIAutomationClient::Context::HandleAutomationEvent(
300 base::win::ScopedComPtr
<IUIAutomationElement
> sender
,
302 DCHECK(thread_checker_
.CalledOnValidThread());
303 if (eventId
== UIA_Window_WindowOpenedEventId
)
304 HandleWindowOpen(sender
);
307 // Handles a WindowOpen event. If |window| is the one for which this instance is
308 // waiting, it is processed and this instance self-destructs after posting the
309 // results to the client.
310 void UIAutomationClient::Context::HandleWindowOpen(
311 const base::win::ScopedComPtr
<IUIAutomationElement
>& window
) {
312 DCHECK(thread_checker_
.CalledOnValidThread());
314 base::win::ScopedVariant var
;
316 hr
= window
->GetCachedPropertyValueEx(UIA_ClassNamePropertyId
, TRUE
,
319 LOG(ERROR
) << std::hex
<< hr
;
323 if (V_VT(&var
) != VT_BSTR
) {
324 LOG(ERROR
) << __FUNCTION__
" class name is not a BSTR: " << V_VT(&var
);
328 base::string16
class_name(V_BSTR(&var
));
330 // Window class names are atoms, which are case-insensitive.
331 if (class_name
.size() == class_name_
.size() &&
332 std::equal(class_name
.begin(), class_name
.end(), class_name_
.begin(),
333 base::CaseInsensitiveCompare
<wchar_t>())) {
334 RemoveWindowObserver();
335 ProcessWindow(window
);
339 // Processes |window| by invoking the desired child item. If the item cannot be
340 // found or invoked, an attempt is made to get a list of all invokable children.
341 // The results are posted back to the client on |client_runner_|, and this
342 // instance self-destructs.
343 void UIAutomationClient::Context::ProcessWindow(
344 const base::win::ScopedComPtr
<IUIAutomationElement
>& window
) {
345 DCHECK(thread_checker_
.CalledOnValidThread());
347 HRESULT result
= S_OK
;
348 std::vector
<base::string16
> choices
;
349 result
= InvokeDesiredItem(window
);
350 if (FAILED(result
)) {
351 GetInvokableItems(window
, &choices
);
355 client_runner_
->PostTask(FROM_HERE
,
356 base::Bind(result_callback_
, result
, choices
));
358 // Self-destruct since there's nothing more to be done here.
362 // Invokes the desired child of |element|.
363 HRESULT
UIAutomationClient::Context::InvokeDesiredItem(
364 const base::win::ScopedComPtr
<IUIAutomationElement
>& element
) {
365 DCHECK(thread_checker_
.CalledOnValidThread());
367 HRESULT result
= S_OK
;
368 base::win::ScopedVariant var
;
369 base::win::ScopedComPtr
<IUIAutomationCondition
> invokable_condition
;
370 base::win::ScopedComPtr
<IUIAutomationCondition
> item_name_condition
;
371 base::win::ScopedComPtr
<IUIAutomationCondition
> control_view_condition
;
372 base::win::ScopedComPtr
<IUIAutomationCondition
> condition
;
373 base::win::ScopedComPtr
<IUIAutomationCacheRequest
> cache_request
;
374 base::win::ScopedComPtr
<IUIAutomationElement
> target
;
376 // Search for an invokable element named item_name.
378 result
= automation_
->CreatePropertyCondition(
379 UIA_IsInvokePatternAvailablePropertyId
,
381 invokable_condition
.Receive());
383 if (FAILED(result
)) {
384 LOG(ERROR
) << std::hex
<< result
;
388 var
.Set(item_name_
.c_str());
389 result
= automation_
->CreatePropertyCondition(UIA_NamePropertyId
,
391 item_name_condition
.Receive());
393 if (FAILED(result
)) {
394 LOG(ERROR
) << std::hex
<< result
;
398 result
= automation_
->get_ControlViewCondition(
399 control_view_condition
.Receive());
400 if (FAILED(result
)) {
401 LOG(ERROR
) << std::hex
<< result
;
405 std::vector
<IUIAutomationCondition
*> conditions
;
406 conditions
.push_back(invokable_condition
.get());
407 conditions
.push_back(item_name_condition
.get());
408 conditions
.push_back(control_view_condition
.get());
409 result
= automation_
->CreateAndConditionFromNativeArray(
410 &conditions
[0], conditions
.size(), condition
.Receive());
411 if (FAILED(result
)) {
412 LOG(ERROR
) << std::hex
<< result
;
416 // Cache invokable pattern for the item.
417 result
= automation_
->CreateCacheRequest(cache_request
.Receive());
418 if (FAILED(result
)) {
419 LOG(ERROR
) << std::hex
<< result
;
422 cache_request
->AddPattern(UIA_InvokePatternId
);
424 result
= element
->FindFirstBuildCache(
425 static_cast<TreeScope
>(TreeScope_Children
| TreeScope_Descendants
),
429 if (FAILED(result
)) {
430 LOG(ERROR
) << std::hex
<< result
;
434 // If the item was found, invoke it.
436 LOG(WARNING
) << "Failed to find desired item to invoke.";
440 base::win::ScopedComPtr
<IUIAutomationInvokePattern
> invoker
;
441 result
= target
->GetCachedPatternAs(UIA_InvokePatternId
, invoker
.iid(),
442 invoker
.ReceiveVoid());
443 if (FAILED(result
)) {
444 LOG(ERROR
) << std::hex
<< result
;
448 result
= invoker
->Invoke();
449 if (FAILED(result
)) {
450 LOG(ERROR
) << std::hex
<< result
;
457 // Populates |choices| with the names of all invokable children of |element|.
458 HRESULT
UIAutomationClient::Context::GetInvokableItems(
459 const base::win::ScopedComPtr
<IUIAutomationElement
>& element
,
460 std::vector
<base::string16
>* choices
) {
462 DCHECK(thread_checker_
.CalledOnValidThread());
464 HRESULT result
= S_OK
;
465 base::win::ScopedVariant var
;
466 base::win::ScopedComPtr
<IUIAutomationCondition
> invokable_condition
;
467 base::win::ScopedComPtr
<IUIAutomationCondition
> control_view_condition
;
468 base::win::ScopedComPtr
<IUIAutomationCondition
> condition
;
469 base::win::ScopedComPtr
<IUIAutomationCacheRequest
> cache_request
;
470 base::win::ScopedComPtr
<IUIAutomationElementArray
> element_array
;
471 base::win::ScopedComPtr
<IUIAutomationElement
> child_element
;
473 // Search for all invokable elements.
475 result
= automation_
->CreatePropertyCondition(
476 UIA_IsInvokePatternAvailablePropertyId
,
478 invokable_condition
.Receive());
480 if (FAILED(result
)) {
481 LOG(ERROR
) << std::hex
<< result
;
485 result
= automation_
->get_ControlViewCondition(
486 control_view_condition
.Receive());
487 if (FAILED(result
)) {
488 LOG(ERROR
) << std::hex
<< result
;
492 result
= automation_
->CreateAndCondition(
493 invokable_condition
, control_view_condition
, condition
.Receive());
494 if (FAILED(result
)) {
495 LOG(ERROR
) << std::hex
<< result
;
500 result
= automation_
->CreateCacheRequest(cache_request
.Receive());
501 if (FAILED(result
)) {
502 LOG(ERROR
) << std::hex
<< result
;
505 cache_request
->AddProperty(UIA_NamePropertyId
);
507 result
= element
->FindAllBuildCache(
508 static_cast<TreeScope
>(TreeScope_Children
| TreeScope_Descendants
),
511 element_array
.Receive());
512 if (FAILED(result
)) {
513 LOG(ERROR
) << std::hex
<< result
;
517 if (!element_array
.get()) {
518 LOG(ERROR
) << "The window may have vanished.";
522 int num_elements
= 0;
523 result
= element_array
->get_Length(&num_elements
);
524 if (FAILED(result
)) {
525 LOG(ERROR
) << std::hex
<< result
;
530 choices
->reserve(num_elements
);
531 for (int i
= 0; i
< num_elements
; ++i
) {
532 child_element
.Release();
533 result
= element_array
->GetElement(i
, child_element
.Receive());
534 if (FAILED(result
)) {
535 LOG(ERROR
) << std::hex
<< result
;
538 result
= child_element
->GetCachedPropertyValueEx(UIA_NamePropertyId
, TRUE
,
540 if (FAILED(result
)) {
541 LOG(ERROR
) << std::hex
<< result
;
544 if (V_VT(&var
) != VT_BSTR
) {
545 LOG(ERROR
) << __FUNCTION__
" name is not a BSTR: " << V_VT(&var
);
548 choices
->push_back(base::string16(V_BSTR(&var
)));
555 // Closes the element |window| by sending it an escape key.
556 void UIAutomationClient::Context::CloseWindow(
557 const base::win::ScopedComPtr
<IUIAutomationElement
>& window
) {
558 DCHECK(thread_checker_
.CalledOnValidThread());
560 // It's tempting to get the Window pattern from |window| and invoke its Close
561 // method. Unfortunately, this doesn't work. Sending an escape key does the
563 HRESULT result
= S_OK
;
564 base::win::ScopedVariant var
;
566 result
= window
->GetCachedPropertyValueEx(
567 UIA_NativeWindowHandlePropertyId
,
570 if (FAILED(result
)) {
571 LOG(ERROR
) << std::hex
<< result
;
575 if (V_VT(&var
) != VT_I4
) {
576 LOG(ERROR
) << __FUNCTION__
" window handle is not an int: " << V_VT(&var
);
580 HWND handle
= reinterpret_cast<HWND
>(V_I4(&var
));
582 uint32 scan_code
= MapVirtualKey(VK_ESCAPE
, MAPVK_VK_TO_VSC
);
583 PostMessage(handle
, WM_KEYDOWN
, VK_ESCAPE
,
584 MAKELPARAM(1, scan_code
));
585 PostMessage(handle
, WM_KEYUP
, VK_ESCAPE
,
586 MAKELPARAM(1, scan_code
| KF_REPEAT
| KF_UP
));
589 UIAutomationClient::UIAutomationClient()
590 : automation_thread_("UIAutomation") {}
592 UIAutomationClient::~UIAutomationClient() {
593 DCHECK(thread_checker_
.CalledOnValidThread());
595 // context_ is still valid when the caller destroys the instance before the
596 // callback(s) have fired. In this case, delete the context on the automation
597 // thread before joining with it.
598 automation_thread_
.message_loop()->PostTask(
600 base::Bind(&UIAutomationClient::Context::DeleteOnAutomationThread
,
604 void UIAutomationClient::Begin(const wchar_t* class_name
,
605 const base::string16
& item_name
,
606 const InitializedCallback
& init_callback
,
607 const ResultCallback
& result_callback
) {
608 DCHECK(thread_checker_
.CalledOnValidThread());
609 DCHECK_EQ(context_
.get(), static_cast<Context
*>(NULL
));
611 // Start the automation thread and initialize our automation client on it.
612 context_
= Context::Create();
613 automation_thread_
.init_com_with_mta(true);
614 automation_thread_
.Start();
615 automation_thread_
.message_loop()->PostTask(
617 base::Bind(&UIAutomationClient::Context::Initialize
,
619 base::ThreadTaskRunnerHandle::Get(),
620 base::string16(class_name
),
626 } // namespace internal