electron/shell/browser/ui/webui/accessibility_ui.cc
electron-roller[bot] e86d1cba75
chore: bump chromium to 105.0.5129.0 (main) (#34403)
* chore: bump chromium in DEPS to 104.0.5096.0

* 3651284: Use the entry settings object for window.open navigation

3651284

* 3644598: Make RenderFrameHost used for notification permission decision

3644598

* 3642842: Window Placement: Prototype Fullscreen Capability Delegation - Part 2

3642842

* 3652785: [sandbox] Enable sandboxed pointers on sanitizer builds

3652785

* 3611967: webhid: Migrate HidDelegate to use BrowserContext and Origin

3611967

* 3665762: Remove RenderFrameHost::IsRenderFrameCreated from //content/.

3665762

* 3659375: Fold x509_util_ios and most of x509_util_mac into x509_util_apple

3659375

* 3656234: [CodeHealth] Remove uses of base::ListValue::Append() (Final, take 2)

3656234

* chore: update patch indices

* chore: fix lint

* 3644598: Make RenderFrameHost used for notification permission decision

3644598

* webhid: Migrate HidDelegate to use BrowserContext and Origin

This is a temporary fix for  3611967 to get the build compiling, but we need to either patch around  3611967 or move our device permissioning to BrowserContext

* chore: fix lint

* build: run electron/script/gen-libc++-filenames.js

fixes gn check

* chore: bump chromium in DEPS to 104.0.5098.0

* chore: disable flaking tests

* 3682394: Change pipes surrounding code references in comments to backticks

3682394

* 3652749: Delete GLRenderer and related classes

3652749

* chore: fixup patch indices

* 3671199: Remove ContentMainDelegate::PostFieldTrialInitialization

3671199

* 3607963: hid: Do not exclude HID device with no collections

3607963

* refactor: use ElectronBrowserContext instead of WebContents to persist devices

due to changes like 3611967,
we can no longer use WebContents to store device permissions so this commit
moves device permission storage to live in memory in ElectronBrowserContext
instead.

* 3557253: Deprecate some signature checks

3557253

* chore: bump chromium in DEPS to 104.0.5100.0

* 3680781: Add policy for Renderer App Container.

3680781

* chore: update patch indices

* 3675465: Update NetLog network service API to use mojom::DictionaryValue.

3675465

* chore: bump chromium in DEPS to 104.0.5102.0

* chore: update patches

* chore: bump chromium in DEPS to 104.0.5103.0

* chore: update patches

* chore: bump chromium in DEPS to 104.0.5104.0

* chore: update patches

* fix: add patch for DCHECK in fullscreen test

* build: fix nan build

* build: make the nan spec runner work on macOS

* chore: bump chromium in DEPS to 104.0.5106.0

* chore: update patches

* chore: bump chromium in DEPS to 104.0.5108.0

* chore: update patches

* chore: bump chromium in DEPS to 104.0.5110.0

* chore: update patches

* chore: bump chromium in DEPS to 104.0.5112.0

* chore: bump chromium in DEPS to 105.0.5113.0

* chore: bump chromium in DEPS to 105.0.5115.0

* chore: bump chromium in DEPS to 105.0.5117.0

* chore: update patches

* chore: update libcpp patch

* 3693745: Delete base::LowerCaseEqualsASCII()

Ref: 3693745

* 3669226: Remove printing PostTask usage of IO thread

Ref: 3669226

* 3687395: Remove DictionaryValue::HasKey().

Ref: 3687395

* 3691014: Prevent unsafe narrowing: ui/accessibility, part 2

Ref: 3691014

* 3560567: [MSC] Porting GenerateStreams clients to handle stream vectors.

Ref: 3560567

* 3684873: [Bluetooth][Win/Linux] Add bluetooth pair confirmation prompt

3684873

* chore: bump chromium in DEPS to 105.0.5119.0

* chore: missing includes in desktop_notification_controller

* chore: update patches

* 3685951: Reland "Make sure screen object is created once in tests."

3685951

* fixup: Reland "Make sure screen object is created once in tests."

* 3646014: [API] Deprecate LegacyOOMErrorCallback

Ref: 3646014

* chore: bump chromium in DEPS to 105.0.5121.0

* chore: update patches

* 3699085: [cleanup] update PrintBackend::EnumeratePrinters to use reference

Ref: 3699085

* chore: bump chromium in DEPS to 105.0.5123.0

* chore: update patches

* chore: bump chromium in DEPS to 105.0.5125.0

* chore: update patches

* 3630082: [sandbox] Also enable the sandbox outside of Chromium builds

Ref: 3630082

* chore: bump chromium in DEPS to 105.0.5127.0

* chore: update patches

* chore: bump chromium in DEPS to 105.0.5129.0

* chore: update patches

* 3703741: Remove WebContents::GetMainFrame.

3703741

* chore: update patches

* fixup! 3703741: Remove WebContents::GetMainFrame.

* fix lint

* more lint

* chore: document breaking change

* 3687671: [v8] Freeze flags after initialization

3687671

* fixup! 3560567: [MSC] Porting GenerateStreams clients to handle stream vectors.

* use the v8 allocator for node serdes

* chore: update patches

* remove faulty non-v8-sandbox-compatible code

* make NodeArrayBufferAllocator use the v8 allocator under the hood

* fixup! 3560567: [MSC] Porting GenerateStreams clients to handle stream vectors.

* fix build on windows

* 3691954: [Reland][Extensions Bindings] Validate arguments sent to API events

3691954

* chore: remove deprecated AccessorSignatures

https://github.com/nodejs/nan/pull/941

* Update patches/chromium/notification_provenance.patch

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>

* remove chore_expose_v8_initialization_isolate_callbacks.patch

* add link to nodejs/nan#941

* 52026: Do not allow md4 or md5 based signatures in X.509 certificates.

https://boringssl-review.googlesource.com/c/boringssl/+/52026

* chore: update patches

* disable nan buffer-test

* disable sandboxed pointers for now

* force sandboxed pointers off

* improve node allocation patch

* restore accidentally dropped node crypto test patch

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@salesforce.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
Co-authored-by: VerteDinde <keeleymhammond@gmail.com>
Co-authored-by: VerteDinde <vertedinde@electronjs.org>
Co-authored-by: Jeremy Rose <jeremya@chromium.org>
Co-authored-by: Jeremy Rose <japthorp@slack-corp.com>
2022-06-27 15:50:08 -05:00

418 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/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.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/dev_ui_browser_resources.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_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";
std::unique_ptr<base::DictionaryValue> 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) {
auto target_data = std::make_unique<base::DictionaryValue>();
target_data->SetInteger(kProcessIdField, process_id);
target_data->SetInteger(kRoutingIdField, routing_id);
target_data->SetString(kUrlField, url.spec());
target_data->SetString(kNameField, base::EscapeForHTML(name));
target_data->SetInteger(kPidField, base::GetProcId(handle));
target_data->SetString(kFaviconUrlField, favicon_url.spec());
target_data->SetInteger(kAccessibilityModeField, accessibility_mode.mode());
target_data->SetString(kTypeField, kPage);
return target_data;
}
std::unique_ptr<base::DictionaryValue> 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);
}
std::unique_ptr<base::DictionaryValue> BuildTargetDescriptor(
electron::NativeWindow* window) {
auto target_data = std::make_unique<base::DictionaryValue>();
target_data->SetInteger(kSessionIdField, window->window_id());
target_data->SetString(kNameField, window->GetTitle());
target_data->SetString(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->push_back(ui::AXPropertyFilter(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::DictionaryValue 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.SetString(kNative,
is_native_enabled ? (native ? kOn : kOff) : kDisabled);
data.SetString(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.SetString(kText, is_web_enabled ? (text ? kOn : kOff) : kDisabled);
data.SetString(kScreenReader,
is_web_enabled ? (screenreader ? kOn : kOff) : kDisabled);
data.SetString(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.SetString(kLabelImages, kDisabled);
// The "pdf" flag is independent of the others.
data.SetString(kPDF, pdf ? kOn : kOff);
// Always dump the Accessibility tree.
data.SetString(kInternal, kOn);
auto rvh_list = std::make_unique<base::ListValue>();
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;
std::unique_ptr<base::DictionaryValue> descriptor =
BuildTargetDescriptor(rvh);
descriptor->SetBoolean(kNative, is_native_enabled);
descriptor->SetBoolean(kWeb, is_web_enabled);
descriptor->SetBoolean(kLabelImages,
are_accessibility_image_labels_enabled);
rvh_list->Append(base::Value::FromUniquePtrValue(std::move(descriptor)));
}
data.Set(kPagesField, std::move(rvh_list));
auto window_list = std::make_unique<base::ListValue>();
for (auto* window : electron::WindowList::GetWindows()) {
window_list->Append(
base::Value::FromUniquePtrValue(BuildTargetDescriptor(window)));
}
data.Set(kBrowsersField, std::move(window_list));
std::string json_string;
base::JSONWriter::Write(data, &json_string);
std::move(callback).Run(base::RefCountedString::TakeString(&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::Create(chrome::kChromeUIAccessibilityHost);
// Add required resources.
html_source->UseStringsJs();
html_source->AddResourcePath("accessibility.css", IDR_ACCESSIBILITY_CSS);
html_source->AddResourcePath("accessibility.js", IDR_ACCESSIBILITY_JS);
html_source->SetDefaultResource(IDR_ACCESSIBILITY_HTML);
html_source->SetRequestFilter(
base::BindRepeating(&ShouldHandleAccessibilityRequestCallback),
base::BindRepeating(&HandleAccessibilityRequestCallback,
web_ui->GetWebContents()->GetBrowserContext()));
content::BrowserContext* browser_context =
web_ui->GetWebContents()->GetBrowserContext();
content::WebUIDataSource::Add(browser_context, html_source);
web_ui->AddMessageHandler(
std::make_unique<ElectronAccessibilityUIMessageHandler>());
}
ElectronAccessibilityUI::~ElectronAccessibilityUI() = default;
ElectronAccessibilityUIMessageHandler::ElectronAccessibilityUIMessageHandler() =
default;
void ElectronAccessibilityUIMessageHandler::RequestNativeUITree(
const base::ListValue* args) {
const base::DictionaryValue* data;
CHECK(args->GetDictionary(0, &data));
int window_id = *data->FindIntPath(kSessionIdField);
const std::string* request_type_p = data->FindStringPath(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* allow_p = data->FindStringPath("filters.allow");
CHECK(IsValidJSValue(allow_p));
std::string allow = *allow_p;
const std::string* allow_empty_p = data->FindStringPath("filters.allowEmpty");
CHECK(IsValidJSValue(allow_empty_p));
std::string allow_empty = *allow_empty_p;
const std::string* deny_p = data->FindStringPath("filters.deny");
CHECK(IsValidJSValue(deny_p));
std::string deny = *deny_p;
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) {
std::unique_ptr<base::DictionaryValue> result(
BuildTargetDescriptor(window));
gfx::NativeWindow native_window = window->GetNativeWindow();
ui::AXPlatformNode* node =
ui::AXPlatformNode::FromNativeWindow(native_window);
result->SetKey(kTreeField,
base::Value(RecursiveDumpAXPlatformNodeAsString(
node, 0, property_filters)));
CallJavascriptFunction(request_type, *(result.get()));
return;
}
}
// No browser with the specified |id| was found.
auto result = std::make_unique<base::DictionaryValue>();
result->SetInteger(kSessionIdField, window_id);
result->SetString(kTypeField, kBrowser);
result->SetString(kErrorField, "Window no longer exists.");
CallJavascriptFunction(request_type, *(result.get()));
}
void ElectronAccessibilityUIMessageHandler::RegisterMessages() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
web_ui()->RegisterDeprecatedMessageCallback(
"toggleAccessibility",
base::BindRepeating(&AccessibilityUIMessageHandler::ToggleAccessibility,
base::Unretained(this)));
web_ui()->RegisterDeprecatedMessageCallback(
"setGlobalFlag",
base::BindRepeating(&AccessibilityUIMessageHandler::SetGlobalFlag,
base::Unretained(this)));
web_ui()->RegisterDeprecatedMessageCallback(
"requestWebContentsTree",
base::BindRepeating(
&AccessibilityUIMessageHandler::RequestWebContentsTree,
base::Unretained(this)));
web_ui()->RegisterDeprecatedMessageCallback(
"requestNativeUITree",
base::BindRepeating(
&ElectronAccessibilityUIMessageHandler::RequestNativeUITree,
base::Unretained(this)));
web_ui()->RegisterDeprecatedMessageCallback(
"requestAccessibilityEvents",
base::BindRepeating(
&AccessibilityUIMessageHandler::RequestAccessibilityEvents,
base::Unretained(this)));
}