blob: e17a5c448041518d2e2deb8c1d911aec6c6261db [file] [log] [blame]
// Copyright 2012 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/browser/accessibility/browser_accessibility_state_impl.h"
#include <stddef.h>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/debug/crash_logging.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/scoped_accessibility_mode.h"
#include "content/public/common/content_switches.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_mode_histogram_logger.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h"
namespace content {
namespace {
BrowserAccessibilityStateImpl* g_instance = nullptr;
// Auto-disable accessibility if this many seconds elapse with user input
// events but no accessibility API usage.
constexpr int kAutoDisableAccessibilityTimeSecs = 30;
// Minimum number of user input events with no accessibility API usage
// before auto-disabling accessibility.
constexpr int kAutoDisableAccessibilityEventCount = 3;
// Updating Active/Inactive time on every accessibility api calls would not be
// good for perf. Instead, delay the update task.
constexpr int kOnAccessibilityUsageUpdateDelaySecs = 5;
// How long to wait after `OnScreenReaderStopped` was called before actually
// disabling accessibility support. The main use case is when a screen reader
// or other client is toggled off and on in rapid succession. We don't want to
// destroy the full accessibility tree only to immediately recreate it because
// doing so is bad for performance.
constexpr int kDisableAccessibilitySupportDelaySecs = 2;
// Used for validating the 'basic' bundle parameter for
// --force-renderer-accessibility.
const char kAXModeBundleBasic[] = "basic";
// Used for validating the 'form-controls' bundle parameter for
// --force-renderer-accessibility.
const char kAXModeBundleFormControls[] = "form-controls";
// Update the accessibility histogram 45 seconds after initialization.
static const int ACCESSIBILITY_HISTOGRAM_DELAY_SECS = 45;
// A holder of a ScopedModeCollection targeting a specific BrowserContext or
// WebContents. The collection is bound to the lifetime of the target.
class ModeCollectionForTarget : public base::SupportsUserData::Data {
public:
ModeCollectionForTarget(
base::SupportsUserData* target,
ScopedModeCollection::OnModeChangedCallback on_mode_changed)
: scoped_mode_collection_(base::BindRepeating(
&ModeCollectionForTarget::OnModeChangedForTarget,
base::Unretained(this), // Safe because `this` owns the collection.
base::Unretained(target), // `target` outlives `this`.
std::move(on_mode_changed))) {}
ModeCollectionForTarget(const ModeCollectionForTarget&) = delete;
ModeCollectionForTarget& operator=(const ModeCollectionForTarget&) = delete;
static ui::AXMode GetAccessibilityMode(base::SupportsUserData* target) {
auto* instance = FromTarget(target);
return instance ? instance->scoped_mode_collection_.accessibility_mode()
: ui::AXMode();
}
// Adds a new scoper targeting `target` (a BrowserContext or a WebContents)
// that applies the accessibility mode flags in `mode`. `on_changed_function`
// is a pointer to a member function of `BrowserAccessibilityStateImpl` that
// is called when the effective mode for `target` changes; see
// `ScopedModeCollection::OnModeChangedCallback`. It is bound into a callback
// (along with `impl`) when this is the first addition for `target`;
// otherwise, it (and `impl`) are ignored.
template <class Target>
static std::unique_ptr<ScopedAccessibilityMode> Add(
Target* target,
void (BrowserAccessibilityStateImpl::*on_changed_function)(Target*,
ui::AXMode,
ui::AXMode),
BrowserAccessibilityStateImpl* impl,
ui::AXMode mode) {
auto* instance = FromTarget(target);
if (!instance) {
auto holder = std::make_unique<ModeCollectionForTarget>(
target,
base::BindRepeating(on_changed_function, base::Unretained(impl),
base::Unretained(target)));
instance = holder.get();
target->SetUserData(&kUserDataKey, std::move(holder));
}
return instance->scoped_mode_collection_.Add(mode);
}
private:
static ModeCollectionForTarget* FromTarget(base::SupportsUserData* target) {
return static_cast<ModeCollectionForTarget*>(
target->GetUserData(&ModeCollectionForTarget::kUserDataKey));
}
void OnModeChangedForTarget(
base::SupportsUserData* target,
base::RepeatingCallback<void(ui::AXMode, ui::AXMode)> impl_callback,
ui::AXMode old_mode,
ui::AXMode new_mode) {
// If the collection is no longer bound to the target, the target is in the
// process of being destroyed. Ignore changes when this is the case.
if (auto* const collection = FromTarget(target); collection) {
std::move(impl_callback).Run(old_mode, new_mode);
}
}
static const int kUserDataKey = 0;
ScopedModeCollection scoped_mode_collection_;
};
// static
const int ModeCollectionForTarget::kUserDataKey;
// Returns a subset of `mode` for delivery to a WebContents.
ui::AXMode FilterAccessibilityModeInvariants(ui::AXMode mode) {
// Strip kLabelImages if kScreenReader is absent.
// TODO(grt): kLabelImages is a feature of //chrome. Find a way to
// achieve this filtering without teaching //content about it. Perhaps via
// the delegate interface to be added in support of https://crbug.com/1470199.
if (ui::AXMode(mode.flags() ^ ui::AXMode::kScreenReader)
.has_mode(ui::AXMode::kLabelImages | ui::AXMode::kScreenReader)) {
mode.set_mode(ui::AXMode::kLabelImages, false);
}
// Modes above kNativeAPIs and kWebContents require kWebContents. Some
// components may enable higher bits, but those should only be given to a
// WebContents if that WebContents also has the kWebContents mode enabled;
// see `content::RenderFrameHostImpl::UpdateAccessibilityMode()` and
// `content::RenderAccessibilityManager::SetMode()`.
if (!mode.has_mode(ui::AXMode::kWebContents)) {
return mode & ui::AXMode::kNativeAPIs;
}
// Form controls mode is restrictive. There are other modes that should not be
// used in combination with it. This could occur if something that needs
// screen reader mode is turned on after forms control mode. In that case,
// forms mode must be removed.
if (mode.has_mode(ui::AXMode::kInlineTextBoxes) ||
mode.has_mode(ui::AXMode::kScreenReader)) {
return ui::AXMode(mode.flags(), mode.experimental_flags() &
~ui::AXMode::kExperimentalFormControls);
}
return mode;
}
} // namespace
// static
BrowserAccessibilityState* BrowserAccessibilityState::GetInstance() {
return BrowserAccessibilityStateImpl::GetInstance();
}
// static
BrowserAccessibilityStateImpl* BrowserAccessibilityStateImpl::GetInstance() {
CHECK(g_instance);
return g_instance;
}
// On Android, Mac, and Windows there are platform-specific subclasses.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_WIN) && !BUILDFLAG(IS_MAC)
// static
std::unique_ptr<BrowserAccessibilityStateImpl>
BrowserAccessibilityStateImpl::Create() {
return base::WrapUnique(new BrowserAccessibilityStateImpl());
}
#endif
BrowserAccessibilityStateImpl::BrowserAccessibilityStateImpl()
: BrowserAccessibilityState(),
ax_platform_(*this),
histogram_delay_(base::Seconds(ACCESSIBILITY_HISTOGRAM_DELAY_SECS)),
scoped_modes_for_process_(base::BindRepeating(
&BrowserAccessibilityStateImpl::OnModeChangedForProcess,
base::Unretained(this))),
process_accessibility_mode_(scoped_modes_for_process_.Add(ui::AXMode())) {
DCHECK_EQ(g_instance, nullptr);
g_instance = this;
bool disallow_changes = false;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableRendererAccessibility)) {
disallow_changes = true;
} else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceRendererAccessibility)) {
#if BUILDFLAG(IS_WIN)
std::string ax_mode_bundle = base::WideToUTF8(
base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
switches::kForceRendererAccessibility));
#else
std::string ax_mode_bundle =
base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
switches::kForceRendererAccessibility);
#endif
if (ax_mode_bundle.empty()) {
// For backwards compatibility, when --force-renderer-accessibility has no
// parameter, use the complete bundle but allow changes.
AddAccessibilityModeFlags(ui::kAXModeComplete);
} else {
// Support --force-renderer-accessibility=[basic|form-controls|complete]
disallow_changes = true;
if (ax_mode_bundle.compare(kAXModeBundleBasic) == 0) {
AddAccessibilityModeFlags(ui::kAXModeBasic);
} else if (ax_mode_bundle.compare(kAXModeBundleFormControls) == 0) {
#if BUILDFLAG(IS_ANDROID)
AddAccessibilityModeFlags(ui::kAXModeFormControls);
#endif
} else {
// If AXMode is 'complete' or invalid, default to complete bundle.
AddAccessibilityModeFlags(ui::kAXModeComplete);
}
}
}
SetAXModeChangeAllowed(!disallow_changes);
}
void BrowserAccessibilityStateImpl::InitBackgroundTasks() {
// Schedule calls to update histograms after a delay.
//
// The delay is necessary because assistive technology sometimes isn't
// detected until after the user interacts in some way, so a reasonable delay
// gives us better numbers.
// Some things can be done on another thread safely.
base::ThreadPool::PostDelayedTask(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(
&BrowserAccessibilityStateImpl::UpdateHistogramsOnOtherThread,
base::Unretained(this)),
histogram_delay_);
// Other things must be done on the UI thread (e.g. to access PrefService).
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&BrowserAccessibilityStateImpl::UpdateHistogramsOnUIThread,
base::Unretained(this)),
histogram_delay_);
}
BrowserAccessibilityStateImpl::~BrowserAccessibilityStateImpl() {
DCHECK_EQ(g_instance, this);
g_instance = nullptr;
}
void BrowserAccessibilityStateImpl::OnScreenReaderDetected() {
// Clear any previous, now obsolete, request to disable support.
disable_accessibility_request_time_ = base::TimeTicks();
if (!allow_ax_mode_changes_) {
return;
}
EnableAccessibility();
}
void BrowserAccessibilityStateImpl::OnScreenReaderStopped() {
disable_accessibility_request_time_ = ui::EventTimeForNow();
// If a screen reader or other client using accessibility API is toggled off
// and on in short succession, we risk destroying and recreating large
// accessibility trees unnecessarily which is bad for performance. So we post
// a delayed task here, and only reset accessibility mode if nothing has
// requested accessibility support be re-enabled after that delay has passed.
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&BrowserAccessibilityStateImpl::MaybeResetAccessibilityMode,
weak_factory_.GetWeakPtr()),
base::Seconds(kDisableAccessibilitySupportDelaySecs));
}
void BrowserAccessibilityStateImpl::EnableAccessibility() {
if (!allow_ax_mode_changes_) {
return;
}
// Enabling accessibility is generally the result of an accessibility API
// call, so we should also reset the auto-disable accessibility code. The only
// exception is in tests or when a user manually toggles accessibility flags
// in chrome://accessibility.
OnAccessibilityApiUsage();
const ui::AXMode previous_mode = process_accessibility_mode_->mode();
// First disable any non-additive modes that restrict or filter the
// information available in the tree.
const ui::AXMode new_mode =
(previous_mode & ~ui::kAXModeFormControls) | ui::kAXModeComplete;
process_accessibility_mode_ = CreateScopedModeForProcess(new_mode);
}
void BrowserAccessibilityStateImpl::DisableAccessibility() {
ResetAccessibilityMode();
}
bool BrowserAccessibilityStateImpl::IsRendererAccessibilityEnabled() {
return !base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableRendererAccessibility);
}
void BrowserAccessibilityStateImpl::MaybeResetAccessibilityMode() {
// `OnScreenReaderStopped` sets `disable_accessibility_request_time_`, and
// `OnScreenReaderDetected` clears it. If we no longer have a request time
// to disable accessibility, this delayed task is obsolete.
if (disable_accessibility_request_time_.is_null()) {
return;
}
// `OnScreenReaderStopped` could be called multiple times prior to the delay
// expiring. The value of `disable_accessibility_request_time_` is updated
// for every call. If we're running this task prior to the delay expiring,
// this request time to disable accessibility is obsolete.
if ((base::TimeTicks::Now() - disable_accessibility_request_time_) <
base::Seconds(kDisableAccessibilitySupportDelaySecs)) {
return;
}
ResetAccessibilityMode();
}
void BrowserAccessibilityStateImpl::ResetAccessibilityMode() {
if (!allow_ax_mode_changes_) {
return;
}
process_accessibility_mode_ = CreateScopedModeForProcess(ui::AXMode());
}
bool BrowserAccessibilityStateImpl::IsAccessibleBrowser() {
return GetAccessibilityMode() == ui::kAXModeComplete;
}
void BrowserAccessibilityStateImpl::AddUIThreadHistogramCallback(
base::OnceClosure callback) {
ui_thread_histogram_callbacks_.push_back(std::move(callback));
}
void BrowserAccessibilityStateImpl::AddOtherThreadHistogramCallback(
base::OnceClosure callback) {
other_thread_histogram_callbacks_.push_back(std::move(callback));
}
void BrowserAccessibilityStateImpl::UpdateHistogramsForTesting() {
UpdateHistogramsOnUIThread();
UpdateHistogramsOnOtherThread();
}
void BrowserAccessibilityStateImpl::SetPerformanceFilteringAllowed(
bool allowed) {
performance_filtering_allowed_ = allowed;
}
bool BrowserAccessibilityStateImpl::IsPerformanceFilteringAllowed() {
return performance_filtering_allowed_;
}
void BrowserAccessibilityStateImpl::UpdateHistogramsOnUIThread() {
for (auto& callback : ui_thread_histogram_callbacks_) {
std::move(callback).Run();
}
ui_thread_histogram_callbacks_.clear();
UMA_HISTOGRAM_BOOLEAN(
"Accessibility.ManuallyEnabled",
!GetAccessibilityMode().is_mode_off() && !allow_ax_mode_changes_);
#if BUILDFLAG(IS_WIN)
base::UmaHistogramEnumeration(
"Accessibility.WinHighContrastTheme",
ui::NativeTheme::GetInstanceForNativeUi()
->GetPlatformHighContrastColorScheme(),
ui::NativeTheme::PlatformHighContrastColorScheme::kMaxValue);
#endif
ui_thread_done_ = true;
if (other_thread_done_ && background_thread_done_callback_) {
std::move(background_thread_done_callback_).Run();
}
}
void BrowserAccessibilityStateImpl::UpdateHistogramsOnOtherThread() {
for (auto& callback : other_thread_histogram_callbacks_) {
std::move(callback).Run();
}
other_thread_histogram_callbacks_.clear();
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowserAccessibilityStateImpl::OnOtherThreadDone,
base::Unretained(this)));
}
void BrowserAccessibilityStateImpl::OnOtherThreadDone() {
other_thread_done_ = true;
if (ui_thread_done_ && background_thread_done_callback_) {
std::move(background_thread_done_callback_).Run();
}
}
void BrowserAccessibilityStateImpl::UpdateAccessibilityActivityTask() {
if (!g_instance) {
// There can be a race on shutdown since this is posted as a delayed task.
return;
}
base::TimeTicks now = ui::EventTimeForNow();
accessibility_last_usage_time_ = now;
if (accessibility_active_start_time_.is_null()) {
accessibility_active_start_time_ = now;
}
// If accessibility was enabled but inactive until now, log the amount
// of time between now and the last API usage.
if (!accessibility_inactive_start_time_.is_null()) {
base::UmaHistogramLongTimes("Accessibility.InactiveTime",
now - accessibility_inactive_start_time_);
accessibility_inactive_start_time_ = base::TimeTicks();
}
accessibility_update_task_pending_ = false;
}
ui::AXMode BrowserAccessibilityStateImpl::GetAccessibilityMode() {
return scoped_modes_for_process_.accessibility_mode();
}
ui::AXMode BrowserAccessibilityStateImpl::GetAccessibilityModeForBrowserContext(
BrowserContext* browser_context) {
return FilterAccessibilityModeInvariants(
GetAccessibilityMode() |
ModeCollectionForTarget::GetAccessibilityMode(browser_context));
}
void BrowserAccessibilityStateImpl::OnUserInputEvent() {
// No need to do anything if accessibility is off, or if it was forced on.
if (GetAccessibilityMode().is_mode_off() || !allow_ax_mode_changes_) {
return;
}
// If we get at least kAutoDisableAccessibilityEventCount user input
// events, more than kAutoDisableAccessibilityTimeSecs apart, with
// no accessibility API usage in-between disable accessibility.
// (See also OnAccessibilityApiUsage()).
base::TimeTicks now = ui::EventTimeForNow();
user_input_event_count_++;
if (user_input_event_count_ == 1) {
first_user_input_event_time_ = now;
return;
}
if (user_input_event_count_ < kAutoDisableAccessibilityEventCount) {
return;
}
if (now - first_user_input_event_time_ >
base::Seconds(kAutoDisableAccessibilityTimeSecs)) {
if (!accessibility_active_start_time_.is_null()) {
base::UmaHistogramLongTimes(
"Accessibility.ActiveTime",
accessibility_last_usage_time_ - accessibility_active_start_time_);
// This will help track the time accessibility spends enabled, but
// inactive.
if (!features::IsAutoDisableAccessibilityEnabled()) {
accessibility_inactive_start_time_ = accessibility_last_usage_time_;
}
accessibility_active_start_time_ = base::TimeTicks();
}
// Check if the feature to auto-disable accessibility is even enabled.
if (features::IsAutoDisableAccessibilityEnabled()) {
base::UmaHistogramCounts1000("Accessibility.AutoDisabled.EventCount",
user_input_event_count_);
DCHECK(!accessibility_enabled_time_.is_null());
base::UmaHistogramLongTimes("Accessibility.AutoDisabled.EnabledTime",
now - accessibility_enabled_time_);
accessibility_disabled_time_ = now;
DisableAccessibility();
}
}
}
void BrowserAccessibilityStateImpl::UpdateUniqueUserHistograms() {}
void BrowserAccessibilityStateImpl::SetAXModeChangeAllowed(bool allowed) {
allow_ax_mode_changes_ = allowed;
ui::AXPlatformNode::SetAXModeChangeAllowed(allowed);
}
bool BrowserAccessibilityStateImpl::IsAXModeChangeAllowed() const {
return allow_ax_mode_changes_;
}
void BrowserAccessibilityStateImpl::NotifyWebContentsPreferencesChanged()
const {
for (WebContentsImpl* wc : WebContentsImpl::GetAllWebContents()) {
wc->OnWebPreferencesChanged();
}
}
void BrowserAccessibilityStateImpl::AddAccessibilityModeFlags(ui::AXMode mode) {
if (!allow_ax_mode_changes_) {
return;
}
// Adding an accessibility mode flag is generally the result of an
// accessibility API call, so we should also reset the auto-disable
// accessibility code. The only exception is in tests or when a user manually
// toggles accessibility flags in chrome://accessibility.
OnAccessibilityApiUsage();
// Update process_accessibility_mode_ via SetProcessMode so that the remainder
// of processing is identical to when AXPlatformNode::NotifyAddAXModeFlags()
// is called -- it will defer to AXPlatform::SetMode() to update the global
// set of flags. AXPlatform, itself, defers to its Delegate, which is this
// instance. This ensures that calls to AddAccessibilityModeFlags() and direct
// calls to AXPlatformNode::NotifyAddAXModeFlags() down in //ui each follow
// the same codepath to set the global mode flags, notify observers, dispatch
// to WebContents, and record metrics.
SetProcessMode(process_accessibility_mode_->mode() | mode);
}
void BrowserAccessibilityStateImpl::RemoveAccessibilityModeFlags(
ui::AXMode mode) {
// Turning off accessibility or changing the mode will not be allowed if the
// --force-renderer-accessibility or --disable-renderer-accessibility command
// line flags are present, or during testing
if (!allow_ax_mode_changes_) {
return;
}
process_accessibility_mode_ =
CreateScopedModeForProcess(process_accessibility_mode_->mode() & ~mode);
}
base::CallbackListSubscription
BrowserAccessibilityStateImpl::RegisterFocusChangedCallback(
FocusChangedCallback callback) {
return focus_changed_callbacks_.Add(std::move(callback));
}
// Returns the effective mode for the process, taking all process-wide scopers
// into account.
ui::AXMode BrowserAccessibilityStateImpl::GetProcessMode() {
return GetAccessibilityMode();
}
// Replaces the scoper that backs the legacy process-wide mode with one applying
// `new_mode`.
void BrowserAccessibilityStateImpl::SetProcessMode(ui::AXMode new_mode) {
const ui::AXMode previous_mode = GetAccessibilityMode();
if (new_mode == previous_mode) {
return;
}
process_accessibility_mode_ = CreateScopedModeForProcess(new_mode);
}
void BrowserAccessibilityStateImpl::OnAccessibilityApiUsage() {
// See OnUserInputEvent for how this is used to disable accessibility.
user_input_event_count_ = 0;
// See comment above kOnAccessibilityUsageUpdateDelaySecs for why we post a
// delayed task.
if (!accessibility_update_task_pending_) {
accessibility_update_task_pending_ = true;
GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&BrowserAccessibilityStateImpl::UpdateAccessibilityActivityTask,
base::Unretained(this)),
base::Seconds(kOnAccessibilityUsageUpdateDelaySecs));
}
}
void BrowserAccessibilityStateImpl::OnInputEvent(
const RenderWidgetHost& widget,
const blink::WebInputEvent& event) {
// |this| observer cares about user input events (specifically keyboard,
// mouse & touch events) to decide if the accessibility APIs can be disabled.
if (event.GetType() == blink::WebInputEvent::Type::kMouseDown ||
event.GetType() == blink::WebInputEvent::Type::kGestureTapDown ||
event.GetType() == blink::WebInputEvent::Type::kTouchStart ||
event.GetType() == blink::WebInputEvent::Type::kRawKeyDown ||
event.GetType() == blink::WebInputEvent::Type::kKeyDown) {
OnUserInputEvent();
}
}
std::unique_ptr<ScopedAccessibilityMode>
BrowserAccessibilityStateImpl::CreateScopedModeForProcess(ui::AXMode mode) {
return scoped_modes_for_process_.Add(mode);
}
void BrowserAccessibilityStateImpl::OnModeChangedForProcess(
ui::AXMode old_mode,
ui::AXMode new_mode) {
ui::RecordAccessibilityModeHistograms(ui::AXHistogramPrefix::kNone, new_mode,
old_mode);
// Add a crash key with the ax_mode, to enable searching for top crashes that
// occur when accessibility is turned on. This adds it for the browser
// process, and elsewhere the same key is added to renderer processes.
static auto* const ax_mode_crash_key = base::debug::AllocateCrashKeyString(
"ax_mode", base::debug::CrashKeySize::Size64);
if (ax_mode_crash_key) {
base::debug::SetCrashKeyString(ax_mode_crash_key, new_mode.ToString());
}
// Combine the new mode for the process with the effective mode for each
// WebContents and its associated BrowserContext.
base::ranges::for_each(
WebContentsImpl::GetAllWebContents(),
[new_mode](WebContentsImpl* web_contents) {
if (!web_contents->IsBeingDestroyed()) {
web_contents->SetAccessibilityMode(FilterAccessibilityModeInvariants(
new_mode |
ModeCollectionForTarget::GetAccessibilityMode(
web_contents->GetBrowserContext()) |
ModeCollectionForTarget::GetAccessibilityMode(web_contents)));
}
});
// Handle additions to the process's mode flags.
if (const auto additions = new_mode & ~old_mode; !additions.is_mode_off()) {
// Keep track of the total time accessibility is enabled, and the time
// it was previously disabled.
if (old_mode.is_mode_off()) {
base::TimeTicks now = ui::EventTimeForNow();
accessibility_enabled_time_ = now;
if (!accessibility_disabled_time_.is_null()) {
base::UmaHistogramLongTimes("Accessibility.AutoDisabled.DisabledTime",
now - accessibility_disabled_time_);
}
}
// Broadcast the new mode flags, if any, to the AXModeObservers.
ax_platform_.NotifyModeAdded(additions);
}
}
std::unique_ptr<ScopedAccessibilityMode>
BrowserAccessibilityStateImpl::CreateScopedModeForBrowserContext(
BrowserContext* browser_context,
ui::AXMode mode) {
return ModeCollectionForTarget::Add(
browser_context,
&BrowserAccessibilityStateImpl::OnModeChangedForBrowserContext, this,
mode);
}
void BrowserAccessibilityStateImpl::OnModeChangedForBrowserContext(
BrowserContext* browser_context,
ui::AXMode old_mode,
ui::AXMode new_mode) {
// Combine the effective modes for the process and for `browser_context`.
ui::AXMode mode_for_context = GetAccessibilityMode() | new_mode;
// Combine this with the effective mode for each WebContents associated with
// `browser_context`.
base::ranges::for_each(
WebContentsImpl::GetAllWebContents(),
[browser_context, mode_for_context](WebContentsImpl* web_contents) {
if (!web_contents->IsBeingDestroyed() &&
web_contents->GetBrowserContext() == browser_context) {
web_contents->SetAccessibilityMode(FilterAccessibilityModeInvariants(
mode_for_context |
ModeCollectionForTarget::GetAccessibilityMode(web_contents)));
}
});
}
std::unique_ptr<ScopedAccessibilityMode>
BrowserAccessibilityStateImpl::CreateScopedModeForWebContents(
WebContents* web_contents,
ui::AXMode mode) {
return ModeCollectionForTarget::Add(
web_contents, &BrowserAccessibilityStateImpl::OnModeChangedForWebContents,
this, mode);
}
void BrowserAccessibilityStateImpl::OnModeChangedForWebContents(
WebContents* web_contents,
ui::AXMode old_mode,
ui::AXMode new_mode) {
if (web_contents->IsBeingDestroyed()) {
return;
}
// Combine the effective modes for the process, `web_contents`'s
// BrowserContext, and for `web_contents.
static_cast<WebContentsImpl*>(web_contents)
->SetAccessibilityMode(FilterAccessibilityModeInvariants(
GetAccessibilityMode() |
ModeCollectionForTarget::GetAccessibilityMode(
web_contents->GetBrowserContext()) |
new_mode));
}
void BrowserAccessibilityStateImpl::CallInitBackgroundTasksForTesting(
base::RepeatingClosure done_callback) {
// Set the delay to 1 second, that ensures that we actually test having
// a nonzero delay but the test still runs quickly.
histogram_delay_ = base::Seconds(1);
background_thread_done_callback_ = done_callback;
InitBackgroundTasks();
}
void BrowserAccessibilityStateImpl::OnFocusChangedInPage(
const FocusedNodeDetails& details) {
focus_changed_callbacks_.Notify(details);
}
} // namespace content