blob: 039a065ec0bb4969645159280f94f5f201e8344f [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/win/automation_controller.h"
#include <stdint.h>
#include <wrl/client.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/win/atl.h"
#include "base/win/scoped_variant.h"
#include "chrome/browser/win/ui_automation_util.h"
#include "ui/base/win/atl_module.h"
namespace {
// Configures a cache request so that it includes all properties used by the
// debug logging.
void ConfigureCacheRequestForLogging(IUIAutomationCacheRequest* cache_request) {
DCHECK(cache_request);
#if DCHECK_IS_ON()
cache_request->AddProperty(UIA_AutomationIdPropertyId);
cache_request->AddProperty(UIA_ClassNamePropertyId);
cache_request->AddProperty(UIA_ControlTypePropertyId);
cache_request->AddProperty(UIA_IsPeripheralPropertyId);
cache_request->AddProperty(UIA_NamePropertyId);
cache_request->AddProperty(UIA_ProcessIdPropertyId);
cache_request->AddProperty(UIA_RuntimeIdPropertyId);
cache_request->AddProperty(UIA_ValueValuePropertyId);
#endif // DCHECK_IS_ON()
}
// Safely keeps |delegate_| alive and available for the Automation context and
// the event handlers. This is safe because only its vtable is accessed on the
// various threads, which is const.
class RefCountedDelegate : public base::RefCounted<RefCountedDelegate> {
public:
explicit RefCountedDelegate(
std::unique_ptr<AutomationController::Delegate> delegate);
// These are forwarded to |delegate_|.
void OnInitialized(HRESULT result);
void ConfigureCacheRequest(IUIAutomationCacheRequest* cache_request);
void OnAutomationEvent(IUIAutomation* automation,
IUIAutomationElement* sender,
EVENTID event_id);
void OnFocusChangedEvent(IUIAutomation* automation,
IUIAutomationElement* sender);
private:
friend class base::RefCounted<RefCountedDelegate>;
~RefCountedDelegate();
const std::unique_ptr<AutomationController::Delegate> delegate_;
DISALLOW_COPY_AND_ASSIGN(RefCountedDelegate);
};
RefCountedDelegate::RefCountedDelegate(
std::unique_ptr<AutomationController::Delegate> delegate)
: delegate_(std::move(delegate)) {}
void RefCountedDelegate::OnInitialized(HRESULT result) {
delegate_->OnInitialized(result);
}
void RefCountedDelegate::ConfigureCacheRequest(
IUIAutomationCacheRequest* cache_request) {
delegate_->ConfigureCacheRequest(cache_request);
}
void RefCountedDelegate::OnAutomationEvent(IUIAutomation* automation,
IUIAutomationElement* sender,
EVENTID event_id) {
delegate_->OnAutomationEvent(automation, sender, event_id);
}
void RefCountedDelegate::OnFocusChangedEvent(IUIAutomation* automation,
IUIAutomationElement* sender) {
delegate_->OnFocusChangedEvent(automation, sender);
}
RefCountedDelegate::~RefCountedDelegate() = default;
} // namespace
// This class lives in the automation sequence and is responsible for
// initializing the UIAutomation library and installing the event observers.
class AutomationController::Context {
public:
// Returns a new instance ready for initialization and use in another
// sequence.
static base::WeakPtr<Context> Create();
// Deletes the instance.
void DeleteInAutomationSequence();
// Initializes the context, invoking the delegate's OnInitialized() method
// when done. On success, the delegate's other On*() methods will be invoked
// as events are observed. On failure, this instance self-destructs after
// invoking OnInitialized().
void Initialize(std::unique_ptr<Delegate> delegate);
protected:
class EventHandler;
// The one and only method that may be called from outside of the automation
// sequence.
Context();
~Context();
// Returns an event handler for all event types of interest.
Microsoft::WRL::ComPtr<IUnknown> GetEventHandler();
// Returns a pointer to the event handler's generic interface.
Microsoft::WRL::ComPtr<IUIAutomationEventHandler> GetAutomationEventHandler();
// Returns a pointer to the event handler's focus changed interface.
Microsoft::WRL::ComPtr<IUIAutomationFocusChangedEventHandler>
GetFocusChangedEventHandler();
// Installs an event handler to observe events of interest.
HRESULT InstallObservers();
// Pointer to the delegate. Passed to event handlers.
scoped_refptr<RefCountedDelegate> ref_counted_delegate_;
SEQUENCE_CHECKER(sequence_checker_);
// The automation client.
Microsoft::WRL::ComPtr<IUIAutomation> automation_;
// The event handler.
Microsoft::WRL::ComPtr<IUnknown> event_handler_;
// Weak pointers to the context are given to event handlers.
base::WeakPtrFactory<Context> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(Context);
};
class AutomationController::Context::EventHandler
: public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>,
public IUIAutomationEventHandler,
public IUIAutomationFocusChangedEventHandler {
public:
BEGIN_COM_MAP(AutomationController::Context::EventHandler)
COM_INTERFACE_ENTRY(IUIAutomationEventHandler)
COM_INTERFACE_ENTRY(IUIAutomationFocusChangedEventHandler)
END_COM_MAP()
EventHandler();
~EventHandler();
// Initializes the object. Events will be dispatched back to |context| via
// |context_runner|.
void Initialize(Microsoft::WRL::ComPtr<IUIAutomation> automation,
scoped_refptr<RefCountedDelegate> ref_counted_delegate);
// IUIAutomationEventHandler:
STDMETHOD(HandleAutomationEvent)
(IUIAutomationElement* sender, EVENTID event_id) override;
// IUIAutomationFocusChangedEventHandler:
STDMETHOD(HandleFocusChangedEvent)(IUIAutomationElement* sender) override;
private:
Microsoft::WRL::ComPtr<IUIAutomation> automation_;
// Pointer to the delegate.
scoped_refptr<RefCountedDelegate> ref_counted_delegate_;
DISALLOW_COPY_AND_ASSIGN(EventHandler);
};
AutomationController::Context::EventHandler::EventHandler() = default;
AutomationController::Context::EventHandler::~EventHandler() = default;
void AutomationController::Context::EventHandler::Initialize(
Microsoft::WRL::ComPtr<IUIAutomation> automation,
scoped_refptr<RefCountedDelegate> ref_counted_delegate) {
automation_ = automation;
ref_counted_delegate_ = std::move(ref_counted_delegate);
}
HRESULT AutomationController::Context::EventHandler::HandleAutomationEvent(
IUIAutomationElement* sender,
EVENTID event_id) {
DVLOG(1)
<< "event id: " << GetEventName(event_id) << ", automation id: "
<< GetCachedBstrValue(sender, UIA_AutomationIdPropertyId)
<< ", name: " << GetCachedBstrValue(sender, UIA_NamePropertyId)
<< ", control type: "
<< GetControlType(GetCachedInt32Value(sender, UIA_ControlTypePropertyId))
<< ", is peripheral: "
<< GetCachedBoolValue(sender, UIA_IsPeripheralPropertyId)
<< ", class name: " << GetCachedBstrValue(sender, UIA_ClassNamePropertyId)
<< ", pid: " << GetCachedInt32Value(sender, UIA_ProcessIdPropertyId)
<< ", value: " << GetCachedBstrValue(sender, UIA_ValueValuePropertyId)
<< ", runtime id: "
<< IntArrayToString(
GetCachedInt32ArrayValue(sender, UIA_RuntimeIdPropertyId));
ref_counted_delegate_->OnAutomationEvent(automation_.Get(), sender, event_id);
return S_OK;
}
HRESULT AutomationController::Context::EventHandler::HandleFocusChangedEvent(
IUIAutomationElement* sender) {
DVLOG(1)
<< "focus changed for automation id: "
<< GetCachedBstrValue(sender, UIA_AutomationIdPropertyId)
<< ", name: " << GetCachedBstrValue(sender, UIA_NamePropertyId)
<< ", control type: "
<< GetControlType(GetCachedInt32Value(sender, UIA_ControlTypePropertyId))
<< ", is peripheral: "
<< GetCachedBoolValue(sender, UIA_IsPeripheralPropertyId)
<< ", class name: " << GetCachedBstrValue(sender, UIA_ClassNamePropertyId)
<< ", pid: " << GetCachedInt32Value(sender, UIA_ProcessIdPropertyId)
<< ", value: " << GetCachedBstrValue(sender, UIA_ValueValuePropertyId)
<< ", runtime id: "
<< IntArrayToString(
GetCachedInt32ArrayValue(sender, UIA_RuntimeIdPropertyId));
ref_counted_delegate_->OnFocusChangedEvent(automation_.Get(), sender);
return S_OK;
}
// AutomationController::Context
// --------------------------------------------------
// static
base::WeakPtr<AutomationController::Context>
AutomationController::Context::Create() {
Context* context = new Context();
return context->weak_ptr_factory_.GetWeakPtr();
}
void AutomationController::Context::DeleteInAutomationSequence() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
delete this;
}
void AutomationController::Context::Initialize(
std::unique_ptr<Delegate> delegate) {
// This and all other methods must be called in the automation sequence.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ref_counted_delegate_ =
base::MakeRefCounted<RefCountedDelegate>(std::move(delegate));
HRESULT result =
::CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&automation_));
if (SUCCEEDED(result))
result = automation_ ? InstallObservers() : E_FAIL;
// Now that the observers are installed, it's time to signal that the
// initialization is done and that events will be received.
ref_counted_delegate_->OnInitialized(result);
// Self-destruct immediately if initialization failed to reduce overhead.
if (FAILED(result))
delete this;
}
AutomationController::Context::Context() : weak_ptr_factory_(this) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
AutomationController::Context::~Context() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (event_handler_) {
event_handler_.Reset();
automation_->RemoveAllEventHandlers();
}
}
Microsoft::WRL::ComPtr<IUnknown>
AutomationController::Context::GetEventHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!event_handler_) {
ATL::CComObject<EventHandler>* obj = nullptr;
HRESULT result = ATL::CComObject<EventHandler>::CreateInstance(&obj);
if (SUCCEEDED(result)) {
obj->Initialize(automation_, ref_counted_delegate_);
obj->QueryInterface(event_handler_.GetAddressOf());
}
}
return event_handler_;
}
Microsoft::WRL::ComPtr<IUIAutomationEventHandler>
AutomationController::Context::GetAutomationEventHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Microsoft::WRL::ComPtr<IUIAutomationEventHandler> handler;
GetEventHandler().CopyTo(handler.GetAddressOf());
return handler;
}
Microsoft::WRL::ComPtr<IUIAutomationFocusChangedEventHandler>
AutomationController::Context::GetFocusChangedEventHandler() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Microsoft::WRL::ComPtr<IUIAutomationFocusChangedEventHandler> handler;
GetEventHandler().CopyTo(handler.GetAddressOf());
return handler;
}
HRESULT AutomationController::Context::InstallObservers() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(automation_);
// Create a cache request so that elements received by way of events contain
// all data needed for processing.
Microsoft::WRL::ComPtr<IUIAutomationCacheRequest> cache_request;
HRESULT result =
automation_->CreateCacheRequest(cache_request.GetAddressOf());
if (FAILED(result))
return result;
ConfigureCacheRequestForLogging(cache_request.Get());
ref_counted_delegate_->ConfigureCacheRequest(cache_request.Get());
// Observe changes in focus.
result = automation_->AddFocusChangedEventHandler(
cache_request.Get(), GetFocusChangedEventHandler().Get());
if (FAILED(result))
return result;
// Observe invocations.
Microsoft::WRL::ComPtr<IUIAutomationElement> desktop;
result = automation_->GetRootElement(desktop.GetAddressOf());
if (desktop) {
result = automation_->AddAutomationEventHandler(
UIA_Invoke_InvokedEventId, desktop.Get(), TreeScope_Subtree,
cache_request.Get(), GetAutomationEventHandler().Get());
}
return result;
}
// AutomationController --------------------------------------------------------
AutomationController::AutomationController(std::unique_ptr<Delegate> delegate) {
ui::win::CreateATLModuleIfNeeded();
// Create the task runner on which the automation client lives.
automation_task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
// Initialize the context on the automation task runner.
context_ = Context::Create();
automation_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AutomationController::Context::Initialize,
context_, std::move(delegate)));
}
AutomationController::~AutomationController() {
// context_ is still valid when the caller destroys the instance before the
// callback(s) have fired. In this case, delete the context in the automation
// sequence before joining with it. DeleteSoon is not used because the monitor
// has only a WeakPtr to the context that is bound to the automation sequence.
automation_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&AutomationController::Context::DeleteInAutomationSequence,
context_));
}