blob: 82a9f737e5f2803ebbd0c8f5f115f9503a5c4654 [file] [log] [blame]
// 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