electron/shell/browser/ui/webui/accessibility_ui.cc
electron-roller[bot] 2dd4b77ae0
chore: bump chromium to 136.0.7095.0 (36-x-y) (#46184)
* chore: bump chromium in DEPS to 136.0.7081.1

* chore: bump chromium in DEPS to 136.0.7083.1

* chore: bump chromium in DEPS to 136.0.7085.1

* chore: bump chromium in DEPS to 136.0.7087.1

* chore: bump chromium in DEPS to 136.0.7089.0

* chore: bump chromium in DEPS to 136.0.7091.0

* chore: bump chromium in DEPS to 136.0.7092.0

* chore: bump chromium in DEPS to 136.0.7093.1

* chore: bump chromium in DEPS to 136.0.7095.1

* chore: bump chromium in DEPS to 136.0.7097.1

* chore: bump chromium in DEPS to 136.0.7099.1

* chore: bump chromium in DEPS to 136.0.7101.0

* chore: bump chromium in DEPS to 136.0.7103.0

* chore: bump chromium in DEPS to 136.0.7103.15

* chore: bump chromium in DEPS to 136.0.7103.17

* chore: bump chromium to 136.0.7095.0 (main) (#46118)

* chore: bump chromium in DEPS to 136.0.7076.0

* chore: bump chromium in DEPS to 136.0.7077.0

* 6368856: Migrate absl variant.h and utility.h in content (part 2/2) | 6368856

* 6356528: Clean up LegacyRenderWidgetHostHWND code | 6356528

* chore: export patches

* 6339113: [Viewport Segments] Add CDP commands to override Viewport Segments without overriding other device properties. | 6339113

* 6352169: [DevTools][MultiInstance] Support new tab in another window on Android | 6352169

* 6368856: Migrate absl variant.h and utility.h in content (part 2/2) | 6368856

* 6360858:Clickiness: Wire response from URLLoader to DB, add e2e tests| 6360858

* chore: bump chromium in DEPS to 136.0.7079.0

* chore: export patches

* chore: bump chromium in DEPS to 136.0.7081.0

* chore: export patches

* chore: bump chromium in DEPS to 136.0.7083.0

* 6361987: Remove double-declaration with gfx::NativeView and gfx::NativeWindow | 6361987

* chore: export patches

* chore: bump chromium in DEPS to 136.0.7087.0

* chore: export patches

* fix: include node patch for missing AtomicsWaitEvent
6385540

* build: add depot_tools python to path

* fix: cppgc init and unregistering v8 isolate

6333562

CppGc is now initialized earlier so Node can skip reinitializing it.

Additionally, gin::IsolateHandle was attempting to destruct an already destructed
v8::Isolate upon electron::JavaScriptEnvironment destruction. By removing the call
to NodePlatform::UnregisterIsolate, this fixes the crash on app shutdown.

* fix: unregister isolate after destruction

See code comment.

* chore: bump chromium in DEPS to 136.0.7095.0

* chore: sync patches

* fix: add script_parsing::ContentScriptType parameter
6298395

* fix: migrate content::BrowserAccessibilityState methods
6401437
6383275

* feat: enableHappyEyeballs option for host resolver
6332599

* fix: add new cookie exclusion reason
6343479

* fix: add new url loader method
6337340

* fix: add new cppgc header file for electron_node headers
6348644

* fix: disable CREL on Linux ARM64
https://chromium-review.googlesource.com/q/I3a62f02f564f07be63173b0773b4ecaffbe939b9

* fixup! fix: add new cppgc header file for electron_node headers 6348644

* chore: update corner smoothing patch

* fixup! chore: update corner smoothing patch

* chore: disable NAN weak tests

These two tests are incompatible with a V8 change that disallows running JS code from a weak finalizer callback.

Ref: 4733273

* test: fix task starvation in node test

A V8 change makes these contexts get collected in a task that is posted
and run asynchronously. The tests were synchronously GC'ing in an
infinite loop, preventing the task loop from running the task that would
GC these contexts.

This change should be upstreamed in some way.

Ref: 4733273

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: alice <alice@makenotion.com>
Co-authored-by: Samuel Maddock <smaddock@slack-corp.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Co-authored-by: clavin <clavin@electronjs.org>
(cherry picked from commit 9c019b6147)

* Remove file-wide unsafe buffer suppression from content/ [3 of N]

6341711

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-04-07 13:09:35 -05:00

442 lines
17 KiB
C++

// Copyright (c) 2020 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/webui/accessibility_ui.h"
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_writer.h"
#include "base/strings/escape.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/accessibility_resources.h" // nogncheck
#include "chrome/grit/accessibility_resources_map.h" // nogncheck
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/ax_inspect_factory.h"
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/browser/web_ui_data_source.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/native_window.h"
#include "shell/browser/window_list.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_updates_and_events.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/accessibility/widget_ax_tree_id_map.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace {
constexpr std::string_view kTargetsDataFile = "targets-data.json";
constexpr std::string_view kAccessibilityModeField = "a11yMode";
constexpr std::string_view kBrowsersField = "browsers";
constexpr std::string_view kErrorField = "error";
constexpr std::string_view kFaviconUrlField = "faviconUrl";
constexpr std::string_view kNameField = "name";
constexpr std::string_view kPagesField = "pages";
constexpr std::string_view kPidField = "pid";
constexpr std::string_view kProcessIdField = "processId";
constexpr std::string_view kRequestTypeField = "requestType";
constexpr std::string_view kRoutingIdField = "routingId";
constexpr std::string_view kSessionIdField = "sessionId";
constexpr std::string_view kSupportedApiTypesField = "supportedApiTypes";
constexpr std::string_view kTreeField = "tree";
constexpr std::string_view kTypeField = "type";
constexpr std::string_view kUrlField = "url";
constexpr std::string_view kApiTypeField = "apiType";
// Global flags
constexpr std::string_view kBrowser = "browser";
constexpr std::string_view kCopyTree = "copyTree";
constexpr std::string_view kHTML = "html";
constexpr std::string_view kLocked = "locked";
constexpr std::string_view kNative = "native";
constexpr std::string_view kPage = "page";
constexpr std::string_view kPDFPrinting = "pdfPrinting";
constexpr std::string_view kExtendedProperties = "extendedProperties";
constexpr std::string_view kShowOrRefreshTree = "showOrRefreshTree";
constexpr std::string_view kText = "text";
constexpr std::string_view kWeb = "web";
// Possible global flag values
constexpr std::string_view kDisabled = "disabled";
constexpr std::string_view kOff = "off";
constexpr std::string_view kOn = "on";
base::Value::Dict BuildTargetDescriptor(
const GURL& url,
const std::string& name,
const GURL& favicon_url,
int process_id,
int routing_id,
ui::AXMode accessibility_mode,
base::ProcessHandle handle = base::kNullProcessHandle) {
base::Value::Dict target_data;
target_data.Set(kProcessIdField, process_id);
target_data.Set(kRoutingIdField, routing_id);
target_data.Set(kUrlField, url.spec());
target_data.Set(kNameField, base::EscapeForHTML(name));
target_data.Set(kPidField, static_cast<int>(base::GetProcId(handle)));
target_data.Set(kFaviconUrlField, favicon_url.spec());
target_data.Set(kAccessibilityModeField,
static_cast<int>(accessibility_mode.flags()));
target_data.Set(kTypeField, kPage);
return target_data;
}
base::Value::Dict BuildTargetDescriptor(content::RenderViewHost* rvh) {
content::WebContents* web_contents =
content::WebContents::FromRenderViewHost(rvh);
ui::AXMode accessibility_mode;
std::string title;
GURL url;
GURL favicon_url;
if (web_contents) {
url = web_contents->GetURL();
title = base::UTF16ToUTF8(web_contents->GetTitle());
content::NavigationController& controller = web_contents->GetController();
content::NavigationEntry* entry = controller.GetVisibleEntry();
if (entry != nullptr && entry->GetURL().is_valid()) {
gfx::Image favicon_image = entry->GetFavicon().image;
if (!favicon_image.IsEmpty()) {
const SkBitmap* favicon_bitmap = favicon_image.ToSkBitmap();
favicon_url = GURL(webui::GetBitmapDataUrl(*favicon_bitmap));
}
}
accessibility_mode = web_contents->GetAccessibilityMode();
}
return BuildTargetDescriptor(url, title, favicon_url,
rvh->GetProcess()->GetDeprecatedID(),
rvh->GetRoutingID(), accessibility_mode);
}
base::Value::Dict BuildTargetDescriptor(electron::NativeWindow* window) {
base::Value::Dict target_data;
target_data.Set(kSessionIdField, window->window_id());
target_data.Set(kNameField, window->GetTitle());
target_data.Set(kTypeField, kBrowser);
return target_data;
}
bool ShouldHandleAccessibilityRequestCallback(const std::string& path) {
return path == kTargetsDataFile;
}
void HandleAccessibilityRequestCallback(
content::BrowserContext* current_context,
const std::string& path,
content::WebUIDataSource::GotDataCallback callback) {
DCHECK(ShouldHandleAccessibilityRequestCallback(path));
base::Value::Dict data;
PrefService* pref =
static_cast<electron::ElectronBrowserContext*>(current_context)->prefs();
ui::AXMode mode =
content::BrowserAccessibilityState::GetInstance()->GetAccessibilityMode();
bool is_a11y_allowed = content::BrowserAccessibilityState::GetInstance()
->IsAccessibilityAllowed();
bool native = mode.has_mode(ui::AXMode::kNativeAPIs);
bool web = mode.has_mode(ui::AXMode::kWebContents);
bool text = mode.has_mode(ui::AXMode::kInlineTextBoxes);
bool extendedProperties = mode.has_mode(ui::AXMode::kExtendedProperties);
bool html = mode.has_mode(ui::AXMode::kHTML);
bool pdf_printing = mode.has_mode(ui::AXMode::kPDFPrinting);
// The "native" and "web" flags are disabled if
// --disable-renderer-accessibility is set.
data.Set(kNative, is_a11y_allowed ? (native ? kOn : kOff) : kDisabled);
data.Set(kWeb, is_a11y_allowed ? (web ? kOn : kOff) : kDisabled);
// The "text", "extendedProperties" and "html" flags are only
// meaningful if "web" is enabled.
bool is_web_enabled = is_a11y_allowed && web;
data.Set(kText, is_web_enabled ? (text ? kOn : kOff) : kDisabled);
data.Set(kExtendedProperties,
is_web_enabled ? (extendedProperties ? kOn : kOff) : kDisabled);
data.Set(kHTML, is_web_enabled ? (html ? kOn : kOff) : kDisabled);
// The "pdfPrinting" flag is independent of the others.
data.Set(kPDFPrinting, pdf_printing ? kOn : kOff);
std::string pref_api_type =
std::string(pref->GetString(prefs::kShownAccessibilityApiType));
bool pref_api_type_supported = false;
std::vector<ui::AXApiType::Type> supported_api_types =
content::AXInspectFactory::SupportedApis();
base::Value::List supported_api_list;
supported_api_list.reserve(supported_api_types.size());
for (ui::AXApiType::Type type : supported_api_types) {
supported_api_list.Append(std::string_view(type));
if (type == ui::AXApiType::From(pref_api_type)) {
pref_api_type_supported = true;
}
}
data.Set(kSupportedApiTypesField, std::move(supported_api_list));
// If the saved API type is not supported, use the default platform formatter
// API type.
if (!pref_api_type_supported) {
pref_api_type = std::string_view(
content::AXInspectFactory::DefaultPlatformFormatterType());
}
data.Set(kApiTypeField, pref_api_type);
bool is_mode_locked = !content::BrowserAccessibilityState::GetInstance()
->IsAXModeChangeAllowed();
data.Set(kLocked, is_mode_locked ? kOn : kOff);
base::Value::List page_list;
std::unique_ptr<content::RenderWidgetHostIterator> widget_iter(
content::RenderWidgetHost::GetRenderWidgetHosts());
while (content::RenderWidgetHost* widget = widget_iter->GetNextHost()) {
// Ignore processes that don't have a connection, such as crashed tabs.
if (!widget->GetProcess()->IsInitializedAndNotDead()) {
continue;
}
content::RenderViewHost* rvh = content::RenderViewHost::From(widget);
if (!rvh) {
continue;
}
content::WebContents* web_contents =
content::WebContents::FromRenderViewHost(rvh);
content::WebContentsDelegate* delegate = web_contents->GetDelegate();
if (!delegate) {
continue;
}
if (web_contents->GetPrimaryMainFrame()->GetRenderViewHost() != rvh) {
continue;
}
// Ignore views that are never user-visible, like background pages.
if (delegate->IsNeverComposited(web_contents)) {
continue;
}
content::BrowserContext* context = rvh->GetProcess()->GetBrowserContext();
if (context != current_context) {
continue;
}
base::Value::Dict descriptor = BuildTargetDescriptor(rvh);
descriptor.Set(kNative, is_a11y_allowed);
descriptor.Set(kExtendedProperties, is_web_enabled && extendedProperties);
descriptor.Set(kWeb, is_web_enabled);
page_list.Append(std::move(descriptor));
}
data.Set(kPagesField, std::move(page_list));
base::Value::List window_list;
for (auto* window : electron::WindowList::GetWindows()) {
window_list.Append(BuildTargetDescriptor(window));
}
data.Set(kBrowsersField, std::move(window_list));
std::move(callback).Run(base::MakeRefCounted<base::RefCountedString>(
base::WriteJson(data).value_or("")));
}
std::string RecursiveDumpAXPlatformNodeAsString(
ui::AXPlatformNode* node,
int indent,
const std::vector<ui::AXPropertyFilter>& property_filters) {
if (!node) {
return "";
}
std::string str(2 * indent, '+');
std::string line = node->GetDelegate()->GetData().ToString();
std::vector<std::string> attributes = base::SplitString(
line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (std::string attribute : attributes) {
if (ui::AXTreeFormatter::MatchesPropertyFilters(property_filters, attribute,
false)) {
str += attribute + " ";
}
}
str += "\n";
for (size_t i = 0; i < node->GetDelegate()->GetChildCount(); i++) {
gfx::NativeViewAccessible child = node->GetDelegate()->ChildAtIndex(i);
ui::AXPlatformNode* child_node =
ui::AXPlatformNode::FromNativeViewAccessible(child);
str += RecursiveDumpAXPlatformNodeAsString(child_node, indent + 1,
property_filters);
}
return str;
}
// Add property filters to the property_filters vector for the given property
// filter type. The attributes are passed in as a string with each attribute
// separated by a space.
void AddPropertyFilters(std::vector<ui::AXPropertyFilter>& property_filters,
const std::string& attributes,
ui::AXPropertyFilter::Type type) {
for (const std::string& attribute : base::SplitString(
attributes, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
property_filters.emplace_back(attribute, type);
}
}
bool IsValidJSValue(const std::string* str) {
return str && str->length() < 5000U;
}
const std::string& CheckJSValue(const std::string* str) {
CHECK(IsValidJSValue(str));
return *str;
}
} // namespace
ElectronAccessibilityUI::ElectronAccessibilityUI(content::WebUI* web_ui)
: content::WebUIController(web_ui) {
auto* const browser_context = web_ui->GetWebContents()->GetBrowserContext();
// Set up the chrome://accessibility source.
content::WebUIDataSource* html_source =
content::WebUIDataSource::CreateAndAdd(
browser_context, chrome::kChromeUIAccessibilityHost);
// Add required resources.
html_source->UseStringsJs();
html_source->AddResourcePaths(kAccessibilityResources);
html_source->SetDefaultResource(IDR_ACCESSIBILITY_ACCESSIBILITY_HTML);
html_source->SetRequestFilter(
base::BindRepeating(&ShouldHandleAccessibilityRequestCallback),
base::BindRepeating(&HandleAccessibilityRequestCallback,
browser_context));
html_source->OverrideContentSecurityPolicy(
network::mojom::CSPDirectiveName::TrustedTypes,
"trusted-types parse-html-subset sanitize-inner-html;");
web_ui->AddMessageHandler(
std::make_unique<ElectronAccessibilityUIMessageHandler>());
}
ElectronAccessibilityUI::~ElectronAccessibilityUI() = default;
ElectronAccessibilityUIMessageHandler::ElectronAccessibilityUIMessageHandler() =
default;
void ElectronAccessibilityUIMessageHandler::GetRequestTypeAndFilters(
const base::Value::Dict& data,
std::string& request_type,
std::string& allow,
std::string& allow_empty,
std::string& deny) {
request_type = CheckJSValue(data.FindString(kRequestTypeField));
CHECK(request_type == kShowOrRefreshTree || request_type == kCopyTree);
allow = CheckJSValue(data.FindStringByDottedPath("filters.allow"));
allow_empty = CheckJSValue(data.FindStringByDottedPath("filters.allowEmpty"));
deny = CheckJSValue(data.FindStringByDottedPath("filters.deny"));
}
void ElectronAccessibilityUIMessageHandler::RequestNativeUITree(
const base::Value::List& args) {
const base::Value::Dict& data = args.front().GetDict();
std::string request_type, allow, allow_empty, deny;
GetRequestTypeAndFilters(data, request_type, allow, allow_empty, deny);
int window_id = *data.FindInt(kSessionIdField);
AllowJavascript();
std::vector<ui::AXPropertyFilter> property_filters;
AddPropertyFilters(property_filters, allow, ui::AXPropertyFilter::ALLOW);
AddPropertyFilters(property_filters, allow_empty,
ui::AXPropertyFilter::ALLOW_EMPTY);
AddPropertyFilters(property_filters, deny, ui::AXPropertyFilter::DENY);
for (auto* window : electron::WindowList::GetWindows()) {
if (window->window_id() == window_id) {
base::Value::Dict result = BuildTargetDescriptor(window);
gfx::NativeWindow native_window = window->GetNativeWindow();
ui::AXPlatformNode* node =
ui::AXPlatformNode::FromNativeWindow(native_window);
result.Set(kTreeField, RecursiveDumpAXPlatformNodeAsString(
node, 0, property_filters));
FireWebUIListener(request_type, result);
return;
}
}
// No browser with the specified |id| was found.
base::Value::Dict result;
result.Set(kSessionIdField, window_id);
result.Set(kTypeField, kBrowser);
result.Set(kErrorField, "Window no longer exists.");
FireWebUIListener(request_type, result);
}
void ElectronAccessibilityUIMessageHandler::RegisterMessages() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
web_ui()->RegisterMessageCallback(
"toggleAccessibility",
base::BindRepeating(
&AccessibilityUIMessageHandler::ToggleAccessibilityForWebContents,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setGlobalFlag",
base::BindRepeating(&AccessibilityUIMessageHandler::SetGlobalFlag,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setGlobalString",
base::BindRepeating(&AccessibilityUIMessageHandler::SetGlobalString,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"requestWebContentsTree",
base::BindRepeating(
&AccessibilityUIMessageHandler::RequestWebContentsTree,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"requestNativeUITree",
base::BindRepeating(
&ElectronAccessibilityUIMessageHandler::RequestNativeUITree,
base::Unretained(this)));
#if defined(USE_AURA)
web_ui()->RegisterMessageCallback(
"requestWidgetsTree",
base::BindRepeating(&AccessibilityUIMessageHandler::RequestWidgetsTree,
base::Unretained(this)));
#endif
web_ui()->RegisterMessageCallback(
"requestAccessibilityEvents",
base::BindRepeating(
&AccessibilityUIMessageHandler::RequestAccessibilityEvents,
base::Unretained(this)));
}
// static
void ElectronAccessibilityUIMessageHandler::RegisterPrefs(
user_prefs::PrefRegistrySyncable* registry) {
const std::string_view default_api_type =
std::string_view(ui::AXApiType::Type(ui::AXApiType::kBlink));
registry->RegisterStringPref(prefs::kShownAccessibilityApiType,
std::string(default_api_type));
}