blob: e858099d9e7f28064c03eb189da4d0b98cc340d3 [file] [log] [blame]
// 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 <atlbase.h>
#include <atlcom.h>
#include <oleauto.h>
#include <stdint.h>
#include <uiautomation.h>
#include <algorithm>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/threading/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<Context> 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<base::SingleThreadTaskRunner> 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<IUIAutomationElement> 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<IUIAutomationElement>& window);
void ProcessWindow(
const base::win::ScopedComPtr<IUIAutomationElement>& window);
HRESULT InvokeDesiredItem(
const base::win::ScopedComPtr<IUIAutomationElement>& element);
HRESULT GetInvokableItems(
const base::win::ScopedComPtr<IUIAutomationElement>& element,
std::vector<base::string16>* choices);
void CloseWindow(const base::win::ScopedComPtr<IUIAutomationElement>& window);
base::ThreadChecker thread_checker_;
// The loop on which the client itself lives.
scoped_refptr<base::SingleThreadTaskRunner> 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<IUIAutomation> automation_;
// A handler of Window open events.
base::win::ScopedComPtr<IUIAutomationEventHandler> event_handler_;
// Weak pointers to the context are given to event handlers.
base::WeakPtrFactory<UIAutomationClient::Context> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(Context);
};
class UIAutomationClient::Context::EventHandler
: public CComObjectRootEx<CComMultiThreadModel>,
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<base::SingleThreadTaskRunner>& context_runner,
const base::WeakPtr<UIAutomationClient::Context>& context);
// IUIAutomationEventHandler methods.
STDMETHOD(HandleAutomationEvent)(IUIAutomationElement* sender,
EVENTID eventId) override;
private:
// The task runner for the UI automation client context.
scoped_refptr<base::SingleThreadTaskRunner> context_runner_;
// The parent UI automation client context.
base::WeakPtr<UIAutomationClient::Context> context_;
DISALLOW_COPY_AND_ASSIGN(EventHandler);
};
UIAutomationClient::Context::EventHandler::EventHandler() {}
UIAutomationClient::Context::EventHandler::~EventHandler() {}
void UIAutomationClient::Context::EventHandler::Initialize(
const scoped_refptr<base::SingleThreadTaskRunner>& context_runner,
const base::WeakPtr<UIAutomationClient::Context>& 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<IUIAutomationElement>(sender),
eventId));
return S_OK;
}
base::WeakPtr<UIAutomationClient::Context>
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<base::SingleThreadTaskRunner> 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<IUIAutomationElement> root_element;
base::win::ScopedComPtr<IUIAutomationCacheRequest> 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<EventHandler>* event_handler_obj = NULL;
result = CComObject<EventHandler>::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<IUIAutomationEventHandler> 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<IUIAutomationElement> 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<IUIAutomationElement> 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<IUIAutomationElement>& 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. Assume that
// the window in question only needs ASCII case-insensitivity.
if (class_name.size() == class_name_.size() &&
std::equal(class_name.begin(), class_name.end(), class_name_.begin(),
base::CaseInsensitiveCompareASCII<wchar_t>())) {
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<IUIAutomationElement>& window) {
DCHECK(thread_checker_.CalledOnValidThread());
HRESULT result = S_OK;
std::vector<base::string16> 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<IUIAutomationElement>& element) {
DCHECK(thread_checker_.CalledOnValidThread());
HRESULT result = S_OK;
base::win::ScopedVariant var;
base::win::ScopedComPtr<IUIAutomationCondition> invokable_condition;
base::win::ScopedComPtr<IUIAutomationCondition> item_name_condition;
base::win::ScopedComPtr<IUIAutomationCondition> control_view_condition;
base::win::ScopedComPtr<IUIAutomationCondition> condition;
base::win::ScopedComPtr<IUIAutomationCacheRequest> cache_request;
base::win::ScopedComPtr<IUIAutomationElement> 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<IUIAutomationCondition*> 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>(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<IUIAutomationInvokePattern> 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<IUIAutomationElement>& element,
std::vector<base::string16>* choices) {
DCHECK(choices);
DCHECK(thread_checker_.CalledOnValidThread());
HRESULT result = S_OK;
base::win::ScopedVariant var;
base::win::ScopedComPtr<IUIAutomationCondition> invokable_condition;
base::win::ScopedComPtr<IUIAutomationCondition> control_view_condition;
base::win::ScopedComPtr<IUIAutomationCondition> condition;
base::win::ScopedComPtr<IUIAutomationCacheRequest> cache_request;
base::win::ScopedComPtr<IUIAutomationElementArray> element_array;
base::win::ScopedComPtr<IUIAutomationElement> 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>(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<IUIAutomationElement>& 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<HWND>(V_I4(var.ptr()));
uint32_t 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<Context*>(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