accessibility_ui.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. // Copyright (c) 2020 Microsoft, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/ui/webui/accessibility_ui.h"
  5. #include <memory>
  6. #include <string>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/command_line.h"
  10. #include "base/functional/bind.h"
  11. #include "base/functional/callback_helpers.h"
  12. #include "base/json/json_writer.h"
  13. #include "base/strings/escape.h"
  14. #include "base/strings/pattern.h"
  15. #include "base/strings/string_number_conversions.h"
  16. #include "base/strings/utf_string_conversions.h"
  17. #include "base/values.h"
  18. #include "build/build_config.h"
  19. #include "chrome/common/chrome_features.h"
  20. #include "chrome/common/pref_names.h"
  21. #include "chrome/common/webui_url_constants.h"
  22. #include "chrome/grit/dev_ui_browser_resources.h" // nogncheck
  23. #include "components/pref_registry/pref_registry_syncable.h"
  24. #include "components/prefs/pref_service.h"
  25. #include "content/public/browser/ax_event_notification_details.h"
  26. #include "content/public/browser/ax_inspect_factory.h"
  27. #include "content/public/browser/browser_accessibility_state.h"
  28. #include "content/public/browser/browser_thread.h"
  29. #include "content/public/browser/favicon_status.h"
  30. #include "content/public/browser/navigation_entry.h"
  31. #include "content/public/browser/render_process_host.h"
  32. #include "content/public/browser/render_view_host.h"
  33. #include "content/public/browser/render_widget_host.h"
  34. #include "content/public/browser/render_widget_host_iterator.h"
  35. #include "content/public/browser/web_contents.h"
  36. #include "content/public/browser/web_contents_delegate.h"
  37. #include "content/public/browser/web_ui_data_source.h"
  38. #include "shell/browser/native_window.h"
  39. #include "shell/browser/window_list.h"
  40. #include "third_party/abseil-cpp/absl/types/optional.h"
  41. #include "ui/accessibility/platform/ax_platform_node.h"
  42. #include "ui/accessibility/platform/ax_platform_node_delegate.h"
  43. #include "ui/base/webui/web_ui_util.h"
  44. namespace {
  45. static const char kTargetsDataFile[] = "targets-data.json";
  46. static const char kAccessibilityModeField[] = "a11yMode";
  47. static const char kBrowsersField[] = "browsers";
  48. static const char kErrorField[] = "error";
  49. static const char kFaviconUrlField[] = "faviconUrl";
  50. static const char kNameField[] = "name";
  51. static const char kPagesField[] = "pages";
  52. static const char kPidField[] = "pid";
  53. static const char kSessionIdField[] = "sessionId";
  54. static const char kProcessIdField[] = "processId";
  55. static const char kRequestTypeField[] = "requestType";
  56. static const char kRoutingIdField[] = "routingId";
  57. static const char kTypeField[] = "type";
  58. static const char kUrlField[] = "url";
  59. static const char kTreeField[] = "tree";
  60. // Global flags
  61. static const char kBrowser[] = "browser";
  62. static const char kCopyTree[] = "copyTree";
  63. static const char kHTML[] = "html";
  64. static const char kInternal[] = "internal";
  65. static const char kLabelImages[] = "labelImages";
  66. static const char kNative[] = "native";
  67. static const char kPage[] = "page";
  68. static const char kPDF[] = "pdf";
  69. static const char kScreenReader[] = "screenreader";
  70. static const char kShowOrRefreshTree[] = "showOrRefreshTree";
  71. static const char kText[] = "text";
  72. static const char kWeb[] = "web";
  73. // Possible global flag values
  74. static const char kDisabled[] = "disabled";
  75. static const char kOff[] = "off";
  76. static const char kOn[] = "on";
  77. base::Value::Dict BuildTargetDescriptor(
  78. const GURL& url,
  79. const std::string& name,
  80. const GURL& favicon_url,
  81. int process_id,
  82. int routing_id,
  83. ui::AXMode accessibility_mode,
  84. base::ProcessHandle handle = base::kNullProcessHandle) {
  85. base::Value::Dict target_data;
  86. target_data.Set(kProcessIdField, process_id);
  87. target_data.Set(kRoutingIdField, routing_id);
  88. target_data.Set(kUrlField, url.spec());
  89. target_data.Set(kNameField, base::EscapeForHTML(name));
  90. target_data.Set(kPidField, static_cast<int>(base::GetProcId(handle)));
  91. target_data.Set(kFaviconUrlField, favicon_url.spec());
  92. target_data.Set(kAccessibilityModeField,
  93. static_cast<int>(accessibility_mode.flags()));
  94. target_data.Set(kTypeField, kPage);
  95. return target_data;
  96. }
  97. base::Value::Dict BuildTargetDescriptor(content::RenderViewHost* rvh) {
  98. content::WebContents* web_contents =
  99. content::WebContents::FromRenderViewHost(rvh);
  100. ui::AXMode accessibility_mode;
  101. std::string title;
  102. GURL url;
  103. GURL favicon_url;
  104. if (web_contents) {
  105. url = web_contents->GetURL();
  106. title = base::UTF16ToUTF8(web_contents->GetTitle());
  107. content::NavigationController& controller = web_contents->GetController();
  108. content::NavigationEntry* entry = controller.GetVisibleEntry();
  109. if (entry != nullptr && entry->GetURL().is_valid()) {
  110. gfx::Image favicon_image = entry->GetFavicon().image;
  111. if (!favicon_image.IsEmpty()) {
  112. const SkBitmap* favicon_bitmap = favicon_image.ToSkBitmap();
  113. favicon_url = GURL(webui::GetBitmapDataUrl(*favicon_bitmap));
  114. }
  115. }
  116. accessibility_mode = web_contents->GetAccessibilityMode();
  117. }
  118. return BuildTargetDescriptor(url, title, favicon_url,
  119. rvh->GetProcess()->GetID(), rvh->GetRoutingID(),
  120. accessibility_mode);
  121. }
  122. base::Value::Dict BuildTargetDescriptor(electron::NativeWindow* window) {
  123. base::Value::Dict target_data;
  124. target_data.Set(kSessionIdField, window->window_id());
  125. target_data.Set(kNameField, window->GetTitle());
  126. target_data.Set(kTypeField, kBrowser);
  127. return target_data;
  128. }
  129. bool ShouldHandleAccessibilityRequestCallback(const std::string& path) {
  130. return path == kTargetsDataFile;
  131. }
  132. // Add property filters to the property_filters vector for the given property
  133. // filter type. The attributes are passed in as a string with each attribute
  134. // separated by a space.
  135. void AddPropertyFilters(std::vector<ui::AXPropertyFilter>* property_filters,
  136. const std::string& attributes,
  137. ui::AXPropertyFilter::Type type) {
  138. for (const std::string& attribute : base::SplitString(
  139. attributes, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY)) {
  140. property_filters->emplace_back(attribute, type);
  141. }
  142. }
  143. bool MatchesPropertyFilters(
  144. const std::vector<ui::AXPropertyFilter>& property_filters,
  145. const std::string& text) {
  146. bool allow = false;
  147. for (const auto& filter : property_filters) {
  148. if (base::MatchPattern(text, filter.match_str)) {
  149. switch (filter.type) {
  150. case ui::AXPropertyFilter::ALLOW_EMPTY:
  151. case ui::AXPropertyFilter::SCRIPT:
  152. allow = true;
  153. break;
  154. case ui::AXPropertyFilter::ALLOW:
  155. allow = (!base::MatchPattern(text, "*=''"));
  156. break;
  157. case ui::AXPropertyFilter::DENY:
  158. allow = false;
  159. break;
  160. }
  161. }
  162. }
  163. return allow;
  164. }
  165. std::string RecursiveDumpAXPlatformNodeAsString(
  166. const ui::AXPlatformNode* node,
  167. int indent,
  168. const std::vector<ui::AXPropertyFilter>& property_filters) {
  169. if (!node)
  170. return "";
  171. std::string str(2 * indent, '+');
  172. const std::string line = node->GetDelegate()->GetData().ToString();
  173. const std::vector<std::string> attributes = base::SplitString(
  174. line, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
  175. for (const std::string& attribute : attributes) {
  176. if (MatchesPropertyFilters(property_filters, attribute)) {
  177. str += attribute + " ";
  178. }
  179. }
  180. str += "\n";
  181. for (size_t i = 0; i < node->GetDelegate()->GetChildCount(); i++) {
  182. gfx::NativeViewAccessible child = node->GetDelegate()->ChildAtIndex(i);
  183. const ui::AXPlatformNode* child_node =
  184. ui::AXPlatformNode::FromNativeViewAccessible(child);
  185. str += RecursiveDumpAXPlatformNodeAsString(child_node, indent + 1,
  186. property_filters);
  187. }
  188. return str;
  189. }
  190. bool IsValidJSValue(const std::string* str) {
  191. return str && str->length() < 5000U;
  192. }
  193. void HandleAccessibilityRequestCallback(
  194. content::BrowserContext* current_context,
  195. const std::string& path,
  196. content::WebUIDataSource::GotDataCallback callback) {
  197. DCHECK(ShouldHandleAccessibilityRequestCallback(path));
  198. base::Value::Dict data;
  199. ui::AXMode mode =
  200. content::BrowserAccessibilityState::GetInstance()->GetAccessibilityMode();
  201. bool is_native_enabled = content::BrowserAccessibilityState::GetInstance()
  202. ->IsRendererAccessibilityEnabled();
  203. bool native = mode.has_mode(ui::AXMode::kNativeAPIs);
  204. bool web = mode.has_mode(ui::AXMode::kWebContents);
  205. bool text = mode.has_mode(ui::AXMode::kInlineTextBoxes);
  206. bool screenreader = mode.has_mode(ui::AXMode::kScreenReader);
  207. bool html = mode.has_mode(ui::AXMode::kHTML);
  208. bool pdf = mode.has_mode(ui::AXMode::kPDF);
  209. // The "native" and "web" flags are disabled if
  210. // --disable-renderer-accessibility is set.
  211. data.Set(kNative, is_native_enabled ? (native ? kOn : kOff) : kDisabled);
  212. data.Set(kWeb, is_native_enabled ? (web ? kOn : kOff) : kDisabled);
  213. // The "text", "screenreader" and "html" flags are only
  214. // meaningful if "web" is enabled.
  215. bool is_web_enabled = is_native_enabled && web;
  216. data.Set(kText, is_web_enabled ? (text ? kOn : kOff) : kDisabled);
  217. data.Set(kScreenReader,
  218. is_web_enabled ? (screenreader ? kOn : kOff) : kDisabled);
  219. data.Set(kHTML, is_web_enabled ? (html ? kOn : kOff) : kDisabled);
  220. // TODO(codebytere): enable use of this flag.
  221. //
  222. // The "labelImages" flag works only if "web" is enabled, the current profile
  223. // has the kAccessibilityImageLabelsEnabled preference set and the appropriate
  224. // command line switch has been used. Since this is so closely tied into user
  225. // prefs and causes bugs, we're disabling it for now.
  226. bool are_accessibility_image_labels_enabled = is_web_enabled;
  227. data.Set(kLabelImages, kDisabled);
  228. // The "pdf" flag is independent of the others.
  229. data.Set(kPDF, pdf ? kOn : kOff);
  230. // Always dump the Accessibility tree.
  231. data.Set(kInternal, kOn);
  232. base::Value::List rvh_list;
  233. std::unique_ptr<content::RenderWidgetHostIterator> widgets(
  234. content::RenderWidgetHost::GetRenderWidgetHosts());
  235. while (content::RenderWidgetHost* widget = widgets->GetNextHost()) {
  236. // Ignore processes that don't have a connection, such as crashed tabs.
  237. if (!widget->GetProcess()->IsInitializedAndNotDead())
  238. continue;
  239. content::RenderViewHost* rvh = content::RenderViewHost::From(widget);
  240. if (!rvh)
  241. continue;
  242. content::WebContents* web_contents =
  243. content::WebContents::FromRenderViewHost(rvh);
  244. content::WebContentsDelegate* delegate = web_contents->GetDelegate();
  245. if (!delegate)
  246. continue;
  247. // Ignore views that are never user-visible, like background pages.
  248. if (delegate->IsNeverComposited(web_contents))
  249. continue;
  250. content::BrowserContext* context = rvh->GetProcess()->GetBrowserContext();
  251. if (context != current_context)
  252. continue;
  253. base::Value::Dict descriptor = BuildTargetDescriptor(rvh);
  254. descriptor.Set(kNative, is_native_enabled);
  255. descriptor.Set(kWeb, is_web_enabled);
  256. descriptor.Set(kLabelImages, are_accessibility_image_labels_enabled);
  257. rvh_list.Append(std::move(descriptor));
  258. }
  259. data.Set(kPagesField, std::move(rvh_list));
  260. base::Value::List window_list;
  261. for (auto* window : electron::WindowList::GetWindows()) {
  262. window_list.Append(BuildTargetDescriptor(window));
  263. }
  264. data.Set(kBrowsersField, std::move(window_list));
  265. std::string json_string;
  266. base::JSONWriter::Write(data, &json_string);
  267. std::move(callback).Run(
  268. base::MakeRefCounted<base::RefCountedString>(std::move(json_string)));
  269. }
  270. } // namespace
  271. ElectronAccessibilityUI::ElectronAccessibilityUI(content::WebUI* web_ui)
  272. : content::WebUIController(web_ui) {
  273. // Set up the chrome://accessibility source.
  274. content::WebUIDataSource* html_source =
  275. content::WebUIDataSource::CreateAndAdd(
  276. web_ui->GetWebContents()->GetBrowserContext(),
  277. chrome::kChromeUIAccessibilityHost);
  278. // Add required resources.
  279. html_source->UseStringsJs();
  280. html_source->AddResourcePath("accessibility.css", IDR_ACCESSIBILITY_CSS);
  281. html_source->AddResourcePath("accessibility.js", IDR_ACCESSIBILITY_JS);
  282. html_source->SetDefaultResource(IDR_ACCESSIBILITY_HTML);
  283. html_source->SetRequestFilter(
  284. base::BindRepeating(&ShouldHandleAccessibilityRequestCallback),
  285. base::BindRepeating(&HandleAccessibilityRequestCallback,
  286. web_ui->GetWebContents()->GetBrowserContext()));
  287. web_ui->AddMessageHandler(
  288. std::make_unique<ElectronAccessibilityUIMessageHandler>());
  289. }
  290. ElectronAccessibilityUI::~ElectronAccessibilityUI() = default;
  291. ElectronAccessibilityUIMessageHandler::ElectronAccessibilityUIMessageHandler() =
  292. default;
  293. void ElectronAccessibilityUIMessageHandler::RequestNativeUITree(
  294. const base::Value::List& args) {
  295. const base::Value::Dict& data = args.front().GetDict();
  296. const int window_id = *data.FindInt(kSessionIdField);
  297. const std::string* const request_type_p = data.FindString(kRequestTypeField);
  298. CHECK(IsValidJSValue(request_type_p));
  299. std::string request_type = *request_type_p;
  300. CHECK(request_type == kShowOrRefreshTree || request_type == kCopyTree);
  301. request_type = "accessibility." + request_type;
  302. const std::string* const allow_p =
  303. data.FindStringByDottedPath("filters.allow");
  304. CHECK(IsValidJSValue(allow_p));
  305. const std::string* const allow_empty_p =
  306. data.FindStringByDottedPath("filters.allowEmpty");
  307. CHECK(IsValidJSValue(allow_empty_p));
  308. const std::string* const deny_p = data.FindStringByDottedPath("filters.deny");
  309. CHECK(IsValidJSValue(deny_p));
  310. AllowJavascript();
  311. std::vector<ui::AXPropertyFilter> property_filters;
  312. AddPropertyFilters(&property_filters, *allow_p, ui::AXPropertyFilter::ALLOW);
  313. AddPropertyFilters(&property_filters, *allow_empty_p,
  314. ui::AXPropertyFilter::ALLOW_EMPTY);
  315. AddPropertyFilters(&property_filters, *deny_p, ui::AXPropertyFilter::DENY);
  316. for (auto* window : electron::WindowList::GetWindows()) {
  317. if (window->window_id() == window_id) {
  318. base::Value::Dict result = BuildTargetDescriptor(window);
  319. gfx::NativeWindow native_window = window->GetNativeWindow();
  320. ui::AXPlatformNode* node =
  321. ui::AXPlatformNode::FromNativeWindow(native_window);
  322. result.Set(kTreeField, base::Value(RecursiveDumpAXPlatformNodeAsString(
  323. node, 0, property_filters)));
  324. CallJavascriptFunction(request_type, base::Value(std::move(result)));
  325. return;
  326. }
  327. }
  328. // No browser with the specified |id| was found.
  329. base::Value::Dict result;
  330. result.Set(kSessionIdField, window_id);
  331. result.Set(kTypeField, kBrowser);
  332. result.Set(kErrorField, "Window no longer exists.");
  333. CallJavascriptFunction(request_type, base::Value(std::move(result)));
  334. }
  335. void ElectronAccessibilityUIMessageHandler::RegisterMessages() {
  336. DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  337. web_ui()->RegisterMessageCallback(
  338. "toggleAccessibility",
  339. base::BindRepeating(&AccessibilityUIMessageHandler::ToggleAccessibility,
  340. base::Unretained(this)));
  341. web_ui()->RegisterMessageCallback(
  342. "setGlobalFlag",
  343. base::BindRepeating(&AccessibilityUIMessageHandler::SetGlobalFlag,
  344. base::Unretained(this)));
  345. web_ui()->RegisterMessageCallback(
  346. "requestWebContentsTree",
  347. base::BindRepeating(
  348. &AccessibilityUIMessageHandler::RequestWebContentsTree,
  349. base::Unretained(this)));
  350. web_ui()->RegisterMessageCallback(
  351. "requestNativeUITree",
  352. base::BindRepeating(
  353. &ElectronAccessibilityUIMessageHandler::RequestNativeUITree,
  354. base::Unretained(this)));
  355. web_ui()->RegisterMessageCallback(
  356. "requestAccessibilityEvents",
  357. base::BindRepeating(
  358. &AccessibilityUIMessageHandler::RequestAccessibilityEvents,
  359. base::Unretained(this)));
  360. }