electron/shell/browser/ui/webui/accessibility_ui.cc
electron-roller[bot] 89117fdd99
chore: bump chromium to 118.0.5975.0 (main) (#39531)
* chore: bump chromium in DEPS to 118.0.5951.0

* chore: update printing.patch

Xref: 4727894

No logic changes, but patch needed to be manually re-applied due to upstream code shear

* chore: update port_autofill_colors_to_the_color_pipeline.patch

No manual changes; patch applied with fuzz

* chore: update patches

* chore: bump chromium in DEPS to 118.0.5953.0

* chore: update patches

* chore: bump chromium in DEPS to 118.0.5955.0

* chore: update patches

* chore: bump chromium in DEPS to 118.0.5957.0

* chore: update patches

* chore: include path of native_web_keyboard_event.h

Xref: 4758689

* chore: remove reference to eextensions/browser/notification-types.h

Xref: 4771627

* chore: update references to renamed upstream field NativeWebKeyboardEvent.skip_if_unhandled (formerly known as skip_in_browser

Xref: 4758689

Need a second pair of eyes on this commit. In particular the reference in content_converter.cc, skipInBrowser, seems to not be set or documented anywhere? Is this unused/vestigal code?

* chore: sync signature of ElectronExtensionsBrowserClient::IsValidContext() to upstream change

Xref: 4784198

* chore: add auto_pip_setting_helper.[cc,h] to chromium_src build

Xref: 4688277

Exiting upstream code used by chromium_src now depends on this new upstream class

* chore: bump chromium in DEPS to 118.0.5959.0

* chore: update add_maximized_parameter_to_linuxui_getwindowframeprovider.patch

Xref: add_maximized_parameter_to_linuxui_getwindowframeprovider.patch

manually adjust patch to minor upstream chagnes

* chore: update patches

* chore: bump chromium in DEPS to 118.0.5961.0

* chore: bump chromium in DEPS to 118.0.5963.0

* chore: update patches

* 4780994: Rename various base files to "apple" since iOS uses them too
4780994

* Many files moved from `mac` -> `apple`

This commit follows a handful of CLs that simply rename files/symbols to change `mac`
to `apple`
to signify their use across both macOS and iOS:
- 4784010: Move scoped_nsautorelease_pool to base/apple, leave a forwarding header
- 4790744: Move foundation_util to base/apple, leave a forwarding header
- 4790741: Move scoped_cftypreref to base/apple, leave a forwarding header
- 4787627: Move and rename macOS+iOS base/ files in PA to "apple"
- 4780399: Move OSStatus logging to base/apple
- 4787387: Remove forwarding headers
- 4781113: Rename message_pump_mac to "apple" because iOS uses it too

* fixup minor patch update error

A function param got dropped from this patch somewhere earlier

* chore: bump chromium in DEPS to 118.0.5965.2

* chore: update patches

* 4799213: Move ScopedTypeRef and ScopedCFTypeRef into base:🍎:
4799213

* Fix removed include to BrowserContext

In crrev.com/c/4767962 an include to BrowserContext was removed,
which was necessary for compilation. This broke only for us because
"chrome/browser/profiles/profile.h" includes that class, but we remove
all references to profiles.

* chore: bump chromium in DEPS to 118.0.5967.0

* chore: update patches

* chore: bump chromium in DEPS to 118.0.5969.0

* chore: update patches

* chore: bump chromium in DEPS to 118.0.5971.0

* chore: bump chromium in DEPS to 118.0.5973.0

* chore: update patches

* 4772121: [OOPIF PDF] Replace PDFWebContentsHelper with PDFDocumentHelper
4772121

* 4811164: [Extensions] Do some cleanup in ChromeManagementAPIDelegate.
4811164

* 4809488: Remove duplicate dnd functionality between Web and Renderer prefs
4809488

Given that this is no longer an option of web preferences, we should
consider deprecating this option and then removing it.

* chore: bump chromium in DEPS to 118.0.5975.0

* chore: update patches

* fixup! chore: add auto_pip_settings_helper.{cc|h} to chromium_src build

* Reland "[windows] Remove RegKey::DeleteEmptyKey"

Refs 4813255

* Ensure StrCat means StrCat

Refs 1117180

* fixup! Remove RegKey::DeleteEmptyKey

* Consistently reject large p and large q in DH

Refs https://boringssl-review.googlesource.com/c/boringssl/+/62226

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: clavin <clavin@electronjs.org>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2023-09-01 15:54:59 +09:00

414 lines
16 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 <utility>
#include <vector>
#include "base/command_line.h"
#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/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/common/chrome_features.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_event_notification_details.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/native_window.h"
#include "shell/browser/window_list.h"
#include "third_party/abseil-cpp/absl/types/optional.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"
namespace {
static const char kTargetsDataFile[] = "targets-data.json";
static const char kAccessibilityModeField[] = "a11yMode";
static const char kBrowsersField[] = "browsers";
static const char kErrorField[] = "error";
static const char kFaviconUrlField[] = "faviconUrl";
static const char kNameField[] = "name";
static const char kPagesField[] = "pages";
static const char kPidField[] = "pid";
static const char kSessionIdField[] = "sessionId";
static const char kProcessIdField[] = "processId";
static const char kRequestTypeField[] = "requestType";
static const char kRoutingIdField[] = "routingId";
static const char kTypeField[] = "type";
static const char kUrlField[] = "url";
static const char kTreeField[] = "tree";
// Global flags
static const char kBrowser[] = "browser";
static const char kCopyTree[] = "copyTree";
static const char kHTML[] = "html";
static const char kInternal[] = "internal";
static const char kLabelImages[] = "labelImages";
static const char kNative[] = "native";
static const char kPage[] = "page";
static const char kPDF[] = "pdf";
static const char kScreenReader[] = "screenreader";
static const char kShowOrRefreshTree[] = "showOrRefreshTree";
static const char kText[] = "text";
static const char kWeb[] = "web";
// Possible global flag values
static const char kDisabled[] = "disabled";
static const char kOff[] = "off";
static const char 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()->GetID(), 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;
}
// 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 MatchesPropertyFilters(
const std::vector<ui::AXPropertyFilter>& property_filters,
const std::string& text) {
bool allow = false;
for (const auto& filter : property_filters) {
if (base::MatchPattern(text, filter.match_str)) {
switch (filter.type) {
case ui::AXPropertyFilter::ALLOW_EMPTY:
case ui::AXPropertyFilter::SCRIPT:
allow = true;
break;
case ui::AXPropertyFilter::ALLOW:
allow = (!base::MatchPattern(text, "*=''"));
break;
case ui::AXPropertyFilter::DENY:
allow = false;
break;
}
}
}
return allow;
}
std::string RecursiveDumpAXPlatformNodeAsString(
const ui::AXPlatformNode* node,
int indent,
const std::vector<ui::AXPropertyFilter>& property_filters) {
if (!node)
return "";
std::string str(2 * indent, '+');
const std::string line = node->GetDelegate()->GetData().ToString();
const std::vector<std::string> attributes = base::SplitString(
line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const std::string& attribute : attributes) {
if (MatchesPropertyFilters(property_filters, attribute)) {
str += attribute + " ";
}
}
str += "\n";
for (size_t i = 0; i < node->GetDelegate()->GetChildCount(); i++) {
gfx::NativeViewAccessible child = node->GetDelegate()->ChildAtIndex(i);
const ui::AXPlatformNode* child_node =
ui::AXPlatformNode::FromNativeViewAccessible(child);
str += RecursiveDumpAXPlatformNodeAsString(child_node, indent + 1,
property_filters);
}
return str;
}
bool IsValidJSValue(const std::string* str) {
return str && str->length() < 5000U;
}
void HandleAccessibilityRequestCallback(
content::BrowserContext* current_context,
const std::string& path,
content::WebUIDataSource::GotDataCallback callback) {
DCHECK(ShouldHandleAccessibilityRequestCallback(path));
base::Value::Dict data;
ui::AXMode mode =
content::BrowserAccessibilityState::GetInstance()->GetAccessibilityMode();
bool is_native_enabled = content::BrowserAccessibilityState::GetInstance()
->IsRendererAccessibilityEnabled();
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 screenreader = mode.has_mode(ui::AXMode::kScreenReader);
bool html = mode.has_mode(ui::AXMode::kHTML);
bool pdf = mode.has_mode(ui::AXMode::kPDF);
// The "native" and "web" flags are disabled if
// --disable-renderer-accessibility is set.
data.Set(kNative, is_native_enabled ? (native ? kOn : kOff) : kDisabled);
data.Set(kWeb, is_native_enabled ? (web ? kOn : kOff) : kDisabled);
// The "text", "screenreader" and "html" flags are only
// meaningful if "web" is enabled.
bool is_web_enabled = is_native_enabled && web;
data.Set(kText, is_web_enabled ? (text ? kOn : kOff) : kDisabled);
data.Set(kScreenReader,
is_web_enabled ? (screenreader ? kOn : kOff) : kDisabled);
data.Set(kHTML, is_web_enabled ? (html ? kOn : kOff) : kDisabled);
// TODO(codebytere): enable use of this flag.
//
// The "labelImages" flag works only if "web" is enabled, the current profile
// has the kAccessibilityImageLabelsEnabled preference set and the appropriate
// command line switch has been used. Since this is so closely tied into user
// prefs and causes bugs, we're disabling it for now.
bool are_accessibility_image_labels_enabled = is_web_enabled;
data.Set(kLabelImages, kDisabled);
// The "pdf" flag is independent of the others.
data.Set(kPDF, pdf ? kOn : kOff);
// Always dump the Accessibility tree.
data.Set(kInternal, kOn);
base::Value::List rvh_list;
std::unique_ptr<content::RenderWidgetHostIterator> widgets(
content::RenderWidgetHost::GetRenderWidgetHosts());
while (content::RenderWidgetHost* widget = widgets->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;
// 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_native_enabled);
descriptor.Set(kWeb, is_web_enabled);
descriptor.Set(kLabelImages, are_accessibility_image_labels_enabled);
rvh_list.Append(std::move(descriptor));
}
data.Set(kPagesField, std::move(rvh_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::string json_string;
base::JSONWriter::Write(data, &json_string);
std::move(callback).Run(
base::MakeRefCounted<base::RefCountedString>(std::move(json_string)));
}
} // namespace
ElectronAccessibilityUI::ElectronAccessibilityUI(content::WebUI* web_ui)
: content::WebUIController(web_ui) {
// Set up the chrome://accessibility source.
content::WebUIDataSource* html_source =
content::WebUIDataSource::CreateAndAdd(
web_ui->GetWebContents()->GetBrowserContext(),
chrome::kChromeUIAccessibilityHost);
// Add required resources.
html_source->UseStringsJs();
html_source->AddResourcePaths(
base::make_span(kAccessibilityResources, kAccessibilityResourcesSize));
html_source->SetDefaultResource(IDR_ACCESSIBILITY_ACCESSIBILITY_HTML);
html_source->SetRequestFilter(
base::BindRepeating(&ShouldHandleAccessibilityRequestCallback),
base::BindRepeating(&HandleAccessibilityRequestCallback,
web_ui->GetWebContents()->GetBrowserContext()));
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::RequestNativeUITree(
const base::Value::List& args) {
const base::Value::Dict& data = args.front().GetDict();
const int window_id = *data.FindInt(kSessionIdField);
const std::string* const request_type_p = data.FindString(kRequestTypeField);
CHECK(IsValidJSValue(request_type_p));
std::string request_type = *request_type_p;
CHECK(request_type == kShowOrRefreshTree || request_type == kCopyTree);
request_type = "accessibility." + request_type;
const std::string* const allow_p =
data.FindStringByDottedPath("filters.allow");
CHECK(IsValidJSValue(allow_p));
const std::string* const allow_empty_p =
data.FindStringByDottedPath("filters.allowEmpty");
CHECK(IsValidJSValue(allow_empty_p));
const std::string* const deny_p = data.FindStringByDottedPath("filters.deny");
CHECK(IsValidJSValue(deny_p));
AllowJavascript();
std::vector<ui::AXPropertyFilter> property_filters;
AddPropertyFilters(&property_filters, *allow_p, ui::AXPropertyFilter::ALLOW);
AddPropertyFilters(&property_filters, *allow_empty_p,
ui::AXPropertyFilter::ALLOW_EMPTY);
AddPropertyFilters(&property_filters, *deny_p, 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, base::Value(RecursiveDumpAXPlatformNodeAsString(
node, 0, property_filters)));
CallJavascriptFunction(request_type, base::Value(std::move(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.");
CallJavascriptFunction(request_type, base::Value(std::move(result)));
}
void ElectronAccessibilityUIMessageHandler::RegisterMessages() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
web_ui()->RegisterMessageCallback(
"toggleAccessibility",
base::BindRepeating(&AccessibilityUIMessageHandler::ToggleAccessibility,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setGlobalFlag",
base::BindRepeating(&AccessibilityUIMessageHandler::SetGlobalFlag,
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)));
web_ui()->RegisterMessageCallback(
"requestAccessibilityEvents",
base::BindRepeating(
&AccessibilityUIMessageHandler::RequestAccessibilityEvents,
base::Unretained(this)));
}