chromium/chrome/browser/ui/views/accessibility/uia_accessibility_event_waiter.cc

// Copyright 2019 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/ui/views/accessibility/uia_accessibility_event_waiter.h"

#include <algorithm>
#include <numeric>
#include <utility>

#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_variant.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/base/win/atl_module.h"

UiaAccessibilityEventWaiter::UiaAccessibilityEventWaiter(
    UiaAccessibilityWaiterInfo info) {
  // Create the event thread, and pump messages via |initialization_loop| until
  // initialization is complete.
  base::RunLoop initialization_loop;
  base::PlatformThread::Create(0, &thread_, &thread_handle_);
  thread_.Init(this, info, initialization_loop.QuitClosure(),
               shutdown_loop_.QuitClosure());
  initialization_loop.Run();
}

UiaAccessibilityEventWaiter::~UiaAccessibilityEventWaiter() {}

void UiaAccessibilityEventWaiter::Wait() {
  // Pump messages via |shutdown_loop_| until the thread is complete.
  shutdown_loop_.Run();
  base::PlatformThread::Join(thread_handle_);
}

void UiaAccessibilityEventWaiter::WaitWithTimeout(base::TimeDelta timeout) {
  // Pump messages via |shutdown_loop_| until the thread is complete.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE, shutdown_loop_.QuitClosure(), timeout);
  shutdown_loop_.Run();
  base::PlatformThread::Join(thread_handle_);
}

UiaAccessibilityEventWaiter::Thread::Thread() {}

UiaAccessibilityEventWaiter::Thread::~Thread() {}

void UiaAccessibilityEventWaiter::Thread::SendShutdownSignal() {
  shutdown_signal_.Signal();
}

void UiaAccessibilityEventWaiter::Thread::Init(
    UiaAccessibilityEventWaiter* owner,
    const UiaAccessibilityWaiterInfo& info,
    base::OnceClosure initialization,
    base::OnceClosure shutdown) {
  owner_ = owner;
  info_ = info;
  initialization_complete_ = std::move(initialization);
  shutdown_complete_ = std::move(shutdown);
}

void UiaAccessibilityEventWaiter::Thread::ThreadMain() {
  // UIA calls must be made on an MTA thread to prevent random timeouts.
  base::win::ScopedCOMInitializer com_init{
      base::win::ScopedCOMInitializer::kMTA};

  // Create an instance of the CUIAutomation class.
  CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER,
                   IID_PPV_ARGS(&uia_));
  CHECK(uia_.Get());

  // Find the IUIAutomationElement for the root content window.
  uia_->ElementFromHandle(info_.hwnd, &root_);
  CHECK(root_.Get());

  // Create the event handler.
  ui::win::CreateATLModuleIfNeeded();
  CHECK(
      SUCCEEDED(CComObject<EventHandler>::CreateInstance(&uia_event_handler_)));
  uia_event_handler_->AddRef();
  uia_event_handler_->Init(this, root_);

  // Create a cache request to avoid cross-thread issues when logging.
  CHECK(SUCCEEDED(uia_->CreateCacheRequest(&cache_request_)));
  CHECK(cache_request_.Get());
  CHECK(SUCCEEDED(cache_request_->AddProperty(UIA_NamePropertyId)));
  CHECK(SUCCEEDED(cache_request_->AddProperty(UIA_AriaRolePropertyId)));

  // Match AccEvent by using Raw View
  Microsoft::WRL::ComPtr<IUIAutomationCondition> pRawCond;
  CHECK(SUCCEEDED(uia_->get_RawViewCondition(&pRawCond)));
  CHECK(SUCCEEDED(cache_request_->put_TreeFilter(pRawCond.Get())));

  // Subscribe to focus events.
  uia_->AddFocusChangedEventHandler(cache_request_.Get(),
                                    uia_event_handler_.Get());

  // Subscribe to all property-change events.
  constexpr PROPERTYID kMinProp = UIA_RuntimeIdPropertyId;
  constexpr PROPERTYID kMaxProp = UIA_HeadingLevelPropertyId;
  std::array<PROPERTYID, (kMaxProp - kMinProp) + 1> property_list;
  std::iota(property_list.begin(), property_list.end(), kMinProp);
  uia_->AddPropertyChangedEventHandlerNativeArray(
      root_.Get(), TreeScope::TreeScope_Subtree, cache_request_.Get(),
      uia_event_handler_.Get(), &property_list[0], property_list.size());

  // Subscribe to all structure-change events.
  uia_->AddStructureChangedEventHandler(root_.Get(), TreeScope_Subtree,
                                        cache_request_.Get(),
                                        uia_event_handler_.Get());

  // Subscribe to all automation events (except structure-change events and
  // live-region events, which are handled elsewhere).
  constexpr EVENTID kMinEvent = UIA_ToolTipOpenedEventId;
  constexpr EVENTID kMaxEvent = UIA_NotificationEventId;
  for (EVENTID event_id = kMinEvent; event_id <= kMaxEvent; ++event_id) {
    if (event_id != UIA_StructureChangedEventId &&
        event_id != UIA_LiveRegionChangedEventId) {
      uia_->AddAutomationEventHandler(
          event_id, root_.Get(), TreeScope::TreeScope_Subtree,
          cache_request_.Get(), uia_event_handler_.Get());
    }
  }

  // Subscribe to live-region change events.  This must be the last event we
  // subscribe to, because |AXFragmentRootWin| will fire events when advised of
  // the subscription, and this can hang the test-process (on Windows 19H1+) if
  // we're simultaneously trying to subscribe to other events.
  uia_->AddAutomationEventHandler(
      UIA_LiveRegionChangedEventId, root_.Get(), TreeScope::TreeScope_Subtree,
      cache_request_.Get(), uia_event_handler_.Get());

  // Signal that initialization is complete; this will wake the main thread to
  // start executing the test code after this waiter has been constructed.
  std::move(initialization_complete_).Run();

  // Wait for shutdown signal.
  shutdown_signal_.Wait();

  // Cleanup
  uia_->RemoveAllEventHandlers();
  uia_event_handler_->CleanUp();
  uia_event_handler_.Reset();
  cache_request_.Reset();
  root_.Reset();
  uia_.Reset();

  std::move(shutdown_complete_).Run();
}

