// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#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/check_deref.h"
#include "base/containers/heap_array.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/platform/ax_platform.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 OnMSAAUsed() override {
// When only basic MSAA functionality is used, just enable kNativeAPIs.
// Enabling kNativeAPIs gives little perf impact, but allows these APIs to
// interact with the BrowserAccessibilityManager allowing ATs to be able at
// least find the document without using any advanced APIs.
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
ui::AXMode::kNativeAPIs);
}
void OnBasicIAccessible2Used() override {
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
ui::AXMode::kNativeAPIs);
}
void OnAdvancedIAccessible2Used() 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);
}
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);
}
}
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);
}
}
void OnBasicUIAutomationUsed() override {
AddAXModeForUIA(ui::AXMode::kNativeAPIs);
}
void OnAdvancedUIAutomationUsed() override {
AddAXModeForUIA(ui::AXMode::kWebContents);
}
void OnUIAutomationIdRequested() override {
// TODO(janewman): Currently, UIA_AutomationIdPropertyId currently uses
// GetAuthorUniqueId. This implementation requires html to be enabled, we
// should avoid needing all of kHTML by either modifying what we return for
// this property or serializing the author supplied ID attribute separately.
// Separating out the id is being tracked by crbug 703277
AddAXModeForUIA(ui::AXMode::kHTML);
}
void OnProbableUIAutomationScreenReaderDetected() override {
// Same as kAXModeComplete but without kHTML as it is not needed for UIA.
AddAXModeForUIA(ui::AXMode::kNativeAPIs | ui::AXMode::kWebContents |
ui::AXMode::kScreenReader);
}
void OnTextPatternRequested() override {
AddAXModeForUIA(ui::AXMode::kInlineTextBoxes);
}
void AddAXModeForUIA(ui::AXMode mode) {
DCHECK(::ui::AXPlatform::GetInstance().IsUiaProviderEnabled());
// 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);
}
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();
protected:
void InitBackgroundTasks() override;
void UpdateHistogramsOnOtherThread() override;
void UpdateUniqueUserHistograms() override;
ui::AXPlatform::ProductStrings GetProductStrings() override;
private:
std::unique_ptr<gfx::SingletonHwndObserver> singleton_hwnd_observer_;
};
BrowserAccessibilityStateImplWin::BrowserAccessibilityStateImplWin() {
ui::GetWinAccessibilityAPIUsageObserverList().AddObserver(
new WindowsAccessibilityEnabler());
}
void BrowserAccessibilityStateImplWin::InitBackgroundTasks() {
BrowserAccessibilityStateImpl::InitBackgroundTasks();
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.
// 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));
// Get the file paths of all DLLs loaded.
HANDLE process = GetCurrentProcess();
HMODULE* modules = nullptr;
DWORD bytes_required;
if (!EnumProcessModules(process, modules, 0, &bytes_required))
return;
auto buffer = base::HeapArray<uint8_t>::WithSize(bytes_required);
modules = reinterpret_cast<HMODULE*>(buffer.data());
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, std::size(filename));
std::string module_name(base::FilePath(filename).BaseName().AsUTF8Unsafe());
if (base::EqualsCaseInsensitiveASCII(module_name, "fsdomsrv.dll")) {
static auto* ax_jaws_crash_key = base::debug::AllocateCrashKeyString(
"ax_jaws", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(ax_jaws_crash_key, "true");
g_jaws = true;
}
if (base::EqualsCaseInsensitiveASCII(module_name,
"vbufbackend_gecko_ia2.dll") ||
base::EqualsCaseInsensitiveASCII(module_name, "nvdahelperremote.dll")) {
static auto* ax_nvda_crash_key = base::debug::AllocateCrashKeyString(
"ax_nvda", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(ax_nvda_crash_key, "true");
g_nvda = true;
}
if (base::EqualsCaseInsensitiveASCII(module_name, "stsaw32.dll")) {
static auto* ax_satogo_crash_key = base::debug::AllocateCrashKeyString(
"ax_satogo", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(ax_satogo_crash_key, "true");
satogo = true;
}
if (base::EqualsCaseInsensitiveASCII(module_name, "dolwinhk.dll")) {
static auto* ax_supernova_crash_key = base::debug::AllocateCrashKeyString(
"ax_supernova", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(ax_supernova_crash_key, "true");
g_supernova = true;
}
if (base::EqualsCaseInsensitiveASCII(module_name, "zslhook.dll") ||
base::EqualsCaseInsensitiveASCII(module_name, "zslhook64.dll")) {
static auto* ax_zoomtext_crash_key = base::debug::AllocateCrashKeyString(
"ax_zoomtext", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(ax_zoomtext_crash_key, "true");
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);
}
ui::AXPlatform::ProductStrings
BrowserAccessibilityStateImplWin::GetProductStrings() {
ContentClient& content_client = CHECK_DEREF(content::GetContentClient());
// GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out
// the part before and after the "/".
std::vector<std::string> product_components = base::SplitString(
CHECK_DEREF(CHECK_DEREF(content::GetContentClient()).browser())
.GetProduct(),
"/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (product_components.size() != 2) {
return {{}, {}, CHECK_DEREF(content_client.browser()).GetUserAgent()};
}
return {product_components[0], product_components[1],
CHECK_DEREF(content_client.browser()).GetUserAgent()};
}
// static
std::unique_ptr<BrowserAccessibilityStateImpl>
BrowserAccessibilityStateImpl::Create() {
return std::make_unique<BrowserAccessibilityStateImplWin>();
}
} // namespace content