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

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>
#include <string>

#include "ash/accessibility/magnifier/fullscreen_magnifier_controller.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "base/command_line.h"
#include "build/build_config.h"
#include "chrome/browser/ash/accessibility/accessibility_feature_browsertest.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/ash/accessibility/fullscreen_magnifier_test_helper.h"
#include "chrome/browser/ash/accessibility/magnification_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#include "content/public/test/accessibility_notification_waiter.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/browsertest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/event_generator.h"

namespace ash {

namespace {

const char kDataURIPrefix[] = "data:text/html;charset=utf-8,";
const char kTestHtmlContent[] =
    "<body style=\"margin-top:0;margin-left:0\">"
    "<button type=\"button\" name=\"test_button_1\" id=\"test_button\" "
    "style=\"margin-left:0;margin-top:0;width:100;height:50\">"
    "Big Button 1</button>"
    "</body>";

FullscreenMagnifierController* GetFullscreenMagnifierController() {
  return Shell::Get()->fullscreen_magnifier_controller();
}

gfx::Rect GetViewPort() {
  return GetFullscreenMagnifierController()->GetViewportRect();
}

}  // namespace

class FullscreenMagnifierControllerTest
    : public AccessibilityFeatureBrowserTest {
 public:
  FullscreenMagnifierControllerTest() = default;
  FullscreenMagnifierControllerTest(const FullscreenMagnifierControllerTest&) =
      delete;
  FullscreenMagnifierControllerTest& operator=(
      const FullscreenMagnifierControllerTest&) = delete;
  ~FullscreenMagnifierControllerTest() override = default;

 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Make screens sufficiently wide to host 2 browsers side by side.
    command_line->AppendSwitchASCII("ash-host-window-bounds", "1200x800");
  }

  void SetUpOnMainThread() override {
    console_observer_ = std::make_unique<ExtensionConsoleErrorObserver>(
        GetProfile(), extension_misc::kAccessibilityCommonExtensionId);

    aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow();
    generator_ = std::make_unique<ui::test::EventGenerator>(root_window);
    // Start in a known location, centered on the screen.
    helper_ =
        std::make_unique<FullscreenMagnifierTestHelper>(gfx::Point(600, 400));

    AccessibilityFeatureBrowserTest::SetUpOnMainThread();
  }

  ui::test::EventGenerator* generator() { return generator_.get(); }
  FullscreenMagnifierTestHelper* helper() { return helper_.get(); }

 private:
  std::unique_ptr<ui::test::EventGenerator> generator_;
  std::unique_ptr<FullscreenMagnifierTestHelper> helper_;
  std::unique_ptr<ExtensionConsoleErrorObserver> console_observer_;
};

IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
                       FollowFocusOnWebButton) {
  helper()->LoadMagnifier(GetProfile());

  AutomationTestUtils utils(extension_misc::kAccessibilityCommonExtensionId);
  utils.SetUpTestSupport();
  const std::string url = std::string(kDataURIPrefix) + kTestHtmlContent;
  NavigateToUrl(GURL(url));
  utils.WaitForPageLoad(url);

  // Move magnifier window to exclude the button.
  const gfx::Rect button_bounds =
      utils.GetNodeBoundsInRoot("Big Button 1", "button");
  helper()->MoveMagnifierWindow(
      button_bounds.right() + GetViewPort().width(),
      button_bounds.bottom() + GetViewPort().height());
  const gfx::Rect view_port_before_focus = GetViewPort();
  EXPECT_FALSE(view_port_before_focus.Contains(button_bounds));

  // Set the focus on the button.
  utils.SetFocusOnNode("Big Button 1", "button");

  // Verify the magnifier window has moved to contain the button.
  helper()->WaitForMagnifierBoundsChanged();
  EXPECT_TRUE(GetViewPort().Contains(button_bounds));
}

IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
                       AnimatesToFollowKeyboardFocus) {
  helper()->LoadMagnifier(GetProfile());

  AutomationTestUtils utils(extension_misc::kAccessibilityCommonExtensionId);
  utils.SetUpTestSupport();
  const std::string url = std::string(kDataURIPrefix) + kTestHtmlContent;
  NavigateToUrl(GURL(url));
  utils.WaitForPageLoad(url);

  // Move magnifier window to exclude the button.
  const gfx::Rect button_bounds =
      utils.GetNodeBoundsInRoot("Big Button 1", "button");
  helper()->MoveMagnifierWindow(
      button_bounds.right() + GetViewPort().width(),
      button_bounds.bottom() + GetViewPort().height());
  const gfx::Rect view_port_before_focus = GetViewPort();
  EXPECT_FALSE(view_port_before_focus.Contains(button_bounds));

  MagnifierAnimationWaiter magnifier_waiter(GetFullscreenMagnifierController());

  // Set the focus on the button.
  utils.SetFocusOnNode("Big Button 1", "button");

  // After the some animation, we should have arrived at the right viewport.
  do {
    magnifier_waiter.Wait();
  } while (!GetViewPort().Contains(button_bounds));
}

IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
                       MovesContinuouslyWithMouse) {
  GetProfile()->GetPrefs()->SetInteger(
      prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
      static_cast<int>(MagnifierMouseFollowingMode::kContinuous));
  helper()->LoadMagnifier(GetProfile());

  // Screen resolution 1200x800.
  gfx::Point center_point(600, 400);
  EXPECT_EQ(GetViewPort().CenterPoint(), center_point);

  for (int i = 0; i < 3; i++) {
    // Move left and down. The viewport should move towards the mouse but not
    // all the way to it.
    gfx::Point mouse_point = gfx::Point(500 - i * 50, 500 + i * 50);
    generator()->MoveMouseTo(mouse_point);
    // No need to wait: Without going through the extension loop needed for
    // focus observation, the movement is all within the same process.
    EXPECT_GT(GetViewPort().CenterPoint().x(), mouse_point.x());
    EXPECT_LT(GetViewPort().CenterPoint().x(), center_point.x());
    EXPECT_LT(GetViewPort().CenterPoint().y(), mouse_point.y());
    EXPECT_GT(GetViewPort().CenterPoint().y(), center_point.y());
    center_point = GetViewPort().CenterPoint();
  }
}

IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
                       MovesWithMouseAtEdge) {
  GetProfile()->GetPrefs()->SetInteger(
      prefs::kAccessibilityScreenMagnifierMouseFollowingMode,
      static_cast<int>(MagnifierMouseFollowingMode::kEdge));
  helper()->LoadMagnifier(GetProfile());

  // Screen resolution 1200x800.
  gfx::Point initial_center = GetViewPort().CenterPoint();
  gfx::Point mouse_point(550, 450);
  generator()->MoveMouseTo(mouse_point);
  // No need to wait: Without going through the extension loop needed for focus
  // observation, the movement is all within the same process.
  EXPECT_EQ(GetViewPort().CenterPoint(), initial_center);

  // Move left and down. The viewport should not move as we are still within it.
  gfx::Point new_mouse_point = gfx::Point(500, 500);
  generator()->MoveMouseTo(new_mouse_point);
  EXPECT_EQ(GetViewPort().CenterPoint(), initial_center);

  // Move mouse to the left/bottom edge. The viewport should move in that
  // direction. Note we have to scale the mouse point based on the magnifer
  // scale to actually reach the edge of the viewport.
  new_mouse_point = gfx::Point(0, 800);
  generator()->MoveMouseTo(new_mouse_point);
  EXPECT_GT(GetViewPort().CenterPoint().x(), new_mouse_point.x());
  EXPECT_LT(GetViewPort().CenterPoint().x(), initial_center.x());
  EXPECT_LT(GetViewPort().CenterPoint().y(), new_mouse_point.y());
  EXPECT_GT(GetViewPort().CenterPoint().y(), initial_center.y());
}

IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest,
                       ChangeZoomWithAccelerator) {
  helper()->LoadMagnifier(GetProfile());

  // Press keyboard shortcut to zoom in. Default zoom is 2.0.
  generator()->PressAndReleaseKeyAndModifierKeys(
      ui::VKEY_BRIGHTNESS_UP, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
  float scale = GetFullscreenMagnifierController()->GetScale();
  EXPECT_LT(2.0f, scale);

  // Keyboard shortcut to zoom out.
  generator()->PressAndReleaseKeyAndModifierKeys(
      ui::VKEY_BRIGHTNESS_DOWN, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN);
  // Note the scale might not be 2.0 again.
  EXPECT_GT(scale, GetFullscreenMagnifierController()->GetScale());
}

IN_PROC_BROWSER_TEST_F(FullscreenMagnifierControllerTest, ChangeZoomWithPrefs) {
  helper()->LoadMagnifier(GetProfile());

  // Change the bounds pref.
  GetProfile()->GetPrefs()->SetDouble(prefs::kAccessibilityScreenMagnifierScale,
                                      4.0);
  EXPECT_EQ(4.0, GetFullscreenMagnifierController()->GetScale());

  GetProfile()->GetPrefs()->SetDouble(prefs::kAccessibilityScreenMagnifierScale,
                                      10.5);
  EXPECT_EQ(10.5, GetFullscreenMagnifierController()->GetScale());
}

// TODO(crbug.com/261462562): Add centered mouse following mode browsertest.

}  // namespace ash