// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "win8/test/ui_automation_client.h" #include #include #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/memory/ref_counted.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "base/win/scoped_comptr.h" #include "base/win/scoped_variant.h" namespace win8 { namespace internal { // The guts of the UI automation client which runs on a dedicated thread in the // multi-threaded COM apartment. An instance may be constructed on any thread, // but Initialize() must be invoked on a thread in the MTA. class UIAutomationClient::Context { public: // Returns a new instance ready for initialization and use on another thread. static base::WeakPtr Create(); // Deletes the instance. void DeleteOnAutomationThread(); // Initializes the context, invoking |init_callback| via |client_runner| when // done. On success, |result_callback| will eventually be called after the // window has been processed. On failure, this instance self-destructs after // posting |init_callback|. void Initialize( scoped_refptr client_runner, base::string16 class_name, base::string16 item_name, UIAutomationClient::InitializedCallback init_callback, UIAutomationClient::ResultCallback result_callback); // Methods invoked by event handlers via weak pointers. void HandleAutomationEvent( base::win::ScopedComPtr sender, EVENTID eventId); private: class EventHandler; // The only and only method that may be called from outside of the automation // thread. Context(); ~Context(); HRESULT InstallWindowObserver(); HRESULT RemoveWindowObserver(); void HandleWindowOpen( const base::win::ScopedComPtr& window); void ProcessWindow( const base::win::ScopedComPtr& window); HRESULT InvokeDesiredItem( const base::win::ScopedComPtr& element); HRESULT GetInvokableItems( const base::win::ScopedComPtr& element, std::vector* choices); void CloseWindow(const base::win::ScopedComPtr& window); base::ThreadChecker thread_checker_; // The loop on which the client itself lives. scoped_refptr client_runner_; // The class name of the window for which the client waits. base::string16 class_name_; // The name of the item to invoke. base::string16 item_name_; // The consumer's result callback. ResultCallback result_callback_; // The automation client. base::win::ScopedComPtr automation_; // A handler of Window open events. base::win::ScopedComPtr event_handler_; // Weak pointers to the context are given to event handlers. base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(Context); }; class UIAutomationClient::Context::EventHandler : public CComObjectRootEx, public IUIAutomationEventHandler { public: BEGIN_COM_MAP(UIAutomationClient::Context::EventHandler) COM_INTERFACE_ENTRY(IUIAutomationEventHandler) END_COM_MAP() EventHandler(); virtual ~EventHandler(); // Initializes the object with its parent UI automation client context's // message loop and pointer. Events are dispatched back to the context on // the given loop. void Initialize( const scoped_refptr& context_runner, const base::WeakPtr& context); // IUIAutomationEventHandler methods. STDMETHOD(HandleAutomationEvent)(IUIAutomationElement* sender, EVENTID eventId) override; private: // The task runner for the UI automation client context. scoped_refptr context_runner_; // The parent UI automation client context. base::WeakPtr context_; DISALLOW_COPY_AND_ASSIGN(EventHandler); }; UIAutomationClient::Context::EventHandler::EventHandler() {} UIAutomationClient::Context::EventHandler::~EventHandler() {} void UIAutomationClient::Context::EventHandler::Initialize( const scoped_refptr& context_runner, const base::WeakPtr& context) { context_runner_ = context_runner; context_ = context; } HRESULT UIAutomationClient::Context::EventHandler::HandleAutomationEvent( IUIAutomationElement* sender, EVENTID eventId) { // Event handlers are invoked on an arbitrary thread in the MTA. Send the // event back to the main UI automation thread for processing. context_runner_->PostTask( FROM_HERE, base::Bind(&UIAutomationClient::Context::HandleAutomationEvent, context_, base::win::ScopedComPtr(sender), eventId)); return S_OK; } base::WeakPtr UIAutomationClient::Context::Create() { Context* context = new Context(); return context->weak_ptr_factory_.GetWeakPtr(); } void UIAutomationClient::Context::DeleteOnAutomationThread() { DCHECK(thread_checker_.CalledOnValidThread()); delete this; } UIAutomationClient::Context::Context() : weak_ptr_factory_(this) {} UIAutomationClient::Context::~Context() { DCHECK(thread_checker_.CalledOnValidThread()); if (event_handler_.get()) { event_handler_ = NULL; HRESULT result = automation_->RemoveAllEventHandlers(); LOG_IF(ERROR, FAILED(result)) << std::hex << result; } } void UIAutomationClient::Context::Initialize( scoped_refptr client_runner, base::string16 class_name, base::string16 item_name, UIAutomationClient::InitializedCallback init_callback, UIAutomationClient::ResultCallback result_callback) { // This and all other methods must be called on the automation thread. DCHECK(!client_runner->BelongsToCurrentThread()); // Bind the checker to this thread. thread_checker_.DetachFromThread(); DCHECK(thread_checker_.CalledOnValidThread()); client_runner_ = client_runner; class_name_ = class_name; item_name_ = item_name; result_callback_ = result_callback; HRESULT result = automation_.CreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER); if (FAILED(result) || !automation_.get()) LOG(ERROR) << std::hex << result; else result = InstallWindowObserver(); // Tell the client that initialization is complete. client_runner_->PostTask(FROM_HERE, base::Bind(init_callback, result)); // Self-destruct if the overall operation failed. if (FAILED(result)) delete this; } // Installs the window observer. HRESULT UIAutomationClient::Context::InstallWindowObserver() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(automation_.get()); DCHECK(!event_handler_.get()); HRESULT result = S_OK; base::win::ScopedComPtr root_element; base::win::ScopedComPtr cache_request; // Observe the opening of all windows. result = automation_->GetRootElement(root_element.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } // Cache Window class, HWND, and window pattern for opened windows. result = automation_->CreateCacheRequest(cache_request.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } cache_request->AddProperty(UIA_ClassNamePropertyId); cache_request->AddProperty(UIA_NativeWindowHandlePropertyId); // Create the observer. CComObject* event_handler_obj = NULL; result = CComObject::CreateInstance(&event_handler_obj); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } event_handler_obj->Initialize(base::ThreadTaskRunnerHandle::Get(), weak_ptr_factory_.GetWeakPtr()); base::win::ScopedComPtr event_handler( event_handler_obj); result = automation_->AddAutomationEventHandler( UIA_Window_WindowOpenedEventId, root_element.get(), TreeScope_Descendants, cache_request.get(), event_handler.get()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } event_handler_ = event_handler; return S_OK; } // Removes this instance's window observer. HRESULT UIAutomationClient::Context::RemoveWindowObserver() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(automation_.get()); DCHECK(event_handler_.get()); HRESULT result = S_OK; base::win::ScopedComPtr root_element; // The opening of all windows are observed. result = automation_->GetRootElement(root_element.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } result = automation_->RemoveAutomationEventHandler( UIA_Window_WindowOpenedEventId, root_element.get(), event_handler_.get()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } event_handler_ = NULL; return S_OK; } // Handles an automation event. If the event results in the processing for which // this context was created, the context self-destructs after posting the // results to the client. void UIAutomationClient::Context::HandleAutomationEvent( base::win::ScopedComPtr sender, EVENTID eventId) { DCHECK(thread_checker_.CalledOnValidThread()); if (eventId == UIA_Window_WindowOpenedEventId) HandleWindowOpen(sender); } // Handles a WindowOpen event. If |window| is the one for which this instance is // waiting, it is processed and this instance self-destructs after posting the // results to the client. void UIAutomationClient::Context::HandleWindowOpen( const base::win::ScopedComPtr& window) { DCHECK(thread_checker_.CalledOnValidThread()); HRESULT hr = S_OK; base::win::ScopedVariant var; hr = window->GetCachedPropertyValueEx(UIA_ClassNamePropertyId, TRUE, var.Receive()); if (FAILED(hr)) { LOG(ERROR) << std::hex << hr; return; } if (V_VT(var.ptr()) != VT_BSTR) { LOG(ERROR) << __FUNCTION__ << " class name is not a BSTR: " << V_VT(var.ptr()); return; } base::string16 class_name(V_BSTR(var.ptr())); // Window class names are atoms, which are case-insensitive. if (class_name.size() == class_name_.size() && std::equal(class_name.begin(), class_name.end(), class_name_.begin(), base::CaseInsensitiveCompare())) { RemoveWindowObserver(); ProcessWindow(window); } } // Processes |window| by invoking the desired child item. If the item cannot be // found or invoked, an attempt is made to get a list of all invokable children. // The results are posted back to the client on |client_runner_|, and this // instance self-destructs. void UIAutomationClient::Context::ProcessWindow( const base::win::ScopedComPtr& window) { DCHECK(thread_checker_.CalledOnValidThread()); HRESULT result = S_OK; std::vector choices; result = InvokeDesiredItem(window); if (FAILED(result)) { GetInvokableItems(window, &choices); CloseWindow(window); } client_runner_->PostTask(FROM_HERE, base::Bind(result_callback_, result, choices)); // Self-destruct since there's nothing more to be done here. delete this; } // Invokes the desired child of |element|. HRESULT UIAutomationClient::Context::InvokeDesiredItem( const base::win::ScopedComPtr& element) { DCHECK(thread_checker_.CalledOnValidThread()); HRESULT result = S_OK; base::win::ScopedVariant var; base::win::ScopedComPtr invokable_condition; base::win::ScopedComPtr item_name_condition; base::win::ScopedComPtr control_view_condition; base::win::ScopedComPtr condition; base::win::ScopedComPtr cache_request; base::win::ScopedComPtr target; // Search for an invokable element named item_name. var.Set(true); result = automation_->CreatePropertyCondition( UIA_IsInvokePatternAvailablePropertyId, var, invokable_condition.Receive()); var.Reset(); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } var.Set(item_name_.c_str()); result = automation_->CreatePropertyCondition(UIA_NamePropertyId, var, item_name_condition.Receive()); var.Reset(); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } result = automation_->get_ControlViewCondition( control_view_condition.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } std::vector conditions; conditions.push_back(invokable_condition.get()); conditions.push_back(item_name_condition.get()); conditions.push_back(control_view_condition.get()); result = automation_->CreateAndConditionFromNativeArray( &conditions[0], conditions.size(), condition.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } // Cache invokable pattern for the item. result = automation_->CreateCacheRequest(cache_request.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } cache_request->AddPattern(UIA_InvokePatternId); result = element->FindFirstBuildCache( static_cast(TreeScope_Children | TreeScope_Descendants), condition.get(), cache_request.get(), target.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } // If the item was found, invoke it. if (!target.get()) { LOG(WARNING) << "Failed to find desired item to invoke."; return E_FAIL; } base::win::ScopedComPtr invoker; result = target->GetCachedPatternAs(UIA_InvokePatternId, invoker.iid(), invoker.ReceiveVoid()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } result = invoker->Invoke(); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } return S_OK; } // Populates |choices| with the names of all invokable children of |element|. HRESULT UIAutomationClient::Context::GetInvokableItems( const base::win::ScopedComPtr& element, std::vector* choices) { DCHECK(choices); DCHECK(thread_checker_.CalledOnValidThread()); HRESULT result = S_OK; base::win::ScopedVariant var; base::win::ScopedComPtr invokable_condition; base::win::ScopedComPtr control_view_condition; base::win::ScopedComPtr condition; base::win::ScopedComPtr cache_request; base::win::ScopedComPtr element_array; base::win::ScopedComPtr child_element; // Search for all invokable elements. var.Set(true); result = automation_->CreatePropertyCondition( UIA_IsInvokePatternAvailablePropertyId, var, invokable_condition.Receive()); var.Reset(); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } result = automation_->get_ControlViewCondition( control_view_condition.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } result = automation_->CreateAndCondition(invokable_condition.get(), control_view_condition.get(), condition.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } // Cache item names. result = automation_->CreateCacheRequest(cache_request.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } cache_request->AddProperty(UIA_NamePropertyId); result = element->FindAllBuildCache( static_cast(TreeScope_Children | TreeScope_Descendants), condition.get(), cache_request.get(), element_array.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } if (!element_array.get()) { LOG(ERROR) << "The window may have vanished."; return S_OK; } int num_elements = 0; result = element_array->get_Length(&num_elements); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return result; } choices->clear(); choices->reserve(num_elements); for (int i = 0; i < num_elements; ++i) { child_element.Release(); result = element_array->GetElement(i, child_element.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; continue; } result = child_element->GetCachedPropertyValueEx(UIA_NamePropertyId, TRUE, var.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; continue; } if (V_VT(var.ptr()) != VT_BSTR) { LOG(ERROR) << __FUNCTION__ << " name is not a BSTR: " << V_VT(var.ptr()); continue; } choices->push_back(base::string16(V_BSTR(var.ptr()))); var.Reset(); } return result; } // Closes the element |window| by sending it an escape key. void UIAutomationClient::Context::CloseWindow( const base::win::ScopedComPtr& window) { DCHECK(thread_checker_.CalledOnValidThread()); // It's tempting to get the Window pattern from |window| and invoke its Close // method. Unfortunately, this doesn't work. Sending an escape key does the // trick, though. HRESULT result = S_OK; base::win::ScopedVariant var; result = window->GetCachedPropertyValueEx( UIA_NativeWindowHandlePropertyId, TRUE, var.Receive()); if (FAILED(result)) { LOG(ERROR) << std::hex << result; return; } if (V_VT(var.ptr()) != VT_I4) { LOG(ERROR) << __FUNCTION__ << " window handle is not an int: " << V_VT(var.ptr()); return; } HWND handle = reinterpret_cast(V_I4(var.ptr())); uint32 scan_code = MapVirtualKey(VK_ESCAPE, MAPVK_VK_TO_VSC); PostMessage(handle, WM_KEYDOWN, VK_ESCAPE, MAKELPARAM(1, scan_code)); PostMessage(handle, WM_KEYUP, VK_ESCAPE, MAKELPARAM(1, scan_code | KF_REPEAT | KF_UP)); } UIAutomationClient::UIAutomationClient() : automation_thread_("UIAutomation") {} UIAutomationClient::~UIAutomationClient() { DCHECK(thread_checker_.CalledOnValidThread()); // context_ is still valid when the caller destroys the instance before the // callback(s) have fired. In this case, delete the context on the automation // thread before joining with it. automation_thread_.message_loop()->PostTask( FROM_HERE, base::Bind(&UIAutomationClient::Context::DeleteOnAutomationThread, context_)); } void UIAutomationClient::Begin(const wchar_t* class_name, const base::string16& item_name, const InitializedCallback& init_callback, const ResultCallback& result_callback) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_EQ(context_.get(), static_cast(NULL)); // Start the automation thread and initialize our automation client on it. context_ = Context::Create(); automation_thread_.init_com_with_mta(true); automation_thread_.Start(); automation_thread_.message_loop()->PostTask( FROM_HERE, base::Bind(&UIAutomationClient::Context::Initialize, context_, base::ThreadTaskRunnerHandle::Get(), base::string16(class_name), item_name, init_callback, result_callback)); } } // namespace internal } // namespace win8