UiaAccessibilityEventWaiter::Thread::EventHandler::EventHandler() {}

UiaAccessibilityEventWaiter::Thread::EventHandler::~EventHandler() {}

void UiaAccessibilityEventWaiter::Thread::EventHandler::Init(
    UiaAccessibilityEventWaiter::Thread* owner,
    Microsoft::WRL::ComPtr<IUIAutomationElement> root) {
  owner_ = owner;
  root_ = root;
}

void UiaAccessibilityEventWaiter::Thread::EventHandler::CleanUp() {
  owner_ = nullptr;
  root_.Reset();
}

HRESULT
UiaAccessibilityEventWaiter::Thread::EventHandler::HandleFocusChangedEvent(
    IUIAutomationElement* sender) {
  // Add focus changed event handling code here.
  return S_OK;
}

HRESULT
UiaAccessibilityEventWaiter::Thread::EventHandler::HandlePropertyChangedEvent(
    IUIAutomationElement* sender,
    PROPERTYID property_id,
    VARIANT new_value) {
  if (owner_ &&
      property_id ==
          ui::AXPlatformNodeWin::MojoEventToUIAProperty(owner_->info_.event) &&
      MatchesNameRole(sender)) {
    owner_->SendShutdownSignal();
  }
  return S_OK;
}

HRESULT
UiaAccessibilityEventWaiter::Thread::EventHandler::HandleStructureChangedEvent(
    IUIAutomationElement* sender,
    StructureChangeType change_type,
    SAFEARRAY* runtime_id) {
  // Add structure changed handling code here.
  return S_OK;
}

HRESULT
UiaAccessibilityEventWaiter::Thread::EventHandler::HandleAutomationEvent(
    IUIAutomationElement* sender,
    EVENTID event_id) {
  if (owner_ &&
      event_id ==
          ui::AXPlatformNodeWin::MojoEventToUIAEvent(owner_->info_.event) &&
      MatchesNameRole(sender)) {
    owner_->SendShutdownSignal();
  }
  return S_OK;
}

bool UiaAccessibilityEventWaiter::Thread::EventHandler::MatchesNameRole(
    IUIAutomationElement* sender) {
  base::win::ScopedBstr aria_role;
  base::win::ScopedBstr name;
  sender->get_CachedAriaRole(aria_role.Receive());
  sender->get_CachedName(name.Receive());

  if (std::wstring(aria_role.Get(), SysStringLen(aria_role.Get())) ==
          owner_->info_.role &&
      std::wstring(name.Get(), SysStringLen(name.Get())) ==
          owner_->info_.name) {
    return true;
  }
  return false;
}