• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "win8/test/ui_automation_client.h"
6 
7 #include <atlbase.h>
8 #include <atlcom.h>
9 #include <oleauto.h>
10 #include <uiautomation.h>
11 
12 #include <algorithm>
13 
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"
22 
23 namespace win8 {
24 namespace internal {
25 
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 {
30  public:
31   // Returns a new instance ready for initialization and use on another thread.
32   static base::WeakPtr<Context> Create();
33 
34   // Deletes the instance.
35   void DeleteOnAutomationThread();
36 
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|.
41   void Initialize(
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);
47 
48   // Methods invoked by event handlers via weak pointers.
49   void HandleAutomationEvent(
50       base::win::ScopedComPtr<IUIAutomationElement> sender,
51       EVENTID eventId);
52 
53  private:
54   class EventHandler;
55 
56   // The only and only method that may be called from outside of the automation
57   // thread.
58   Context();
59   ~Context();
60 
61   HRESULT InstallWindowObserver();
62   HRESULT RemoveWindowObserver();
63 
64   void HandleWindowOpen(
65       const base::win::ScopedComPtr<IUIAutomationElement>& window);
66   void ProcessWindow(
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);
74 
75   base::ThreadChecker thread_checker_;
76 
77   // The loop on which the client itself lives.
78   scoped_refptr<base::SingleThreadTaskRunner> client_runner_;
79 
80   // The class name of the window for which the client waits.
81   base::string16 class_name_;
82 
83   // The name of the item to invoke.
84   base::string16 item_name_;
85 
86   // The consumer's result callback.
87   ResultCallback result_callback_;
88 
89   // The automation client.
90   base::win::ScopedComPtr<IUIAutomation> automation_;
91 
92   // A handler of Window open events.
93   base::win::ScopedComPtr<IUIAutomationEventHandler> event_handler_;
94 
95   // Weak pointers to the context are given to event handlers.
96   base::WeakPtrFactory<UIAutomationClient::Context> weak_ptr_factory_;
97 
98   DISALLOW_COPY_AND_ASSIGN(Context);
99 };
100 
101 class UIAutomationClient::Context::EventHandler
102     : public CComObjectRootEx<CComMultiThreadModel>,
103       public IUIAutomationEventHandler {
104  public:
105   BEGIN_COM_MAP(UIAutomationClient::Context::EventHandler)
106     COM_INTERFACE_ENTRY(IUIAutomationEventHandler)
107   END_COM_MAP()
108 
109   EventHandler();
110   virtual ~EventHandler();
111 
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
114   // the given loop.
115   void Initialize(
116       const scoped_refptr<base::SingleThreadTaskRunner>& context_runner,
117       const base::WeakPtr<UIAutomationClient::Context>& context);
118 
119   // IUIAutomationEventHandler methods.
120   STDMETHOD(HandleAutomationEvent)(IUIAutomationElement* sender,
121                                    EVENTID eventId);
122 
123  private:
124   // The task runner for the UI automation client context.
125   scoped_refptr<base::SingleThreadTaskRunner> context_runner_;
126 
127   // The parent UI automation client context.
128   base::WeakPtr<UIAutomationClient::Context> context_;
129 
130   DISALLOW_COPY_AND_ASSIGN(EventHandler);
131 };
132 
EventHandler()133 UIAutomationClient::Context::EventHandler::EventHandler() {}
134 
~EventHandler()135 UIAutomationClient::Context::EventHandler::~EventHandler() {}
136 
Initialize(const scoped_refptr<base::SingleThreadTaskRunner> & context_runner,const base::WeakPtr<UIAutomationClient::Context> & context)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;
141   context_ = context;
142 }
143 
HandleAutomationEvent(IUIAutomationElement * sender,EVENTID eventId)144 HRESULT UIAutomationClient::Context::EventHandler::HandleAutomationEvent(
145     IUIAutomationElement* sender,
146     EVENTID eventId) {
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(
150       FROM_HERE,
151       base::Bind(&UIAutomationClient::Context::HandleAutomationEvent, context_,
152                  base::win::ScopedComPtr<IUIAutomationElement>(sender),
153                  eventId));
154 
155   return S_OK;
156 }
157 
158 base::WeakPtr<UIAutomationClient::Context>
Create()159     UIAutomationClient::Context::Create() {
160   Context* context = new Context();
161   return context->weak_ptr_factory_.GetWeakPtr();
162 }
163 
DeleteOnAutomationThread()164 void UIAutomationClient::Context::DeleteOnAutomationThread() {
165   DCHECK(thread_checker_.CalledOnValidThread());
166   delete this;
167 }
168 
Context()169 UIAutomationClient::Context::Context() : weak_ptr_factory_(this) {}
170 
~Context()171 UIAutomationClient::Context::~Context() {
172   DCHECK(thread_checker_.CalledOnValidThread());
173 
174   if (event_handler_.get()) {
175     event_handler_ = NULL;
176     HRESULT result = automation_->RemoveAllEventHandlers();
177     LOG_IF(ERROR, FAILED(result)) << std::hex << result;
178   }
179 }
180 
Initialize(scoped_refptr<base::SingleThreadTaskRunner> client_runner,base::string16 class_name,base::string16 item_name,UIAutomationClient::InitializedCallback init_callback,UIAutomationClient::ResultCallback result_callback)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());
192 
193   client_runner_ = client_runner;
194   class_name_ = class_name;
195   item_name_ = item_name;
196   result_callback_ = result_callback;
197 
198   HRESULT result = automation_.CreateInstance(CLSID_CUIAutomation, NULL,
199                                               CLSCTX_INPROC_SERVER);
200   if (FAILED(result) || !automation_.get())
201     LOG(ERROR) << std::hex << result;
202   else
203     result = InstallWindowObserver();
204 
205   // Tell the client that initialization is complete.
206   client_runner_->PostTask(FROM_HERE, base::Bind(init_callback, result));
207 
208   // Self-destruct if the overall operation failed.
209   if (FAILED(result))
210     delete this;
211 }
212 
213 // Installs the window observer.
InstallWindowObserver()214 HRESULT UIAutomationClient::Context::InstallWindowObserver() {
215   DCHECK(thread_checker_.CalledOnValidThread());
216   DCHECK(automation_.get());
217   DCHECK(!event_handler_.get());
218 
219   HRESULT result = S_OK;
220   base::win::ScopedComPtr<IUIAutomationElement> root_element;
221   base::win::ScopedComPtr<IUIAutomationCacheRequest> cache_request;
222 
223   // Observe the opening of all windows.
224   result = automation_->GetRootElement(root_element.Receive());
225   if (FAILED(result)) {
226     LOG(ERROR) << std::hex << result;
227     return result;
228   }
229 
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;
234     return result;
235   }
236   cache_request->AddProperty(UIA_ClassNamePropertyId);
237   cache_request->AddProperty(UIA_NativeWindowHandlePropertyId);
238 
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;
244     return result;
245   }
246   event_handler_obj->Initialize(base::ThreadTaskRunnerHandle::Get(),
247                                 weak_ptr_factory_.GetWeakPtr());
248   base::win::ScopedComPtr<IUIAutomationEventHandler> event_handler(
249       event_handler_obj);
250 
251   result = automation_->AddAutomationEventHandler(
252       UIA_Window_WindowOpenedEventId,
253       root_element,
254       TreeScope_Descendants,
255       cache_request,
256       event_handler);
257 
258   if (FAILED(result)) {
259     LOG(ERROR) << std::hex << result;
260     return result;
261   }
262 
263   event_handler_ = event_handler;
264   return S_OK;
265 }
266 
267 // Removes this instance's window observer.
RemoveWindowObserver()268 HRESULT UIAutomationClient::Context::RemoveWindowObserver() {
269   DCHECK(thread_checker_.CalledOnValidThread());
270   DCHECK(automation_.get());
271   DCHECK(event_handler_.get());
272 
273   HRESULT result = S_OK;
274   base::win::ScopedComPtr<IUIAutomationElement> root_element;
275 
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;
280     return result;
281   }
282 
283   result = automation_->RemoveAutomationEventHandler(
284       UIA_Window_WindowOpenedEventId,
285       root_element,
286       event_handler_);
287   if (FAILED(result)) {
288     LOG(ERROR) << std::hex << result;
289     return result;
290   }
291 
292   event_handler_ = NULL;
293   return S_OK;
294 }
295 
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.
HandleAutomationEvent(base::win::ScopedComPtr<IUIAutomationElement> sender,EVENTID eventId)299 void UIAutomationClient::Context::HandleAutomationEvent(
300     base::win::ScopedComPtr<IUIAutomationElement> sender,
301     EVENTID eventId) {
302   DCHECK(thread_checker_.CalledOnValidThread());
303   if (eventId == UIA_Window_WindowOpenedEventId)
304     HandleWindowOpen(sender);
305 }
306 
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.
HandleWindowOpen(const base::win::ScopedComPtr<IUIAutomationElement> & window)310 void UIAutomationClient::Context::HandleWindowOpen(
311     const base::win::ScopedComPtr<IUIAutomationElement>& window) {
312   DCHECK(thread_checker_.CalledOnValidThread());
313   HRESULT hr = S_OK;
314   base::win::ScopedVariant var;
315 
316   hr = window->GetCachedPropertyValueEx(UIA_ClassNamePropertyId, TRUE,
317                                         var.Receive());
318   if (FAILED(hr)) {
319     LOG(ERROR) << std::hex << hr;
320     return;
321   }
322 
323   if (V_VT(&var) != VT_BSTR) {
324     LOG(ERROR) << __FUNCTION__ " class name is not a BSTR: " << V_VT(&var);
325     return;
326   }
327 
328   base::string16 class_name(V_BSTR(&var));
329 
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);
336   }
337 }
338 
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.
ProcessWindow(const base::win::ScopedComPtr<IUIAutomationElement> & window)343 void UIAutomationClient::Context::ProcessWindow(
344     const base::win::ScopedComPtr<IUIAutomationElement>& window) {
345   DCHECK(thread_checker_.CalledOnValidThread());
346 
347   HRESULT result = S_OK;
348   std::vector<base::string16> choices;
349   result = InvokeDesiredItem(window);
350   if (FAILED(result)) {
351     GetInvokableItems(window, &choices);
352     CloseWindow(window);
353   }
354 
355   client_runner_->PostTask(FROM_HERE,
356                            base::Bind(result_callback_, result, choices));
357 
358   // Self-destruct since there's nothing more to be done here.
359   delete this;
360 }
361 
362 // Invokes the desired child of |element|.
InvokeDesiredItem(const base::win::ScopedComPtr<IUIAutomationElement> & element)363 HRESULT UIAutomationClient::Context::InvokeDesiredItem(
364     const base::win::ScopedComPtr<IUIAutomationElement>& element) {
365   DCHECK(thread_checker_.CalledOnValidThread());
366 
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;
375 
376   // Search for an invokable element named item_name.
377   var.Set(true);
378   result = automation_->CreatePropertyCondition(
379       UIA_IsInvokePatternAvailablePropertyId,
380       var,
381       invokable_condition.Receive());
382   var.Reset();
383   if (FAILED(result)) {
384     LOG(ERROR) << std::hex << result;
385     return false;
386   }
387 
388   var.Set(item_name_.c_str());
389   result = automation_->CreatePropertyCondition(UIA_NamePropertyId,
390                                                 var,
391                                                 item_name_condition.Receive());
392   var.Reset();
393   if (FAILED(result)) {
394     LOG(ERROR) << std::hex << result;
395     return result;
396   }
397 
398   result = automation_->get_ControlViewCondition(
399       control_view_condition.Receive());
400   if (FAILED(result)) {
401     LOG(ERROR) << std::hex << result;
402     return result;
403   }
404 
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;
413     return result;
414   }
415 
416   // Cache invokable pattern for the item.
417   result = automation_->CreateCacheRequest(cache_request.Receive());
418   if (FAILED(result)) {
419     LOG(ERROR) << std::hex << result;
420     return result;
421   }
422   cache_request->AddPattern(UIA_InvokePatternId);
423 
424   result = element->FindFirstBuildCache(
425       static_cast<TreeScope>(TreeScope_Children | TreeScope_Descendants),
426       condition,
427       cache_request,
428       target.Receive());
429   if (FAILED(result)) {
430     LOG(ERROR) << std::hex << result;
431     return result;
432   }
433 
434   // If the item was found, invoke it.
435   if (!target.get()) {
436     LOG(WARNING) << "Failed to find desired item to invoke.";
437     return E_FAIL;
438   }
439 
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;
445     return result;
446   }
447 
448   result = invoker->Invoke();
449   if (FAILED(result)) {
450     LOG(ERROR) << std::hex << result;
451     return result;
452   }
453 
454   return S_OK;
455 }
456 
457 // Populates |choices| with the names of all invokable children of |element|.
GetInvokableItems(const base::win::ScopedComPtr<IUIAutomationElement> & element,std::vector<base::string16> * choices)458 HRESULT UIAutomationClient::Context::GetInvokableItems(
459     const base::win::ScopedComPtr<IUIAutomationElement>& element,
460     std::vector<base::string16>* choices) {
461   DCHECK(choices);
462   DCHECK(thread_checker_.CalledOnValidThread());
463 
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;
472 
473   // Search for all invokable elements.
474   var.Set(true);
475   result = automation_->CreatePropertyCondition(
476       UIA_IsInvokePatternAvailablePropertyId,
477       var,
478       invokable_condition.Receive());
479   var.Reset();
480   if (FAILED(result)) {
481     LOG(ERROR) << std::hex << result;
482     return result;
483   }
484 
485   result = automation_->get_ControlViewCondition(
486       control_view_condition.Receive());
487   if (FAILED(result)) {
488     LOG(ERROR) << std::hex << result;
489     return result;
490   }
491 
492   result = automation_->CreateAndCondition(
493       invokable_condition, control_view_condition, condition.Receive());
494   if (FAILED(result)) {
495     LOG(ERROR) << std::hex << result;
496     return result;
497   }
498 
499   // Cache item names.
500   result = automation_->CreateCacheRequest(cache_request.Receive());
501   if (FAILED(result)) {
502     LOG(ERROR) << std::hex << result;
503     return result;
504   }
505   cache_request->AddProperty(UIA_NamePropertyId);
506 
507   result = element->FindAllBuildCache(
508       static_cast<TreeScope>(TreeScope_Children | TreeScope_Descendants),
509       condition,
510       cache_request,
511       element_array.Receive());
512   if (FAILED(result)) {
513     LOG(ERROR) << std::hex << result;
514     return result;
515   }
516 
517   if (!element_array.get()) {
518     LOG(ERROR) << "The window may have vanished.";
519     return S_OK;
520   }
521 
522   int num_elements = 0;
523   result = element_array->get_Length(&num_elements);
524   if (FAILED(result)) {
525     LOG(ERROR) << std::hex << result;
526     return result;
527   }
528 
529   choices->clear();
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;
536       continue;
537     }
538     result = child_element->GetCachedPropertyValueEx(UIA_NamePropertyId, TRUE,
539                                                      var.Receive());
540     if (FAILED(result)) {
541       LOG(ERROR) << std::hex << result;
542       continue;
543     }
544     if (V_VT(&var) != VT_BSTR) {
545       LOG(ERROR) << __FUNCTION__ " name is not a BSTR: " << V_VT(&var);
546       continue;
547     }
548     choices->push_back(base::string16(V_BSTR(&var)));
549     var.Reset();
550   }
551 
552   return result;
553 }
554 
555 // Closes the element |window| by sending it an escape key.
CloseWindow(const base::win::ScopedComPtr<IUIAutomationElement> & window)556 void UIAutomationClient::Context::CloseWindow(
557     const base::win::ScopedComPtr<IUIAutomationElement>& window) {
558   DCHECK(thread_checker_.CalledOnValidThread());
559 
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
562   // trick, though.
563   HRESULT result = S_OK;
564   base::win::ScopedVariant var;
565 
566   result = window->GetCachedPropertyValueEx(
567       UIA_NativeWindowHandlePropertyId,
568       TRUE,
569       var.Receive());
570   if (FAILED(result)) {
571     LOG(ERROR) << std::hex << result;
572     return;
573   }
574 
575   if (V_VT(&var) != VT_I4) {
576     LOG(ERROR) << __FUNCTION__ " window handle is not an int: " << V_VT(&var);
577     return;
578   }
579 
580   HWND handle = reinterpret_cast<HWND>(V_I4(&var));
581 
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));
587 }
588 
UIAutomationClient()589 UIAutomationClient::UIAutomationClient()
590     : automation_thread_("UIAutomation") {}
591 
~UIAutomationClient()592 UIAutomationClient::~UIAutomationClient() {
593   DCHECK(thread_checker_.CalledOnValidThread());
594 
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(
599       FROM_HERE,
600       base::Bind(&UIAutomationClient::Context::DeleteOnAutomationThread,
601                  context_));
602 }
603 
Begin(const wchar_t * class_name,const base::string16 & item_name,const InitializedCallback & init_callback,const ResultCallback & result_callback)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));
610 
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(
616       FROM_HERE,
617       base::Bind(&UIAutomationClient::Context::Initialize,
618                  context_,
619                  base::ThreadTaskRunnerHandle::Get(),
620                  base::string16(class_name),
621                  item_name,
622                  init_callback,
623                  result_callback));
624 }
625 
626 }  // namespace internal
627 }  // namespace win8
628