[chrome:accessibility] Display a list of accessibility events fired on a page.
Use the accessibility event recorder to capture events fired on a page, and
display the log on chrome:accessibility. This log isn't particularly
meaningful for now, but follow-up CLs will make this be more useful.
Bug: 785493
Change-Id: I5bf88bc070ac7df322cb41847a86642cbf038483
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1823617
Commit-Queue: Abigail Klein <[email protected]>
Reviewed-by: John Abd-El-Malek <[email protected]>
Reviewed-by: Dominic Mazzoni <[email protected]>
Cr-Commit-Position: refs/heads/master@{#711608}
diff --git a/chrome/browser/accessibility/accessibility_ui.cc b/chrome/browser/accessibility/accessibility_ui.cc
index 6bdee07..18b239678 100644
--- a/chrome/browser/accessibility/accessibility_ui.cc
+++ b/chrome/browser/accessibility/accessibility_ui.cc
@@ -54,6 +54,7 @@
static const char kBrowsersField[] = "browsers";
static const char kEnabledField[] = "enabled";
static const char kErrorField[] = "error";
+static const char kEventLogsField[] = "eventLogs";
static const char kFaviconUrlField[] = "faviconUrl";
static const char kFlagNameField[] = "flagName";
static const char kModeIdField[] = "modeId";
@@ -62,9 +63,11 @@
static const char kPidField[] = "pid";
static const char kProcessIdField[] = "processId";
static const char kRequestTypeField[] = "requestType";
+// TODO rename to routingId to match the name elsewhere.
static const char kRouteIdField[] = "routeId";
static const char kSessionIdField[] = "sessionId";
static const char kShouldRequestTreeField[] = "shouldRequestTree";
+static const char kStartField[] = "start";
static const char kTreeField[] = "tree";
static const char kTypeField[] = "type";
static const char kUrlField[] = "url";
@@ -372,6 +375,11 @@
"requestNativeUITree",
base::BindRepeating(&AccessibilityUIMessageHandler::RequestNativeUITree,
base::Unretained(this)));
+ web_ui()->RegisterMessageCallback(
+ "requestAccessibilityEvents",
+ base::BindRepeating(
+ &AccessibilityUIMessageHandler::RequestAccessibilityEvents,
+ base::Unretained(this)));
}
void AccessibilityUIMessageHandler::ToggleAccessibility(
@@ -613,6 +621,52 @@
CallJavascriptFunction(request_type, *(result.get()));
}
+void AccessibilityUIMessageHandler::Callback(const std::string& str) {
+ event_logs_.push_back(str);
+}
+
+void AccessibilityUIMessageHandler::RequestAccessibilityEvents(
+ const base::ListValue* args) {
+ const base::DictionaryValue* data;
+ CHECK(args->GetDictionary(0, &data));
+
+ int process_id = *data->FindIntPath(kProcessIdField);
+ int route_id = *data->FindIntPath(kRouteIdField);
+ bool start = *data->FindBoolPath(kStartField);
+
+ AllowJavascript();
+
+ content::RenderViewHost* rvh =
+ content::RenderViewHost::FromID(process_id, route_id);
+ if (!rvh) {
+ return;
+ }
+
+ std::unique_ptr<base::DictionaryValue> result(BuildTargetDescriptor(rvh));
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderViewHost(rvh);
+ if (start) {
+ web_contents->RecordAccessibilityEvents(
+ base::BindRepeating(&AccessibilityUIMessageHandler::Callback,
+ base::Unretained(this)),
+ true);
+ } else {
+ web_contents->RecordAccessibilityEvents(
+ base::BindRepeating(&AccessibilityUIMessageHandler::Callback,
+ base::Unretained(this)),
+ false);
+ std::string event_logs_str;
+ for (std::string log : event_logs_) {
+ event_logs_str += log;
+ event_logs_str += "\n";
+ }
+ result->SetString(kEventLogsField, event_logs_str);
+ event_logs_.clear();
+
+ CallJavascriptFunction("accessibility.startOrStopEvents", *(result.get()));
+ }
+}
+
// static
void AccessibilityUIMessageHandler::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
diff --git a/chrome/browser/accessibility/accessibility_ui.h b/chrome/browser/accessibility/accessibility_ui.h
index 8a2f32e..545dffe6 100644
--- a/chrome/browser/accessibility/accessibility_ui.h
+++ b/chrome/browser/accessibility/accessibility_ui.h
@@ -34,10 +34,14 @@
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
private:
+ std::vector<std::string> event_logs_;
+
void ToggleAccessibility(const base::ListValue* args);
void SetGlobalFlag(const base::ListValue* args);
void RequestWebContentsTree(const base::ListValue* args);
void RequestNativeUITree(const base::ListValue* args);
+ void RequestAccessibilityEvents(const base::ListValue* args);
+ void Callback(const std::string&);
DISALLOW_COPY_AND_ASSIGN(AccessibilityUIMessageHandler);
};
diff --git a/chrome/browser/resources/accessibility/accessibility.js b/chrome/browser/resources/accessibility/accessibility.js
index 7db0aaf..31a6377 100644
--- a/chrome/browser/resources/accessibility/accessibility.js
+++ b/chrome/browser/resources/accessibility/accessibility.js
@@ -100,6 +100,25 @@
}
}
+ function requestEvents(data, element) {
+ const start = element.textContent == 'Start recording';
+ if (start) {
+ element.textContent = 'Stop recording';
+ element.setAttribute('aria-expanded', 'true');
+
+ // TODO Hide all other start recording elements. UI should reflect the
+ // fact that there can only be one accessibility recorder at once.
+ } else {
+ element.textContent = 'Start recording';
+ element.setAttribute('aria-expanded', 'false');
+
+ // TODO Show all start recording elements.
+ }
+ chrome.send('requestAccessibilityEvents', [
+ {'processId': data.processId, 'routeId': data.routeId, 'start': start}
+ ]);
+ }
+
function initialize() {
console.log('initialize');
const data = requestData();
@@ -178,7 +197,7 @@
function formatRow(row, data) {
if (!('url' in data)) {
if ('error' in data) {
- row.appendChild(createErrorMessageElement(data, row));
+ row.appendChild(createErrorMessageElement(data));
return;
}
}
@@ -206,14 +225,24 @@
row.appendChild(document.createTextNode(' | '));
- if ('tree' in data) {
- row.appendChild(createTreeButtons(data, row.id));
- } else {
- row.appendChild(createShowAccessibilityTreeElement(data, row.id, false));
+ const hasTree = 'tree' in data;
+ row.appendChild(createShowAccessibilityTreeElement(data, row.id, hasTree));
+ if (navigator.clipboard) {
row.appendChild(createCopyAccessibilityTreeElement(data, row.id));
- if ('error' in data) {
- row.appendChild(createErrorMessageElement(data, row));
- }
+ }
+ if (hasTree) {
+ row.appendChild(createHideAccessibilityTreeElement(row.id));
+ }
+ row.appendChild(
+ createStartStopAccessibilityEventRecordingElement(data, row.id));
+
+ if (hasTree) {
+ row.appendChild(createAccessibilityOutputElement(data, row.id, 'tree'));
+ } else if ('eventLogs' in data) {
+ row.appendChild(
+ createAccessibilityOutputElement(data, row.id, 'eventLogs'));
+ } else if ('error' in data) {
+ row.appendChild(createErrorMessageElement(data));
}
}
@@ -292,17 +321,6 @@
return link;
}
- function createTreeButtons(data, id) {
- const row = document.createElement('span');
- row.appendChild(createShowAccessibilityTreeElement(data, id, true));
- if (navigator.clipboard) {
- row.appendChild(createCopyAccessibilityTreeElement(data, id));
- }
- row.appendChild(createHideAccessibilityTreeElement(id));
- row.appendChild(createAccessibilityTreeElement(data, id));
- return row;
- }
-
function createShowAccessibilityTreeElement(data, id, opt_refresh) {
const show = document.createElement('button');
if (opt_refresh) {
@@ -344,6 +362,15 @@
return copy;
}
+ function createStartStopAccessibilityEventRecordingElement(data, id) {
+ const show = document.createElement('button');
+ show.textContent = 'Start recording';
+ show.id = id + ':startOrStopEvents';
+ show.setAttribute('aria-expanded', 'false');
+ show.addEventListener('click', requestEvents.bind(this, data, show));
+ return show;
+ }
+
function createErrorMessageElement(data) {
const errorMessageElement = document.createElement('div');
const errorMessage = data.error;
@@ -376,6 +403,19 @@
}
// Called from C++
+ function startOrStopEvents(data) {
+ const id = getIdFromData(data);
+ const row = $(id);
+ if (!row) {
+ return;
+ }
+
+ row.textContent = '';
+ formatRow(row, data);
+ $(id + ':startOrStopEvents').focus();
+ }
+
+ // Called from C++
function copyTree(data) {
const id = getIdFromData(data);
const row = $(id);
@@ -416,15 +456,14 @@
return row;
}
- function createAccessibilityTreeElement(data, id) {
- let treeElement = $(id + ':tree');
- if (treeElement) {
- treeElement.style.display = '';
- } else {
+ // type is either 'tree' or 'eventLogs'
+ function createAccessibilityOutputElement(data, id, type) {
+ let treeElement = $(id + ':' + type);
+ if (!treeElement) {
treeElement = document.createElement('pre');
- treeElement.id = id + ':tree';
+ treeElement.id = id + ':' + type;
}
- treeElement.textContent = data.tree;
+ treeElement.textContent = data[type];
return treeElement;
}
@@ -432,7 +471,8 @@
return {
copyTree: copyTree,
initialize: initialize,
- showOrRefreshTree: showOrRefreshTree
+ showOrRefreshTree: showOrRefreshTree,
+ startOrStopEvents: startOrStopEvents
};
});
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 5d1e281..dcd4853 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -306,6 +306,12 @@
"../common/service_manager/child_connection.h",
"about_url_loader_factory.cc",
"about_url_loader_factory.h",
+ "accessibility/accessibility_event_recorder.cc",
+ "accessibility/accessibility_event_recorder.h",
+ "accessibility/accessibility_event_recorder_mac.mm",
+ "accessibility/accessibility_event_recorder_uia_win.cc",
+ "accessibility/accessibility_event_recorder_uia_win.h",
+ "accessibility/accessibility_event_recorder_win.cc",
"accessibility/accessibility_tree_formatter_base.cc",
"accessibility/accessibility_tree_formatter_base.h",
"accessibility/accessibility_tree_formatter_blink.cc",
@@ -2279,6 +2285,7 @@
if (use_atk) {
sources += [
+ "accessibility/accessibility_event_recorder_auralinux.cc",
"accessibility/accessibility_tree_formatter_auralinux.cc",
"accessibility/accessibility_tree_formatter_utils_auralinux.cc",
"accessibility/accessibility_tree_formatter_utils_auralinux.h",
diff --git a/content/browser/accessibility/accessibility_event_recorder.h b/content/browser/accessibility/accessibility_event_recorder.h
index d9ee28c..6a9adf7 100644
--- a/content/browser/accessibility/accessibility_event_recorder.h
+++ b/content/browser/accessibility/accessibility_event_recorder.h
@@ -12,6 +12,7 @@
#include "base/callback.h"
#include "base/macros.h"
#include "base/process/process_handle.h"
+#include "content/common/content_export.h"
namespace content {
@@ -35,7 +36,7 @@
// each platform does most of the work.
//
// As currently designed, there should only be one instance of this class.
-class AccessibilityEventRecorder {
+class CONTENT_EXPORT AccessibilityEventRecorder {
public:
// Construct the right platform-specific subclass.
static std::unique_ptr<AccessibilityEventRecorder> Create(
diff --git a/content/browser/accessibility/accessibility_event_recorder_auralinux.cc b/content/browser/accessibility/accessibility_event_recorder_auralinux.cc
index 76678e7..bbb3320 100644
--- a/content/browser/accessibility/accessibility_event_recorder_auralinux.cc
+++ b/content/browser/accessibility/accessibility_event_recorder_auralinux.cc
@@ -63,7 +63,6 @@
AtspiEventListener* atspi_event_listener_ = nullptr;
base::ProcessId pid_;
base::StringPiece application_name_match_pattern_;
- static std::vector<unsigned int> atk_listener_ids_;
static AccessibilityEventRecorderAuraLinux* instance_;
DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderAuraLinux);
@@ -72,9 +71,12 @@
// static
AccessibilityEventRecorderAuraLinux*
AccessibilityEventRecorderAuraLinux::instance_ = nullptr;
-std::vector<unsigned int>
- content::AccessibilityEventRecorderAuraLinux::atk_listener_ids_ =
- std::vector<unsigned int>();
+
+// static
+std::vector<unsigned int>& GetATKListenerIds() {
+ static base::NoDestructor<std::vector<unsigned int>> atk_listener_ids;
+ return *atk_listener_ids;
+}
// static
gboolean AccessibilityEventRecorderAuraLinux::OnATKEventReceived(
@@ -145,11 +147,12 @@
if (!id)
LOG(FATAL) << "atk_add_global_event_listener failed for " << event_name;
- atk_listener_ids_.push_back(id);
+ std::vector<unsigned int>& atk_listener_ids = GetATKListenerIds();
+ atk_listener_ids.push_back(id);
}
void AccessibilityEventRecorderAuraLinux::AddATKEventListeners() {
- if (atk_listener_ids_.size() >= 1)
+ if (GetATKListenerIds().size() >= 1)
return;
GObject* gobject = G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr, nullptr));
g_object_unref(atk_no_op_object_new(gobject));
@@ -165,10 +168,11 @@
}
void AccessibilityEventRecorderAuraLinux::RemoveATKEventListeners() {
- for (const auto& id : atk_listener_ids_)
+ std::vector<unsigned int>& atk_listener_ids = GetATKListenerIds();
+ for (const auto& id : atk_listener_ids)
atk_remove_global_event_listener(id);
- atk_listener_ids_.clear();
+ atk_listener_ids.clear();
}
// Pruning states which are not supported on older bots makes it possible to
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index dafbbbc..c960b2b 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -41,6 +41,7 @@
#include "components/download/public/common/download_stats.h"
#include "components/rappor/public/rappor_utils.h"
#include "components/url_formatter/url_formatter.h"
+#include "content/browser/accessibility/accessibility_event_recorder.h"
#include "content/browser/accessibility/accessibility_tree_formatter_blink.h"
#include "content/browser/bad_message.h"
#include "content/browser/browser_main_loop.h"
@@ -186,6 +187,10 @@
#endif
namespace content {
+
+using AccessibilityEventCallback =
+ base::RepeatingCallback<void(const std::string&)>;
+
namespace {
const int kMinimumDelayBetweenLoadingUpdatesMS = 100;
@@ -3254,6 +3259,24 @@
ax_mgr, internal, property_filters);
}
+void WebContentsImpl::RecordAccessibilityEvents(
+ AccessibilityEventCallback callback,
+ bool start) {
+ if (start) {
+ SetAccessibilityMode(ui::AXMode::kWebContents);
+ auto* ax_mgr = GetOrCreateRootBrowserAccessibilityManager();
+ DCHECK(ax_mgr);
+ base::ProcessId pid = base::Process::Current().Pid();
+ event_recorder_ = content::AccessibilityEventRecorder::Create(
+ ax_mgr, pid, base::StringPiece{});
+ event_recorder_->ListenToEvents(callback);
+ } else {
+ DCHECK(event_recorder_);
+ event_recorder_->FlushAsyncEvents();
+ event_recorder_ = nullptr;
+ }
+}
+
RenderFrameHost* WebContentsImpl::GetGuestByInstanceID(
RenderFrameHost* render_frame_host,
int browser_plugin_instance_id) {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 40f1bb1..8b8caa8 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -26,6 +26,7 @@
#include "base/values.h"
#include "build/build_config.h"
#include "components/download/public/common/download_url_parameters.h"
+#include "content/browser/accessibility/accessibility_event_recorder.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/interstitial_page_impl.h"
@@ -135,6 +136,9 @@
class PepperPlaybackObserver;
#endif
+using AccessibilityEventCallback =
+ base::RepeatingCallback<void(const std::string&)>;
+
// Factory function for the implementations that content knows about. Takes
// ownership of |delegate|.
WebContentsView* CreateWebContentsView(
@@ -564,6 +568,8 @@
bool internal,
std::vector<content::AccessibilityTreeFormatter::PropertyFilter>
property_filters) override;
+ void RecordAccessibilityEvents(AccessibilityEventCallback callback,
+ bool start) override;
RenderFrameHost* GetGuestByInstanceID(
RenderFrameHost* render_frame_host,
int browser_plugin_instance_id) override;
@@ -1792,6 +1798,8 @@
// is created, and broadcast to all frames when it changes.
ui::AXMode accessibility_mode_;
+ std::unique_ptr<content::AccessibilityEventRecorder> event_recorder_;
+
// Monitors power levels for audio streams associated with this WebContents.
AudioStreamMonitor audio_stream_monitor_;
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 075dd254..3b6ac0f 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -411,6 +411,18 @@
std::vector<content::AccessibilityTreeFormatter::PropertyFilter>
property_filters) = 0;
+ // A callback that takes a string which contains accessibility event
+ // information.
+ using AccessibilityEventCallback =
+ base::RepeatingCallback<void(const std::string&)>;
+
+ // Starts or stops recording accessibility events. While accessibility events
+ // are being recorded, the callback will be called when an accessibility
+ // event is received. The start paramater says whether the recording is
+ // starting or stopping.
+ virtual void RecordAccessibilityEvents(AccessibilityEventCallback callback,
+ bool start) = 0;
+
virtual const PageImportanceSignals& GetPageImportanceSignals() = 0;
// Tab navigation state ------------------------------------------------------
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 631d5697..9cf599b9 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -37,12 +37,6 @@
}
sources = [
- "../browser/accessibility/accessibility_event_recorder.cc",
- "../browser/accessibility/accessibility_event_recorder.h",
- "../browser/accessibility/accessibility_event_recorder_mac.mm",
- "../browser/accessibility/accessibility_event_recorder_uia_win.cc",
- "../browser/accessibility/accessibility_event_recorder_uia_win.h",
- "../browser/accessibility/accessibility_event_recorder_win.cc",
"../browser/accessibility/test_browser_accessibility_delegate.cc",
"../browser/accessibility/test_browser_accessibility_delegate.h",
"../browser/background_fetch/background_fetch_test_base.cc",
@@ -500,8 +494,6 @@
}
if (use_atk) {
- sources +=
- [ "../browser/accessibility/accessibility_event_recorder_auralinux.cc" ]
configs += [
"//build/config/linux/atk",
"//build/config/linux/atspi2",