| // Copyright 2018 The Chromium Authors |
| // 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/functional/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/thread_pool.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); |
| |
| RefCountedDelegate(const RefCountedDelegate&) = delete; |
| RefCountedDelegate& operator=(const RefCountedDelegate&) = delete; |
| |
| // 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_; |
| }; |
| |
| 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(); |
| |
| Context(const Context&) = delete; |
| Context& operator=(const Context&) = delete; |
| |
| // 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_{this}; |
| }; |
| |
| 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(const EventHandler&) = delete; |
| EventHandler& operator=(const EventHandler&) = delete; |
| |
| ~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_; |
| }; |
| |
| 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); |
| } |
| |
| STDMETHODIMP |
| 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; |
| } |
| |
| STDMETHODIMP |
| 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() { |
| 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(IID_PPV_ARGS(&event_handler_)); |
| } |
| } |
| return event_handler_; |
| } |
| |
| Microsoft::WRL::ComPtr<IUIAutomationEventHandler> |
| AutomationController::Context::GetAutomationEventHandler() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Microsoft::WRL::ComPtr<IUIAutomationEventHandler> handler; |
| GetEventHandler().As(&handler); |
| return handler; |
| } |
| |
| Microsoft::WRL::ComPtr<IUIAutomationFocusChangedEventHandler> |
| AutomationController::Context::GetFocusChangedEventHandler() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| Microsoft::WRL::ComPtr<IUIAutomationFocusChangedEventHandler> handler; |
| GetEventHandler().As(&handler); |
| 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); |
| 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); |
| 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::ThreadPool::CreateSequencedTaskRunner( |
| {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_)); |
| } |