910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>
#include <array>

#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_mock_time_message_loop_task_runner.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/time.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
#include "content/common/features.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/scoped_accessibility_mode_override.h"
#include "content/shell/browser/shell.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

class ProgressiveAccessibilityTest
    : public ContentBrowserTest,
      public testing::WithParamInterface<
          features::ProgressiveAccessibilityMode> {
 protected:
  ProgressiveAccessibilityTest() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kProgressiveAccessibility,
        {{"progressive_accessibility_mode",
          GetParam() == features::ProgressiveAccessibilityMode::kDisableOnHide
              ? "disable_on_hide"
              : "only_enable"}});
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Tests that a WebContents only gets its accessibility mode flags when it is
// revealed.
IN_PROC_BROWSER_TEST_P(ProgressiveAccessibilityTest, AccessibilityOnReveal) {
  // Create a second WebContents and hide it.
  Shell* shell_2 =
      Shell::CreateNewWindow(shell()->web_contents()->GetBrowserContext(),
                             GURL(), nullptr, gfx::Size());
  shell_2->web_contents()->WasHidden();

  // Enable accessibility.
  ScopedAccessibilityModeOverride basic(ui::kAXModeBasic);

  // The visible WebContents received it.
  EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);

  // The hidden WebContents did not.
  EXPECT_EQ(shell_2->web_contents()->GetAccessibilityMode(), ui::AXMode());

  // But does when shown.
  shell_2->web_contents()->WasShown();
  EXPECT_EQ(shell_2->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);

  // The original WebContents should be unaffected.
  EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);
}

// Tests that accessibility is disabled for a hidden WebContents after five
// others have been hidden and five minutes has passed.
IN_PROC_BROWSER_TEST_P(ProgressiveAccessibilityTest, DisableAfterHide) {
  base::ScopedMockTimeMessageLoopTaskRunner mock_time_runner;

  // Enable accessibility.
  ScopedAccessibilityModeOverride basic(ui::kAXModeBasic);

  // The initial WebContents has received the new mode.
  EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);

  // Create five WebContentses (in addition to the initial one).
  std::array<Shell*, BrowserAccessibilityStateImpl::kMaxPreservedWebContents>
      shells;
  std::ranges::generate(
      shells,
      [browser_context = shell()->web_contents()->GetBrowserContext()]() {
        auto* shell = Shell::CreateNewWindow(browser_context, GURL(), nullptr,
                                             gfx::Size());
        // Each new shell has accessibility enabled at creation.
        EXPECT_EQ(shell->web_contents()->GetAccessibilityMode(),
                  ui::kAXModeBasic);
        return shell;
      });

  // Hide all six, starting with the initial one.
  shell()->web_contents()->WasHidden();
  std::ranges::for_each(
      shells, [](Shell* shell) { shell->web_contents()->WasHidden(); });

  // Let time pass.
  mock_time_runner->FastForwardBy(
      BrowserAccessibilityStateImpl::GetMaxDisableDelay());

  // The last five all still have accessibility enabled.
  std::ranges::for_each(shells, [](Shell* shell) {
    EXPECT_EQ(shell->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);
  });

  // The initial WebContents does not if this is the DisableOnHide variant.
  switch (GetParam()) {
    case features::ProgressiveAccessibilityMode::kOnlyEnable:
      EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(),
                ui::kAXModeBasic);
      break;
    case features::ProgressiveAccessibilityMode::kDisableOnHide:
      EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(), ui::AXMode());
      break;
  }
}

// Tests that nothing bad happens if a WebContents is destroyed after being
// hidden.
IN_PROC_BROWSER_TEST_P(ProgressiveAccessibilityTest, DestroyAfterHide) {
  // Enable accessibility.
  ScopedAccessibilityModeOverride basic(ui::kAXModeBasic);

  // Hide the WebContents
  shell()->web_contents()->WasHidden();

  // Now destroy it.
  shell()->Close();
}

// Tests that accessibility is not disabled when a screen reader is active.
IN_PROC_BROWSER_TEST_P(ProgressiveAccessibilityTest,
                       NoDisableWithScreenReader) {
  base::ScopedMockTimeMessageLoopTaskRunner mock_time_runner;

  // Enable accessibility via a screen reader.
  const ui::AXMode mode = ui::kAXModeBasic | ui::AXMode::kScreenReader;
  ScopedAccessibilityModeOverride basic(mode);

  // Create five WebContentses (in addition to the initial one).
  std::array<Shell*, BrowserAccessibilityStateImpl::kMaxPreservedWebContents>
      shells;
  std::ranges::generate(
      shells,
      [browser_context = shell()->web_contents()->GetBrowserContext()]() {
        return Shell::CreateNewWindow(browser_context, GURL(), nullptr,
                                      gfx::Size());
      });

  // Hide all six, starting with the initial one.
  shell()->web_contents()->WasHidden();
  std::ranges::for_each(
      shells, [](Shell* shell) { shell->web_contents()->WasHidden(); });

  // Let time pass.
  mock_time_runner->FastForwardBy(
      BrowserAccessibilityStateImpl::GetMaxDisableDelay());

  // Accessibility should still be enabled for the first WebContents.
  EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(), mode);
}

// Tests that accessibility is not disabled when a screen reader is detected
// while there are hidden WebContents.
IN_PROC_BROWSER_TEST_P(ProgressiveAccessibilityTest,
                       NoDisableWithScreenReaderLater) {
  base::ScopedMockTimeMessageLoopTaskRunner mock_time_runner;

  // Enable accessibility.
  ScopedAccessibilityModeOverride basic(ui::kAXModeBasic);

  // Create five WebContentses (in addition to the initial one).
  std::array<Shell*, BrowserAccessibilityStateImpl::kMaxPreservedWebContents>
      shells;
  std::ranges::generate(
      shells,
      [browser_context = shell()->web_contents()->GetBrowserContext()]() {
        return Shell::CreateNewWindow(browser_context, GURL(), nullptr,
                                      gfx::Size());
      });

  // Hide all six, starting with the initial one.
  shell()->web_contents()->WasHidden();
  std::ranges::for_each(
      shells, [](Shell* shell) { shell->web_contents()->WasHidden(); });

  // Now a screen reader is detected.
  ScopedAccessibilityModeOverride screen_reader(ui::AXMode::kScreenReader);

  // Let time pass.
  mock_time_runner->FastForwardBy(
      BrowserAccessibilityStateImpl::GetMaxDisableDelay());

  // The last five all still have basic accessibility enabled. (They don't
  // know about the screen reader yet since they're hidden).
  std::ranges::for_each(shells, [](Shell* shell) {
    EXPECT_EQ(shell->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);
  });

  // As should the initial WebContents.
  EXPECT_EQ(shell()->web_contents()->GetAccessibilityMode(), ui::kAXModeBasic);
}

INSTANTIATE_TEST_SUITE_P(
    OnlyEnable,
    ProgressiveAccessibilityTest,
    testing::Values(features::ProgressiveAccessibilityMode::kOnlyEnable));
INSTANTIATE_TEST_SUITE_P(
    DisableOnHide,
    ProgressiveAccessibilityTest,
    testing::Values(features::ProgressiveAccessibilityMode::kDisableOnHide));

}  // namespace

}  // namespace content