1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/accessibility/accessibility_event_recorder.h"
6 
7 #include <atk/atk.h>
8 #include <atk/atkutil.h>
9 #include <atspi/atspi.h>
10 
11 #include "base/process/process_handle.h"
12 #include "base/stl_util.h"
13 #include "base/strings/pattern.h"
14 #include "base/strings/stringprintf.h"
15 #include "content/browser/accessibility/accessibility_tree_formatter_utils_auralinux.h"
16 #include "content/browser/accessibility/browser_accessibility_auralinux.h"
17 #include "content/browser/accessibility/browser_accessibility_manager.h"
18 
19 #if defined(ATK_CHECK_VERSION) && ATK_CHECK_VERSION(2, 16, 0)
20 #define ATK_216
21 #endif
22 
23 namespace content {
24 
25 // This class has two distinct event recording code paths. When we are
26 // recording events in-process (typically this is used for
27 // DumpAccessibilityEvents tests), we use ATK's global event handlers. Since
28 // ATK doesn't support intercepting events from other processes, if we have a
29 // non-zero PID or an accessibility application name pattern, we use AT-SPI2
30 // directly to intercept events. Since AT-SPI2 should be capable of
31 // intercepting events in-process as well, eventually it would be nice to
32 // remove the ATK code path entirely.
33 class AccessibilityEventRecorderAuraLinux : public AccessibilityEventRecorder {
34  public:
35   explicit AccessibilityEventRecorderAuraLinux(
36       BrowserAccessibilityManager* manager,
37       base::ProcessId pid,
38       const AXTreeSelector& selector);
39   ~AccessibilityEventRecorderAuraLinux() override;
40 
41   void ProcessATKEvent(const char* event,
42                        unsigned int n_params,
43                        const GValue* params);
44   void ProcessATSPIEvent(const AtspiEvent* event);
45 
46   static gboolean OnATKEventReceived(GSignalInvocationHint* hint,
47                                      unsigned int n_params,
48                                      const GValue* params,
49                                      gpointer data);
50 
51  private:
52   bool ShouldUseATSPI();
53 
54   std::string AtkObjectToString(AtkObject* obj, bool include_name);
55   void AddATKEventListener(const char* event_name);
56   void AddATKEventListeners();
57   void RemoveATKEventListeners();
58   bool IncludeState(AtkStateType state_type);
59 
60   void AddATSPIEventListeners();
61   void RemoveATSPIEventListeners();
62 
63   AtspiEventListener* atspi_event_listener_ = nullptr;
64   base::ProcessId pid_;
65   base::StringPiece application_name_match_pattern_;
66   static AccessibilityEventRecorderAuraLinux* instance_;
67 
68   DISALLOW_COPY_AND_ASSIGN(AccessibilityEventRecorderAuraLinux);
69 };
70 
71 // static
72 AccessibilityEventRecorderAuraLinux*
73     AccessibilityEventRecorderAuraLinux::instance_ = nullptr;
74 
75 // static
GetATKListenerIds()76 std::vector<unsigned int>& GetATKListenerIds() {
77   static base::NoDestructor<std::vector<unsigned int>> atk_listener_ids;
78   return *atk_listener_ids;
79 }
80 
81 // static
OnATKEventReceived(GSignalInvocationHint * hint,unsigned int n_params,const GValue * params,gpointer data)82 gboolean AccessibilityEventRecorderAuraLinux::OnATKEventReceived(
83     GSignalInvocationHint* hint,
84     unsigned int n_params,
85     const GValue* params,
86     gpointer data) {
87   GSignalQuery query;
88   g_signal_query(hint->signal_id, &query);
89 
90   if (instance_) {
91     // "add" and "remove" are details; not part of the signal name itself.
92     gchar* signal_name =
93         g_strcmp0(query.signal_name, "children-changed")
94             ? g_strdup(query.signal_name)
95             : g_strconcat(query.signal_name, ":",
96                           g_quark_to_string(hint->detail), nullptr);
97     instance_->ProcessATKEvent(signal_name, n_params, params);
98     g_free(signal_name);
99   }
100   return true;
101 }
102 
103 // static
Create(BrowserAccessibilityManager * manager,base::ProcessId pid,const AXTreeSelector & selector)104 std::unique_ptr<AccessibilityEventRecorder> AccessibilityEventRecorder::Create(
105     BrowserAccessibilityManager* manager,
106     base::ProcessId pid,
107     const AXTreeSelector& selector) {
108   return std::make_unique<AccessibilityEventRecorderAuraLinux>(manager, pid,
109                                                                selector);
110 }
111 
112 std::vector<AccessibilityEventRecorder::TestPass>
GetTestPasses()113 AccessibilityEventRecorder::GetTestPasses() {
114   // Both the Blink pass and native pass use the same recorder
115   return {
116       {"blink", &AccessibilityEventRecorder::Create},
117       {"linux", &AccessibilityEventRecorder::Create},
118   };
119 }
120 
ShouldUseATSPI()121 bool AccessibilityEventRecorderAuraLinux::ShouldUseATSPI() {
122   return pid_ != base::GetCurrentProcId() ||
123          !application_name_match_pattern_.empty();
124 }
125 
AccessibilityEventRecorderAuraLinux(BrowserAccessibilityManager * manager,base::ProcessId pid,const AXTreeSelector & selector)126 AccessibilityEventRecorderAuraLinux::AccessibilityEventRecorderAuraLinux(
127     BrowserAccessibilityManager* manager,
128     base::ProcessId pid,
129     const AXTreeSelector& selector)
130     : AccessibilityEventRecorder(manager),
131       pid_(pid),
132       application_name_match_pattern_(selector.pattern) {
133   CHECK(!instance_) << "There can be only one instance of"
134                     << " AccessibilityEventRecorder at a time.";
135 
136   if (ShouldUseATSPI()) {
137     AddATSPIEventListeners();
138   } else {
139     AddATKEventListeners();
140   }
141 
142   instance_ = this;
143 }
144 
~AccessibilityEventRecorderAuraLinux()145 AccessibilityEventRecorderAuraLinux::~AccessibilityEventRecorderAuraLinux() {
146   RemoveATSPIEventListeners();
147   instance_ = nullptr;
148 }
149 
AddATKEventListener(const char * event_name)150 void AccessibilityEventRecorderAuraLinux::AddATKEventListener(
151     const char* event_name) {
152   unsigned id = atk_add_global_event_listener(OnATKEventReceived, event_name);
153   if (!id)
154     LOG(FATAL) << "atk_add_global_event_listener failed for " << event_name;
155 
156   std::vector<unsigned int>& atk_listener_ids = GetATKListenerIds();
157   atk_listener_ids.push_back(id);
158 }
159 
AddATKEventListeners()160 void AccessibilityEventRecorderAuraLinux::AddATKEventListeners() {
161   if (GetATKListenerIds().size() >= 1)
162     return;
163   GObject* gobject = G_OBJECT(g_object_new(G_TYPE_OBJECT, nullptr, nullptr));
164   g_object_unref(atk_no_op_object_new(gobject));
165   g_object_unref(gobject);
166 
167   AddATKEventListener("ATK:AtkObject:state-change");
168   AddATKEventListener("ATK:AtkObject:focus-event");
169   AddATKEventListener("ATK:AtkObject:property-change");
170   AddATKEventListener("ATK:AtkObject:children-changed");
171   AddATKEventListener("ATK:AtkText:text-insert");
172   AddATKEventListener("ATK:AtkText:text-remove");
173   AddATKEventListener("ATK:AtkText:text-selection-changed");
174   AddATKEventListener("ATK:AtkText:text-caret-moved");
175   AddATKEventListener("ATK:AtkText:text-attributes-changed");
176   AddATKEventListener("ATK:AtkSelection:selection-changed");
177   AddATKEventListener("ATK:AtkTable:column-reordered");
178   AddATKEventListener("ATK:AtkTable:row-reordered");
179 }
180 
RemoveATKEventListeners()181 void AccessibilityEventRecorderAuraLinux::RemoveATKEventListeners() {
182   std::vector<unsigned int>& atk_listener_ids = GetATKListenerIds();
183   for (const auto& id : atk_listener_ids)
184     atk_remove_global_event_listener(id);
185 
186   atk_listener_ids.clear();
187 }
188 
189 // Pruning states which are not supported on older bots makes it possible to
190 // run the events tests in more environments.
IncludeState(AtkStateType state_type)191 bool AccessibilityEventRecorderAuraLinux::IncludeState(
192     AtkStateType state_type) {
193   switch (state_type) {
194 #if defined(ATK_216)
195     case ATK_STATE_CHECKABLE:
196     case ATK_STATE_HAS_POPUP:
197     case ATK_STATE_READ_ONLY:
198       return false;
199 #endif
200     case ATK_STATE_LAST_DEFINED:
201       return false;
202     default:
203       return true;
204   }
205 }
206 
AtkObjectToString(AtkObject * obj,bool include_name)207 std::string AccessibilityEventRecorderAuraLinux::AtkObjectToString(
208     AtkObject* obj,
209     bool include_name) {
210   std::string role = AtkRoleToString(atk_object_get_role(obj));
211   base::ReplaceChars(role, " ", "_", &role);
212   std::string str =
213       base::StringPrintf("role=ROLE_%s", base::ToUpperASCII(role).c_str());
214   // Getting the name breaks firing of name-change events. Allow disabling of
215   // logging the name in those situations.
216   if (include_name)
217     str += base::StringPrintf(" name='%s'", atk_object_get_name(obj));
218   return str;
219 }
220 
ProcessATKEvent(const char * event,unsigned int n_params,const GValue * params)221 void AccessibilityEventRecorderAuraLinux::ProcessATKEvent(
222     const char* event,
223     unsigned int n_params,
224     const GValue* params) {
225   // If we don't have a root object, it means the tree is being destroyed.
226   if (!manager_->GetRoot()) {
227     RemoveATKEventListeners();
228     return;
229   }
230 
231   bool log_name = true;
232   std::string event_name(event);
233   std::string log;
234   if (event_name.find("property-change") != std::string::npos) {
235     DCHECK_GE(n_params, 2u);
236     AtkPropertyValues* property_values =
237         static_cast<AtkPropertyValues*>(g_value_get_pointer(&params[1]));
238 
239     if (g_strcmp0(property_values->property_name, "accessible-value") == 0) {
240       log += "VALUE-CHANGED:";
241       log +=
242           base::NumberToString(g_value_get_double(&property_values->new_value));
243     } else if (g_strcmp0(property_values->property_name, "accessible-name") ==
244                0) {
245       const char* new_name = g_value_get_string(&property_values->new_value);
246       log += "NAME-CHANGED:";
247       log += (new_name) ? new_name : "(null)";
248     } else if (g_strcmp0(property_values->property_name,
249                          "accessible-description") == 0) {
250       const char* new_description =
251           g_value_get_string(&property_values->new_value);
252       log += "DESCRIPTION-CHANGED:";
253       log += (new_description) ? new_description : "(null)";
254     } else if (g_strcmp0(property_values->property_name, "accessible-parent") ==
255                0) {
256       log += "PARENT-CHANGED";
257       if (AtkObject* new_parent = static_cast<AtkObject*>(
258               g_value_get_object(&property_values->new_value)))
259         log += " PARENT:(" + AtkObjectToString(new_parent, log_name) + ")";
260     } else {
261       return;
262     }
263   } else if (event_name.find("children-changed") != std::string::npos) {
264     log_name = false;
265     log += base::ToUpperASCII(event);
266     // Despite this actually being a signed integer, it's defined as a uint.
267     int index = static_cast<int>(g_value_get_uint(&params[1]));
268     log += base::StringPrintf(" index:%d", index);
269     AtkObject* child = static_cast<AtkObject*>(g_value_get_pointer(&params[2]));
270     if (child)
271       log += " CHILD:(" + AtkObjectToString(child, log_name) + ")";
272     else
273       log += " CHILD:(NULL)";
274   } else if (event_name.find("focus-event") != std::string::npos) {
275     log += base::ToUpperASCII(event);
276     gchar* parameter = g_strdup_value_contents(&params[1]);
277     log += base::StringPrintf(":%s", parameter);
278     g_free(parameter);
279   } else {
280     log += base::ToUpperASCII(event);
281     if (event_name.find("state-change") != std::string::npos) {
282       std::string state_type = g_value_get_string(&params[1]);
283       log += ":" + base::ToUpperASCII(state_type);
284 
285       gchar* parameter = g_strdup_value_contents(&params[2]);
286       log += base::StringPrintf(":%s", parameter);
287       g_free(parameter);
288 
289     } else if (event_name.find("text-insert") != std::string::npos ||
290                event_name.find("text-remove") != std::string::npos) {
291       DCHECK_GE(n_params, 4u);
292       log += base::StringPrintf(
293           " (start=%i length=%i '%s')", g_value_get_int(&params[1]),
294           g_value_get_int(&params[2]), g_value_get_string(&params[3]));
295     }
296   }
297 
298   AtkObject* obj = ATK_OBJECT(g_value_get_object(&params[0]));
299   log += " " + AtkObjectToString(obj, log_name);
300 
301   std::string states;
302   AtkStateSet* state_set = atk_object_ref_state_set(obj);
303   for (int i = ATK_STATE_INVALID; i < ATK_STATE_LAST_DEFINED; i++) {
304     AtkStateType state_type = static_cast<AtkStateType>(i);
305     if (atk_state_set_contains_state(state_set, state_type) &&
306         IncludeState(state_type)) {
307       states += " " + base::ToUpperASCII(atk_state_type_get_name(state_type));
308     }
309   }
310   states = base::CollapseWhitespaceASCII(states, false);
311   base::ReplaceChars(states, " ", ",", &states);
312   log += base::StringPrintf(" %s", states.c_str());
313   g_object_unref(state_set);
314 
315   OnEvent(log);
316 }
317 
318 // This list is composed of the sorted event names taken from the list provided
319 // in the libatspi documentation at:
320 // https://developer.gnome.org/libatspi/stable/AtspiEventListener.html#atspi-event-listener-register
321 const char* const kEventNames[] = {
322     "object:active-descendant-changed",
323     "object:children-changed",
324     "object:column-deleted",
325     "object:column-inserted",
326     "object:column-reordered",
327     "object:model-changed",
328     "object:property-change",
329     "object:property-change:accessible-description",
330     "object:property-change:accessible-name",
331     "object:property-change:accessible-parent",
332     "object:property-change:accessible-role",
333     "object:property-change:accessible-table-caption",
334     "object:property-change:accessible-table-column-description",
335     "object:property-change:accessible-table-column-header",
336     "object:property-change:accessible-table-row-description",
337     "object:property-change:accessible-table-row-header",
338     "object:property-change:accessible-table-summary",
339     "object:property-change:accessible-value",
340     "object:row-deleted",
341     "object:row-inserted",
342     "object:row-reordered",
343     "object:selection-changed",
344     "object:state-changed",
345     "object:text-attributes-changed",
346     "object:text-caret-moved",
347     "object:text-changed",
348     "object:text-selection-changed",
349     "object:visible-data-changed",
350     "window:activate",
351     "window:close",
352     "window:create",
353     "window:deactivate",
354     "window:desktop-create",
355     "window:desktop-destroy",
356     "window:lower",
357     "window:maximize",
358     "window:minimize",
359     "window:move",
360     "window:raise",
361     "window:reparent",
362     "window:resize",
363     "window:restore",
364     "window:restyle",
365     "window:shade",
366     "window:unshade",
367 };
368 
OnATSPIEventReceived(AtspiEvent * event,void * data)369 static void OnATSPIEventReceived(AtspiEvent* event, void* data) {
370   static_cast<AccessibilityEventRecorderAuraLinux*>(data)->ProcessATSPIEvent(
371       event);
372   g_boxed_free(ATSPI_TYPE_EVENT, static_cast<void*>(event));
373 }
374 
AddATSPIEventListeners()375 void AccessibilityEventRecorderAuraLinux::AddATSPIEventListeners() {
376   atspi_init();
377   atspi_event_listener_ =
378       atspi_event_listener_new(OnATSPIEventReceived, this, nullptr);
379 
380   GError* error = nullptr;
381   for (size_t i = 0; i < base::size(kEventNames); i++) {
382     atspi_event_listener_register(atspi_event_listener_, kEventNames[i],
383                                   &error);
384     if (error) {
385       LOG(ERROR) << "Could not register event listener for " << kEventNames[i];
386       g_clear_error(&error);
387     }
388   }
389 }
390 
RemoveATSPIEventListeners()391 void AccessibilityEventRecorderAuraLinux::RemoveATSPIEventListeners() {
392   if (!atspi_event_listener_)
393     return;
394 
395   GError* error = nullptr;
396   for (size_t i = 0; i < base::size(kEventNames); i++) {
397     atspi_event_listener_deregister(atspi_event_listener_, kEventNames[i],
398                                     nullptr);
399     if (error) {
400       LOG(ERROR) << "Could not deregister event listener for "
401                  << kEventNames[i];
402       g_clear_error(&error);
403     }
404   }
405 
406   g_object_unref(atspi_event_listener_);
407   atspi_event_listener_ = nullptr;
408 }
409 
ProcessATSPIEvent(const AtspiEvent * event)410 void AccessibilityEventRecorderAuraLinux::ProcessATSPIEvent(
411     const AtspiEvent* event) {
412   GError* error = nullptr;
413 
414   if (!application_name_match_pattern_.empty()) {
415     AtspiAccessible* application =
416         atspi_accessible_get_application(event->source, &error);
417     if (error || !application)
418       return;
419 
420     char* application_name = atspi_accessible_get_name(application, &error);
421     g_object_unref(application);
422     if (error || !application_name) {
423       g_clear_error(&error);
424       return;
425     }
426 
427     if (!base::MatchPattern(application_name,
428                             application_name_match_pattern_)) {
429       return;
430     }
431     free(application_name);
432   }
433 
434   if (pid_) {
435     int pid = atspi_accessible_get_process_id(event->source, &error);
436     if (!error && pid != pid_)
437       return;
438     g_clear_error(&error);
439   }
440 
441   std::stringstream output;
442   output << event->type << " ";
443 
444   GHashTable* attributes =
445       atspi_accessible_get_attributes(event->source, &error);
446   std::string html_tag, html_class, html_id;
447   if (!error && attributes) {
448     if (char* tag = static_cast<char*>(g_hash_table_lookup(attributes, "tag")))
449       html_tag = tag;
450     if (char* id = static_cast<char*>(g_hash_table_lookup(attributes, "id")))
451       html_id = id;
452     if (char* class_chars =
453             static_cast<char*>(g_hash_table_lookup(attributes, "class")))
454       html_class = std::string(".") + class_chars;
455     g_hash_table_unref(attributes);
456   }
457   g_clear_error(&error);
458 
459   if (!html_tag.empty())
460     output << "<" << html_tag << html_id << html_class << ">";
461 
462   AtspiRole role = atspi_accessible_get_role(event->source, &error);
463   output << "role=";
464   if (!error)
465     output << ATSPIRoleToString(role);
466   else
467     output << "#error";
468   g_clear_error(&error);
469 
470   char* name = atspi_accessible_get_name(event->source, &error);
471   output << " name=";
472   if (!error && name)
473     output << name;
474   else
475     output << "#error";
476   g_clear_error(&error);
477   free(name);
478 
479   AtspiStateSet* atspi_states = atspi_accessible_get_state_set(event->source);
480   GArray* state_array = atspi_state_set_get_states(atspi_states);
481   std::vector<std::string> states;
482   for (unsigned i = 0; i < state_array->len; i++) {
483     AtspiStateType state_type = g_array_index(state_array, AtspiStateType, i);
484     states.push_back(ATSPIStateToString(state_type));
485   }
486   g_array_free(state_array, TRUE);
487   g_object_unref(atspi_states);
488   output << " ";
489   std::copy(states.begin(), states.end(),
490             std::ostream_iterator<std::string>(output, ", "));
491 
492   OnEvent(output.str());
493 }
494 
495 }  // namespace content
496