chromium/chrome/browser/win/automation_controller.cc

// 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_));
}