// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/accessibility/accessibility_ui.h" #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/command_line.h" #include "base/json/json_writer.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "content/browser/accessibility/accessibility_tree_formatter.h" #include "content/browser/accessibility/accessibility_tree_formatter_blink.h" #include "content/browser/accessibility/browser_accessibility_manager.h" #include "content/browser/accessibility/browser_accessibility_state_impl.h" #include "content/browser/renderer_host/render_view_host_impl.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/renderer_host/render_widget_host_view_base.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/browser/webui/web_ui_data_source_impl.h" #include "content/common/view_message_enums.h" #include "content/grit/content_resources.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_ui_data_source.h" #include "content/public/common/content_switches.h" #include "content/public/common/url_constants.h" #include "net/base/escape.h" static const char kDataFile[] = "targets-data.json"; static const char kProcessIdField[] = "processId"; static const char kRouteIdField[] = "routeId"; static const char kUrlField[] = "url"; static const char kNameField[] = "name"; static const char kFaviconUrlField[] = "favicon_url"; static const char kPidField[] = "pid"; static const char kAccessibilityModeField[] = "a11y_mode"; // Global flags static const char kInternal[] = "internal"; static const char kNative[] = "native"; static const char kWeb[] = "web"; static const char kText[] = "text"; static const char kScreenReader[] = "screenreader"; static const char kHTML[] = "html"; // Possible global flag values static const char kOff[]= "off"; static const char kOn[] = "on"; static const char kDisabled[] = "disabled"; namespace content { namespace { bool g_show_internal_accessibility_tree = false; std::unique_ptr BuildTargetDescriptor( const GURL& url, const std::string& name, const GURL& favicon_url, int process_id, int route_id, AccessibilityMode accessibility_mode, base::ProcessHandle handle = base::kNullProcessHandle) { std::unique_ptr target_data( new base::DictionaryValue()); target_data->SetInteger(kProcessIdField, process_id); target_data->SetInteger(kRouteIdField, route_id); target_data->SetString(kUrlField, url.spec()); target_data->SetString(kNameField, net::EscapeForHTML(name)); target_data->SetInteger(kPidField, base::GetProcId(handle)); target_data->SetString(kFaviconUrlField, favicon_url.spec()); target_data->SetInteger(kAccessibilityModeField, accessibility_mode.mode()); return target_data; } std::unique_ptr BuildTargetDescriptor( RenderViewHost* rvh) { WebContentsImpl* web_contents = static_cast( WebContents::FromRenderViewHost(rvh)); AccessibilityMode accessibility_mode; std::string title; GURL url; GURL favicon_url; if (web_contents) { // TODO(nasko): Fix the following code to use a consistent set of data // across the URL, title, and favicon. url = web_contents->GetURL(); title = base::UTF16ToUTF8(web_contents->GetTitle()); NavigationController& controller = web_contents->GetController(); NavigationEntry* entry = controller.GetVisibleEntry(); if (entry != NULL && entry->GetURL().is_valid()) favicon_url = entry->GetFavicon().url; accessibility_mode = web_contents->GetAccessibilityMode(); } return BuildTargetDescriptor(url, title, favicon_url, rvh->GetProcess()->GetID(), rvh->GetRoutingID(), accessibility_mode); } bool HandleRequestCallback(BrowserContext* current_context, const std::string& path, const WebUIDataSource::GotDataCallback& callback) { if (path != kDataFile) return false; std::unique_ptr rvh_list(new base::ListValue()); std::unique_ptr widgets( RenderWidgetHost::GetRenderWidgetHosts()); while (RenderWidgetHost* widget = widgets->GetNextHost()) { // Ignore processes that don't have a connection, such as crashed tabs. if (!widget->GetProcess()->HasConnection()) continue; RenderViewHost* rvh = RenderViewHost::From(widget); if (!rvh) continue; // Ignore views that are never visible, like background pages. if (static_cast(rvh)->GetDelegate()->IsNeverVisible()) continue; BrowserContext* context = rvh->GetProcess()->GetBrowserContext(); if (context != current_context) continue; rvh_list->Append(BuildTargetDescriptor(rvh)); } base::DictionaryValue data; data.Set("list", rvh_list.release()); AccessibilityMode mode = BrowserAccessibilityStateImpl::GetInstance()->accessibility_mode(); bool disabled = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableRendererAccessibility); bool native = mode.has_mode(AccessibilityMode::kNativeAPIs); bool web = mode.has_mode(AccessibilityMode::kWebContents); bool text = mode.has_mode(AccessibilityMode::kInlineTextBoxes); bool screenreader = mode.has_mode(AccessibilityMode::kScreenReader); bool html = mode.has_mode(AccessibilityMode::kHTML); // The "native" and "web" flags are disabled if // --disable-renderer-accessibility is set. data.SetString(kNative, disabled ? kDisabled : (native ? kOn : kOff)); data.SetString(kWeb, disabled ? kDisabled : (web ? kOn : kOff)); // The "text", "screenreader", and "html" flags are only meaningful if // "web" is enabled. data.SetString(kText, web ? (text ? kOn : kOff) : kDisabled); data.SetString(kScreenReader, web ? (screenreader ? kOn : kOff) : kDisabled); data.SetString(kHTML, web ? (html ? kOn : kOff) : kDisabled); data.SetString(kInternal, g_show_internal_accessibility_tree ? kOn : kOff); std::string json_string; base::JSONWriter::Write(data, &json_string); callback.Run(base::RefCountedString::TakeString(&json_string)); return true; } } // namespace AccessibilityUI::AccessibilityUI(WebUI* web_ui) : WebUIController(web_ui) { // Set up the chrome://accessibility source. WebUIDataSourceImpl* html_source = static_cast( WebUIDataSource::Create(kChromeUIAccessibilityHost)); web_ui->RegisterMessageCallback( "toggleAccessibility", base::Bind(&AccessibilityUI::ToggleAccessibility, base::Unretained(this))); web_ui->RegisterMessageCallback( "setGlobalFlag", base::Bind(&AccessibilityUI::SetGlobalFlag, base::Unretained(this))); web_ui->RegisterMessageCallback( "requestAccessibilityTree", base::Bind(&AccessibilityUI::RequestAccessibilityTree, base::Unretained(this))); // Add required resources. html_source->SetJsonPath("strings.js"); 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::Bind(&HandleRequestCallback, web_ui->GetWebContents()->GetBrowserContext())); std::unordered_set exclude_from_gzip; exclude_from_gzip.insert(kDataFile); html_source->UseGzip(exclude_from_gzip); BrowserContext* browser_context = web_ui->GetWebContents()->GetBrowserContext(); WebUIDataSource::Add(browser_context, html_source); } AccessibilityUI::~AccessibilityUI() {} void AccessibilityUI::ToggleAccessibility(const base::ListValue* args) { std::string process_id_str; std::string route_id_str; int process_id; int route_id; int mode; CHECK_EQ(3U, args->GetSize()); CHECK(args->GetString(0, &process_id_str)); CHECK(args->GetString(1, &route_id_str)); // TODO(695247): We should pass each ax flag seperately CHECK(args->GetInteger(2, &mode)); CHECK(base::StringToInt(process_id_str, &process_id)); CHECK(base::StringToInt(route_id_str, &route_id)); RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); if (!rvh) return; auto* web_contents = static_cast(WebContents::FromRenderViewHost(rvh)); AccessibilityMode current_mode = web_contents->GetAccessibilityMode(); if (mode & AccessibilityMode::kNativeAPIs) current_mode.set_mode(AccessibilityMode::kNativeAPIs, true); if (mode & AccessibilityMode::kWebContents) current_mode.set_mode(AccessibilityMode::kWebContents, true); if (mode & AccessibilityMode::kInlineTextBoxes) current_mode.set_mode(AccessibilityMode::kInlineTextBoxes, true); if (mode & AccessibilityMode::kScreenReader) current_mode.set_mode(AccessibilityMode::kScreenReader, true); if (mode & AccessibilityMode::kHTML) current_mode.set_mode(AccessibilityMode::kHTML, true); web_contents->SetAccessibilityMode(current_mode); } void AccessibilityUI::SetGlobalFlag(const base::ListValue* args) { std::string flag_name_str; bool enabled; CHECK_EQ(2U, args->GetSize()); CHECK(args->GetString(0, &flag_name_str)); CHECK(args->GetBoolean(1, &enabled)); if (flag_name_str == kInternal) { g_show_internal_accessibility_tree = enabled; LOG(ERROR) << "INTERNAL: " << g_show_internal_accessibility_tree; return; } AccessibilityMode new_mode; if (flag_name_str == kNative) { new_mode = AccessibilityMode::kNativeAPIs; } else if (flag_name_str == kWeb) { new_mode = AccessibilityMode::kWebContents; } else if (flag_name_str == kText) { new_mode = AccessibilityMode::kInlineTextBoxes; } else if (flag_name_str == kScreenReader) { new_mode = AccessibilityMode::kScreenReader; } else if (flag_name_str == kHTML) { new_mode = AccessibilityMode::kHTML; } else { NOTREACHED(); return; } // It doesn't make sense to enable one of the flags that depends on // web contents without enabling web contents accessibility too. if (enabled && (new_mode.has_mode(AccessibilityMode::kInlineTextBoxes) || new_mode.has_mode(AccessibilityMode::kScreenReader) || new_mode.has_mode(AccessibilityMode::kHTML))) { new_mode.set_mode(AccessibilityMode::kWebContents, true); } // Similarly if you disable web accessibility we should remove all // flags that depend on it. if (!enabled && new_mode.has_mode(AccessibilityMode::kWebContents)) { new_mode.set_mode(AccessibilityMode::kInlineTextBoxes, true); new_mode.set_mode(AccessibilityMode::kScreenReader, true); new_mode.set_mode(AccessibilityMode::kHTML, true); } BrowserAccessibilityStateImpl* state = BrowserAccessibilityStateImpl::GetInstance(); if (enabled) state->AddAccessibilityModeFlags(new_mode); else state->RemoveAccessibilityModeFlags(new_mode); } void AccessibilityUI::RequestAccessibilityTree(const base::ListValue* args) { std::string process_id_str; std::string route_id_str; int process_id; int route_id; CHECK_EQ(2U, args->GetSize()); CHECK(args->GetString(0, &process_id_str)); CHECK(args->GetString(1, &route_id_str)); CHECK(base::StringToInt(process_id_str, &process_id)); CHECK(base::StringToInt(route_id_str, &route_id)); RenderViewHost* rvh = RenderViewHost::FromID(process_id, route_id); if (!rvh) { std::unique_ptr result(new base::DictionaryValue()); result->SetInteger(kProcessIdField, process_id); result->SetInteger(kRouteIdField, route_id); result->Set("error", new base::Value("Renderer no longer exists.")); web_ui()->CallJavascriptFunctionUnsafe("accessibility.showTree", *(result.get())); return; } std::unique_ptr result(BuildTargetDescriptor(rvh)); auto* web_contents = static_cast(WebContents::FromRenderViewHost(rvh)); // No matter the state of the current web_contents, we want to force the mode // because we are about to show the accessibility tree web_contents->SetAccessibilityMode(AccessibilityMode( AccessibilityMode::kNativeAPIs | AccessibilityMode::kWebContents)); std::unique_ptr formatter; if (g_show_internal_accessibility_tree) formatter.reset(new AccessibilityTreeFormatterBlink()); else formatter.reset(AccessibilityTreeFormatter::Create()); base::string16 accessibility_contents_utf16; std::vector filters; filters.push_back(AccessibilityTreeFormatter::Filter( base::ASCIIToUTF16("*"), AccessibilityTreeFormatter::Filter::ALLOW)); formatter->SetFilters(filters); auto* ax_mgr = web_contents->GetOrCreateRootBrowserAccessibilityManager(); DCHECK(ax_mgr); formatter->FormatAccessibilityTree(ax_mgr->GetRoot(), &accessibility_contents_utf16); result->Set("tree", new base::Value(base::UTF16ToUTF8(accessibility_contents_utf16))); web_ui()->CallJavascriptFunctionUnsafe("accessibility.showTree", *(result.get())); } } // namespace content