blob: a15418f42f5acc029755cc840f8be24287d7919e [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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 <windows.h> // Must be in front of other Windows header files.
#include <psapi.h>
#include <stddef.h>
#include <memory>
#include "base/cxx17_backports.h"
#include "base/files/file_path.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/gfx/animation/animation.h"
#include "ui/gfx/win/singleton_hwnd_observer.h"
namespace content {
namespace {
static bool g_jaws = false;
static bool g_nvda = false;
static bool g_supernova = false;
static bool g_zoomtext = false;
// Enables accessibility based on clues that indicate accessibility API usage.
class WindowsAccessibilityEnabler
: public ui::WinAccessibilityAPIUsageObserver {
public:
WindowsAccessibilityEnabler() {}
private:
// WinAccessibilityAPIUsageObserver
void OnIAccessible2Used() override {
// When IAccessible2 APIs have been used elsewhere in the codebase,
// enable basic web accessibility support. (Full screen reader support is
// detected later when specific more advanced APIs are accessed.)
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
ui::kAXModeBasic);
BrowserAccessibilityStateImpl::GetInstance()->OnAccessibilityApiUsage();
}
void OnScreenReaderHoneyPotQueried() override {
// We used to trust this as a signal that a screen reader is running,
// but it's been abused. Now only enable accessibility if we also
// detect a call to get_accName.
if (screen_reader_honeypot_queried_)
return;
screen_reader_honeypot_queried_ = true;
if (acc_name_called_) {
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
ui::kAXModeBasic);
}
BrowserAccessibilityStateImpl::GetInstance()->OnAccessibilityApiUsage();
}
void OnAccNameCalled() override {
// See OnScreenReaderHoneyPotQueried, above.
if (acc_name_called_)
return;
acc_name_called_ = true;
if (screen_reader_honeypot_queried_) {
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
ui::kAXModeBasic);
}
BrowserAccessibilityStateImpl::GetInstance()->OnAccessibilityApiUsage();
}
void OnBasicUIAutomationUsed() override {
AddAXModeForUIA(ui::AXMode::kNativeAPIs);
}
void OnAdvancedUIAutomationUsed() override {
AddAXModeForUIA(ui::AXMode::kWebContents);
}
void OnProbableUIAutomationScreenReaderDetected() override {
// Same as kAXModeComplete but without kHTML as it is not needed for UIA.
AddAXModeForUIA(ui::kAXModeCompleteNoHTML);
}
void OnTextPatternRequested() override {
AddAXModeForUIA(ui::AXMode::kInlineTextBoxes);
}
void AddAXModeForUIA(ui::AXMode mode) {
DCHECK(::switches::IsExperimentalAccessibilityPlatformUIAEnabled());
// Firing a UIA event can cause UIA to call back into our APIs, don't
// consider this to be usage.
if (firing_uia_events_)
return;
// UI Automation insulates providers from knowing about the client(s) asking
// for information. When IsSelectiveUIAEnablement is Enabled, we turn on
// various parts of accessibility depending on what APIs have been called.
if (!features::IsSelectiveUIAEnablementEnabled())
mode = ui::kAXModeComplete;
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
mode);
BrowserAccessibilityStateImpl::GetInstance()->OnAccessibilityApiUsage();
}
void StartFiringUIAEvents() override { firing_uia_events_ = true; }
void EndFiringUIAEvents() override { firing_uia_events_ = false; }
// This should be set to true while we are firing uia events. Firing UIA
// events causes UIA to call back into our APIs, this should not be considered
// usage.
bool firing_uia_events_ = false;
bool screen_reader_honeypot_queried_ = false;
bool acc_name_called_ = false;
};
void OnWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (message == WM_SETTINGCHANGE && wparam == SPI_SETCLIENTAREAANIMATION) {
gfx::Animation::UpdatePrefersReducedMotion();
for (WebContentsImpl* wc : WebContentsImpl::GetAllWebContents()) {
wc->OnWebPreferencesChanged();
}
}
}
} // namespace
class BrowserAccessibilityStateImplWin : public BrowserAccessibilityStateImpl {
public:
BrowserAccessibilityStateImplWin();
~BrowserAccessibilityStateImplWin() override {}
protected:
void UpdateHistogramsOnOtherThread() override;
void UpdateUniqueUserHistograms() override;
private:
std::unique_ptr<gfx::SingletonHwndObserver> singleton_hwnd_observer_;
};
BrowserAccessibilityStateImplWin::BrowserAccessibilityStateImplWin() {
ui::GetWinAccessibilityAPIUsageObserverList().AddObserver(
new WindowsAccessibilityEnabler());
singleton_hwnd_observer_ = std::make_unique<gfx::SingletonHwndObserver>(
base::BindRepeating(&OnWndProc));
}
void BrowserAccessibilityStateImplWin::UpdateHistogramsOnOtherThread() {
BrowserAccessibilityStateImpl::UpdateHistogramsOnOtherThread();
// NOTE: this method is run from another thread to reduce jank, since
// there's no guarantee these system calls will return quickly. Code that
// needs to run in the UI thread can be run in
// UpdateHistogramsOnUIThread instead.
AUDIODESCRIPTION audio_description = {0};
audio_description.cbSize = sizeof(AUDIODESCRIPTION);
SystemParametersInfo(SPI_GETAUDIODESCRIPTION, 0, &audio_description, 0);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinAudioDescription",
!!audio_description.Enabled);
// This screen reader flag is nearly meaningless, it is set very often
// when there is no screen reader, and is not set for Narrator.
BOOL win_screen_reader = FALSE;
SystemParametersInfo(SPI_GETSCREENREADER, 0, &win_screen_reader, 0);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinScreenReader", !!win_screen_reader);
// Better all-encompassing screen reader metric.
// See also specific screen reader metrics below, e.g. WinJAWS, WinNVDA.
ui::AXMode mode =
BrowserAccessibilityStateImpl::GetInstance()->GetAccessibilityMode();
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinScreenReader2",
mode.has_mode(ui::AXMode::kScreenReader));
STICKYKEYS sticky_keys = {0};
sticky_keys.cbSize = sizeof(STICKYKEYS);
SystemParametersInfo(SPI_GETSTICKYKEYS, 0, &sticky_keys, 0);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinStickyKeys",
0 != (sticky_keys.dwFlags & SKF_STICKYKEYSON));
// We only measure systems where SPI_GETCLIENTAREAANIMATION exists.
BOOL win_anim_enabled = TRUE;
if (SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &win_anim_enabled,
0)) {
UMA_HISTOGRAM_BOOLEAN("Accessibility.Win.AnimationsEnabled",
win_anim_enabled);
}
// Get the file paths of all DLLs loaded.
HANDLE process = GetCurrentProcess();
HMODULE* modules = NULL;
DWORD bytes_required;
if (!EnumProcessModules(process, modules, 0, &bytes_required))
return;
std::unique_ptr<char[]> buffer(new char[bytes_required]);
modules = reinterpret_cast<HMODULE*>(buffer.get());
DWORD ignore;
if (!EnumProcessModules(process, modules, bytes_required, &ignore))
return;
// Look for DLLs of assistive technology known to work with Chrome.
size_t module_count = bytes_required / sizeof(HMODULE);
bool satogo = false; // Very few users -- do not need uniques
for (size_t i = 0; i < module_count; i++) {
TCHAR filename[MAX_PATH];
GetModuleFileName(modules[i], filename, base::size(filename));
std::string module_name(base::FilePath(filename).BaseName().AsUTF8Unsafe());
if (base::LowerCaseEqualsASCII(module_name, "fsdomsrv.dll"))
g_jaws = true;
if (base::LowerCaseEqualsASCII(module_name, "vbufbackend_gecko_ia2.dll") ||
base::LowerCaseEqualsASCII(module_name, "nvdahelperremote.dll"))
g_nvda = true;
if (base::LowerCaseEqualsASCII(module_name, "stsaw32.dll"))
satogo = true;
if (base::LowerCaseEqualsASCII(module_name, "dolwinhk.dll"))
g_supernova = true;
if (base::LowerCaseEqualsASCII(module_name, "zslhook.dll") ||
base::LowerCaseEqualsASCII(module_name, "zslhook64.dll"))
g_zoomtext = true;
}
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinJAWS", g_jaws);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinNVDA", g_nvda);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinSAToGo", satogo);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinSupernova", g_supernova);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinZoomText", g_zoomtext);
}
void BrowserAccessibilityStateImplWin::UpdateUniqueUserHistograms() {
BrowserAccessibilityStateImpl::UpdateUniqueUserHistograms();
ui::AXMode mode = GetAccessibilityMode();
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinScreenReader2.EveryReport",
mode.has_mode(ui::AXMode::kScreenReader));
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinJAWS.EveryReport", g_jaws);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinNVDA.EveryReport", g_nvda);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinSupernova.EveryReport", g_supernova);
UMA_HISTOGRAM_BOOLEAN("Accessibility.WinZoomText.EveryReport", g_zoomtext);
}
//
// BrowserAccessibilityStateImpl::GetInstance implementation that constructs
// this class instead of the base class.
//
// static
BrowserAccessibilityStateImpl* BrowserAccessibilityStateImpl::GetInstance() {
static base::NoDestructor<BrowserAccessibilityStateImplWin> instance;
return &*instance;
}
} // namespace content