chromium/chrome/browser/ash/accessibility/autoclick_test_utils.cc

// Copyright 2023 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/ash/accessibility/autoclick_test_utils.h"

#include "ash/accessibility/autoclick/autoclick_controller.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/accessibility_controller_enums.h"
#include "ash/shell.h"
#include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
#include "ash/system/accessibility/autoclick_menu_view.h"
#include "base/check_op.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/accessibility_test_utils.h"
#include "chrome/browser/ash/accessibility/automation_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/extension_constants.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_host_test_helper.h"
#include "ui/events/test/event_generator.h"

namespace {
const int kDefaultDelay = 5;
}  // namespace

namespace ash {

AutoclickTestUtils::AutoclickTestUtils(Profile* profile) {
  CHECK_EQ(false, AccessibilityManager::Get()->IsAutoclickEnabled());
  profile_ = profile;
  console_observer_ = std::make_unique<ExtensionConsoleErrorObserver>(
      profile_, extension_misc::kAccessibilityCommonExtensionId);

  SetAutoclickDelayMs(kDefaultDelay);

  pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
  pref_change_registrar_->Init(profile_->GetPrefs());
  pref_change_registrar_->Add(
      prefs::kAccessibilityAutoclickEventType,
      base::BindRepeating(&AutoclickTestUtils::OnEventTypePrefChanged,
                          GetWeakPtr()));

  automation_utils_ = std::make_unique<AutomationTestUtils>(
      extension_misc::kAccessibilityCommonExtensionId);
}

AutoclickTestUtils::~AutoclickTestUtils() {
  pref_change_registrar_.reset();
}

void AutoclickTestUtils::LoadAutoclick(bool install_automation_utils) {
  extensions::ExtensionHostTestHelper host_helper(
      profile_, extension_misc::kAccessibilityCommonExtensionId);
  AccessibilityManager::Get()->EnableAutoclick(true);
  Shell::Get()
      ->autoclick_controller()
      ->GetMenuBubbleControllerForTesting()
      ->SetAnimateForTesting(false);
  host_helper.WaitForHostCompletedFirstLoad();
  WaitForAutoclickReady();
  if (install_automation_utils) {
    automation_utils_->SetUpTestSupport();
  }
}

void AutoclickTestUtils::SetAutoclickDelayMs(int ms) {
  profile_->GetPrefs()->SetInteger(prefs::kAccessibilityAutoclickDelayMs, ms);
  profile_->GetPrefs()->CommitPendingWrite();
}

void AutoclickTestUtils::SetAutoclickEventTypeWithHover(
    ui::test::EventGenerator* generator,
    AutoclickEventType type) {
  // Check if we already have the right type selected.
  if (profile_->GetPrefs()->GetInteger(
          prefs::kAccessibilityAutoclickEventType) == static_cast<int>(type)) {
    return;
  }

  // Change the Autoclick delay to a value we know will work for this method
  // (in case it was set to a very large value before this method was called).
  int old_delay =
      profile_->GetPrefs()->GetInteger(prefs::kAccessibilityAutoclickDelayMs);
  SetAutoclickDelayMs(kDefaultDelay);

  // Find the menu button.
  AutoclickMenuView::ButtonId button_id;
  switch (type) {
    case AutoclickEventType::kLeftClick:
      button_id = AutoclickMenuView::ButtonId::kLeftClick;
      break;
    case AutoclickEventType::kRightClick:
      button_id = AutoclickMenuView::ButtonId::kRightClick;
      break;
    case AutoclickEventType::kDoubleClick:
      button_id = AutoclickMenuView::ButtonId::kDoubleClick;
      break;
    case AutoclickEventType::kDragAndDrop:
      button_id = AutoclickMenuView::ButtonId::kDragAndDrop;
      break;
    case AutoclickEventType::kScroll:
      button_id = AutoclickMenuView::ButtonId::kScroll;
      break;
    case AutoclickEventType::kNoAction:
      button_id = AutoclickMenuView::ButtonId::kPause;
      break;
  }
  AutoclickMenuView* menu_view = Shell::Get()
                                     ->autoclick_controller()
                                     ->GetMenuBubbleControllerForTesting()
                                     ->menu_view_;
  CHECK_NE(nullptr, menu_view);
  auto* button_view = menu_view->GetViewByID(static_cast<int>(button_id));
  CHECK_NE(nullptr, button_view);

  // Hover over it.
  const gfx::Rect bounds = button_view->GetBoundsInScreen();
  generator->MoveMouseTo(bounds.CenterPoint());

  // Wait for the pref change, indicating the button was pressed.
  base::RunLoop runner;
  pref_change_waiter_ = runner.QuitClosure();
  runner.Run();

  // Restore the delay to its previous value.
  SetAutoclickDelayMs(old_delay);
}

void AutoclickTestUtils::WaitForPageLoad(const std::string& url) {
  automation_utils_->WaitForPageLoad(url);
}

void AutoclickTestUtils::WaitForTextSelectionChangedEvent() {
  automation_utils_->WaitForTextSelectionChangedEvent();
}

void AutoclickTestUtils::HoverOverHtmlElement(
    ui::test::EventGenerator* generator,
    const std::string& name,
    const std::string& role) {
  const gfx::Rect bounds = automation_utils_->GetNodeBoundsInRoot(name, role);
  generator->MoveMouseTo(bounds.CenterPoint());
}

void AutoclickTestUtils::ObserveFocusRings() {
  // Create a callback for the focus ring observer.
  AccessibilityManager::Get()->SetFocusRingObserverForTest(base::BindRepeating(
      &AutoclickTestUtils::OnFocusRingChanged, GetWeakPtr()));
}

void AutoclickTestUtils::WaitForFocusRingChanged() {
  loop_runner_ = std::make_unique<base::RunLoop>();
  loop_runner_->Run();
}

gfx::Rect AutoclickTestUtils::GetNodeBoundsInRoot(const std::string& name,
                                                  const std::string& role) {
  return automation_utils_->GetNodeBoundsInRoot(name, role);
}

gfx::Rect AutoclickTestUtils::GetBoundsForNodeInRootByClassName(
    const std::string& class_name) {
  return automation_utils_->GetBoundsForNodeInRootByClassName(class_name);
}

void AutoclickTestUtils::WaitForAutoclickReady() {
  base::ScopedAllowBlockingForTesting allow_blocking;
  std::string script = base::StringPrintf(R"JS(
    (async function() {
      window.accessibilityCommon.setFeatureLoadCallbackForTest('autoclick',
          () => {
            chrome.test.sendScriptResult('ready');
          });
    })();
  )JS");
  base::Value result =
      extensions::browsertest_util::ExecuteScriptInBackgroundPage(
          profile_, extension_misc::kAccessibilityCommonExtensionId, script);
  ASSERT_EQ("ready", result);
}

void AutoclickTestUtils::OnEventTypePrefChanged() {
  if (pref_change_waiter_) {
    std::move(pref_change_waiter_).Run();
  }
}

void AutoclickTestUtils::OnFocusRingChanged() {
  if (loop_runner_ && loop_runner_->running()) {
    loop_runner_->Quit();
  }
}

base::WeakPtr<AutoclickTestUtils> AutoclickTestUtils::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

}  // namespace ash