blob: 1e3e2d10b15b6dbe46ce768d1695fab8f5094285 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/public/browser/scoped_accessibility_mode.h"
#include <memory>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "content/public/browser/browser_accessibility_state.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/shell/browser/shell.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_mode.h"
#include "ui/accessibility/ax_mode_observer.h"
#include "ui/accessibility/platform/ax_platform.h"
namespace content {
using ::testing::_;
namespace {
class MockAXModeObserver : public ui::AXModeObserver {
public:
MOCK_METHOD(void, OnAXModeAdded, (ui::AXMode mode), (override));
};
} // namespace
// A test for ScopedAccessibilityModes vended by BrowserAccessibilityState.
class ScopedAccessibilityModeTest : public ContentBrowserTest {
protected:
ScopedAccessibilityModeTest() = default;
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
accessibility_state_ = BrowserAccessibilityState::GetInstance();
// Get the initial BrowserContext and WebContents created by
// ContentBrowserTest.
auto* const default_shell = shell();
browser_context1_ = default_shell->web_contents()->GetBrowserContext();
web_contents1_ = default_shell->web_contents();
// Create a second WebContents belonging to the initial BrowserContext.
web_contents2_ = CreateBrowser()->web_contents();
// Create a third WebContents belonging to a distinct BrowserContext.
web_contents3_ = CreateOffTheRecordBrowser()->web_contents();
browser_context2_ = web_contents3_->GetBrowserContext();
}
void TearDownOnMainThread() override {
// Forget about the WebContentses and BrowserContext -- ContentBrowserTest
// will clean them up.
web_contents3_ = nullptr;
browser_context2_ = nullptr;
web_contents2_ = nullptr;
web_contents1_ = nullptr;
browser_context1_ = nullptr;
accessibility_state_ = nullptr;
ContentBrowserTest::TearDownOnMainThread();
}
BrowserAccessibilityState& accessibility_state() {
return *accessibility_state_;
}
// The initial BrowserContext and its two WebContentses.
BrowserContext& browser_context1() { return *browser_context1_; }
WebContents& web_contents1() { return *web_contents1_; }
WebContents& web_contents2() { return *web_contents2_; }
// The second BrowserContext and its WebContents.
BrowserContext& browser_context2() { return *browser_context2_; }
WebContents& web_contents3() { return *web_contents3_; }
#if BUILDFLAG(IS_WIN)
static constexpr ui::AXMode kIgnoredModeFlags{ui::AXMode::kNativeAPIs};
#else
static constexpr ui::AXMode kIgnoredModeFlags{};
#endif
private:
raw_ptr<BrowserAccessibilityState> accessibility_state_ = nullptr;
raw_ptr<BrowserContext> browser_context1_ = nullptr;
raw_ptr<WebContents> web_contents1_ = nullptr;
raw_ptr<WebContents> web_contents2_ = nullptr;
raw_ptr<BrowserContext> browser_context2_ = nullptr;
raw_ptr<WebContents> web_contents3_ = nullptr;
};
// Matches `arg` if it equals `flags`, ignoring those in `ignored_flags`. This
// is required on Windows, where kNativeAPIs may be turned on when a window
// receives focus; see https://crbug.com/1447827.
MATCHER_P2(EqualsIgnoring, flags, ignored_flags, "") {
return (arg & ~ignored_flags) == (flags & ~ignored_flags);
}
// Verifies that all WebContentses in the process receive mode flags targeting
// the process.
IN_PROC_BROWSER_TEST_F(ScopedAccessibilityModeTest, Process) {
::testing::StrictMock<MockAXModeObserver> mock_observer;
base::ScopedObservation<ui::AXPlatform, ui::AXModeObserver> observation(
&mock_observer);
observation.Observe(&ui::AXPlatform::GetInstance());
// Accessibility is off to start with.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Add the basic flags to the process and verify that observers were notified
// and that they were applied to all WebContentses.
EXPECT_CALL(mock_observer, OnAXModeAdded(EqualsIgnoring(ui::kAXModeBasic,
kIgnoredModeFlags)));
auto scoped_mode_1 =
accessibility_state().CreateScopedModeForProcess(ui::kAXModeBasic);
::testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeBasic);
EXPECT_EQ(web_contents2().GetAccessibilityMode(), ui::kAXModeBasic);
EXPECT_EQ(web_contents3().GetAccessibilityMode(), ui::kAXModeBasic);
// Now add the complete flags and verify.
EXPECT_CALL(mock_observer, OnAXModeAdded(EqualsIgnoring(ui::kAXModeComplete,
ui::kAXModeBasic)));
auto scoped_mode_2 =
accessibility_state().CreateScopedModeForProcess(ui::kAXModeComplete);
::testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents2().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents3().GetAccessibilityMode(), ui::kAXModeComplete);
// Release the basic flags and verify that complete still applies.
scoped_mode_1.reset();
::testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents2().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents3().GetAccessibilityMode(), ui::kAXModeComplete);
// Release the complete flags and verify that all flags are cleared.
scoped_mode_2.reset();
::testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
}
// Verifies that only WebContentses belonging to a specific BrowserContext
// receive mode flags targeting that BrowserContext.
IN_PROC_BROWSER_TEST_F(ScopedAccessibilityModeTest, BrowserContext) {
// Accessibility is off to start with.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context1()) &
~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context2()) &
~kIgnoredModeFlags,
ui::AXMode());
// Add the basic flags to all WebContentses for the first BrowserContext and
// verify that they were applied only to its WebContentses.
auto scoped_mode_1 = accessibility_state().CreateScopedModeForBrowserContext(
&browser_context1(), ui::kAXModeBasic);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeBasic);
EXPECT_EQ(web_contents2().GetAccessibilityMode(), ui::kAXModeBasic);
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context1()),
ui::kAXModeBasic);
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context2()) &
~kIgnoredModeFlags,
ui::AXMode());
// Now add the complete flags and verify that they were applied.
auto scoped_mode_2 = accessibility_state().CreateScopedModeForBrowserContext(
&browser_context1(), ui::kAXModeComplete);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents2().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context1()),
ui::kAXModeComplete);
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context2()) &
~kIgnoredModeFlags,
ui::AXMode());
// Release the basic flags and verify that complete still applies.
scoped_mode_1.reset();
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents2().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context1()),
ui::kAXModeComplete);
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context2()) &
~kIgnoredModeFlags,
ui::AXMode());
// Release the complete flags and verify that all flags are cleared.
scoped_mode_2.reset();
EXPECT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context1()) &
~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(accessibility_state().GetAccessibilityModeForBrowserContext(
&browser_context2()) &
~kIgnoredModeFlags,
ui::AXMode());
}
// Verifies that only a targeted WebContentses receives mode flags.
IN_PROC_BROWSER_TEST_F(ScopedAccessibilityModeTest, WebContents) {
// Accessibility is off to start with.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
ASSERT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Add the basic flags to the WebContext and verify that they were applied.
auto scoped_mode_1 = accessibility_state().CreateScopedModeForWebContents(
&web_contents1(), ui::kAXModeBasic);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeBasic);
EXPECT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Now add the complete flags and verify that they were applied.
auto scoped_mode_2 = accessibility_state().CreateScopedModeForWebContents(
&web_contents1(), ui::kAXModeComplete);
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Release the basic flags and verify that complete still applies.
scoped_mode_1.reset();
EXPECT_EQ(web_contents1().GetAccessibilityMode(), ui::kAXModeComplete);
EXPECT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Release the complete flags and verify that all flags are cleared.
scoped_mode_2.reset();
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents2().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
EXPECT_EQ(web_contents3().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
}
// Verifies that filtering results in a WebContents receives various web flags
// only when web accessibility is enabled.
IN_PROC_BROWSER_TEST_F(ScopedAccessibilityModeTest, Filtering) {
// Accessibility is off to start with.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Enable PDF OCR for the WebContents.
auto wc_mode = accessibility_state().CreateScopedModeForWebContents(
&web_contents1(), ui::AXMode::kPDFOcr);
// No change to the WebContents.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
ui::AXMode());
// Enable basic accessibility for the process.
auto process_mode =
accessibility_state().CreateScopedModeForProcess(ui::kAXModeBasic);
// Now the WebContents gets both.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
(ui::kAXModeBasic | ui::AXMode::kPDFOcr) & ~kIgnoredModeFlags);
// Add image labeling for the WebContents.
wc_mode = accessibility_state().CreateScopedModeForWebContents(
&web_contents1(), ui::AXMode::kPDFOcr | ui::AXMode::kLabelImages);
// The WebContents doesn't get kLabelImages until kScreenReader appears.
ASSERT_EQ(web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
(ui::kAXModeBasic | ui::AXMode::kPDFOcr) & ~kIgnoredModeFlags);
process_mode =
accessibility_state().CreateScopedModeForProcess(ui::kAXModeComplete);
ASSERT_EQ(
web_contents1().GetAccessibilityMode() & ~kIgnoredModeFlags,
(ui::kAXModeComplete | ui::AXMode::kPDFOcr | ui::AXMode::kLabelImages) &
~kIgnoredModeFlags);
}
} // namespace content