{"id":994,"date":"2020-07-27T17:01:11","date_gmt":"2020-07-27T15:01:11","guid":{"rendered":"http:\/\/blogs.igalia.com\/jaragunde\/?p=994"},"modified":"2021-11-11T17:55:06","modified_gmt":"2021-11-11T16:55:06","slug":"the-trip-of-a-key-press-event-in-chromium-accessibility","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/jaragunde\/2020\/07\/the-trip-of-a-key-press-event-in-chromium-accessibility\/","title":{"rendered":"The trip of a key press event in Chromium accessibility"},"content":{"rendered":"<p>It&#8217;s amazing to think about how much computing goes into something as simple as a keystroke that we just take for granted. Recently, I was fixing a bug related to accessibility key events, and to do this, first I had to understand the complex trip that these events take when they arrive to the browser &#8211; from the X server until they reach the accessibility system.<\/p>\n<p>Let me start from the beginning. I&#8217;m working on the accessibility of the Chromium browser on Linux. The bug was <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1042864\">#1042864<\/a>: key strokes happening on native dialogs, like open and save dialogs, were not reported to the screen reader. The issue also affects Electron-based software, one important example is <a href=\"https:\/\/code.visualstudio.com\/\">Visual Studio Code<\/a>.<\/p>\n<p><!--more--><\/p>\n<h3>A cake with many layers<\/h3>\n<p>Linux accessibility is composed of many layers, and this fact stands out when working on a complex piece of software like Chromium which comes with its own UI toolkit.<\/p>\n<p><a href=\"https:\/\/blogs.igalia.com\/jaragunde\/files\/2020\/07\/chromium-a11y-layers.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blogs.igalia.com\/jaragunde\/files\/2020\/07\/chromium-a11y-layers.png\" alt=\"\" width=\"718\" height=\"378\" class=\"alignnone size-full wp-image-1013\" srcset=\"https:\/\/blogs.igalia.com\/jaragunde\/files\/2020\/07\/chromium-a11y-layers.png 718w, https:\/\/blogs.igalia.com\/jaragunde\/files\/2020\/07\/chromium-a11y-layers-300x158.png 300w, https:\/\/blogs.igalia.com\/jaragunde\/files\/2020\/07\/chromium-a11y-layers-500x263.png 500w\" sizes=\"auto, (max-width: 718px) 100vw, 718px\" \/><\/a><\/p>\n<p>Currently, the most straight-forward way to build accessible applications for the Linux desktop is using and implementing the hooks provided by the ATK (Accessibility Toolkit) API. GTK+ widgets already do so, applications using them will be accessible by default, but more complex software that implements custom widgets or its own toolkit will need to provide the code for the ATK entry points and emit the corresponding events. That&#8217;s the case of Chromium!<\/p>\n<p>The screen reader or any Assistive technology (AT) has to get information and listen for events happening in any software running on the system, to transform it into something meaningful for their users, like speech or braille.<\/p>\n<p>AT-SPI is the glue between these two ends: it runs at system level, observing what&#8217;s going on with applications and keeping a global registry of the accessible objects; it receives updates from applications, which translate local ATK objects into global AT-SPI objects; and it receives queries from ATs then pings them when events of their interest happen. It uses D-Bus for inter-process communication (IPC).<\/p>\n<p>You can learn more about accessibility, in general and also in the web platform, in this <a href=\"https:\/\/www.igalia.com\/chats\/accessibility-with-brian-kardell-and-martin-robinson\">great interview with my colleague Martin Robinson<\/a>.<\/p>\n<h3>The trip of a keypress event<\/h3>\n<p>Let&#8217;s say we are building an AT in Python, making use of the <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/pyatspi2\">pyatspi2<\/a> library. We want to listen keypress events, so we run <a href=\"https:\/\/github.com\/GNOME\/pyatspi2\/blob\/e8d2dfeb5457ea6e1fe0b2c86e1d8e795c611a31\/pyatspi\/registry.py#L288\">registerKeystrokeListener<\/a> to register a callback function.<\/p>\n<p>Pyatspi2 actually wraps AT-SPI&#8217;s function <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/atspi\/atspi-registry.c#L197\">atspi_register_keystroke_listener<\/a>, which eventually <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/atspi\/atspi-registry.c#L102\">calls the remote method RegisterKeystrokeListener via D-Bus<\/a>. The actual D-Bus remote calls happen at <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/dbind\/dbind.c#L220\">dbind.c<\/a>.<\/p>\n<p>We have jumped from our AT to the AT-SPI service, via IPC. The <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/xml\/DeviceEventController.xml#L5\">DeviceEventController interface<\/a> provides the remote method mentioned above, and the actual code implementing it is in <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/registryd\/deviceeventcontroller.c#L1341\">impl_register_keystroke_listener<\/a>. Then, the function is added to a list of listeners for key events, in <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/registryd\/deviceeventcontroller.c#L1341\">spi_controller_register_device_listener<\/a>; these listeners in the list will be notified when an event happens, in <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/f7898011838287b097d964da87c35724bcf92e10\/registryd\/deviceeventcontroller.c#L1152\">spi_controller_notify_keylisteners<\/a>.<\/p>\n<p>AT-SPI will sit there, waiting for the events from applications to arrive. They will come over through D-Bus, as they are from different processes: the entry point for any D-Bus message in the AT-SPI core is <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/master\/registryd\/deviceeventcontroller.c#L1925\">handle_dec_method_from_idle<\/a>. One of the operations, NotifyListenersSync, will run <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-core\/-\/blob\/master\/registryd\/deviceeventcontroller.c#L1801\">impl_notify_listeners_sync<\/a> which will later call the function spi_controller_notify_keylisteners we just mentioned, and run all the registered listeners.<\/p>\n<p>Who will call the remote method NotifyListenersSync? Applications will have to do it, if they want to be accessible. They could implement this themselves, but they are likely using a wrapper library. In the case of GTK+, there is <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-atk\">at-spi2-atk<\/a>, which bridges ATK signals with the at-spi2-core D-Bus interfaces so applications don&#8217;t have to know them.<\/p>\n<p>The bridge sports its own callback, named <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-atk\/-\/blob\/eac8e935128753f8204ecc3904e6e3e7b231e3ea\/atk-adaptor\/event.c#L275\">spi_atk_bridge_key_listener<\/a>, and eventually calls the NotifyListenersSync method via D-Bus, at <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-atk\/-\/blob\/eac8e935128753f8204ecc3904e6e3e7b231e3ea\/atk-adaptor\/event.c#L194\">Accessibility_DeviceEventController_NotifyListenersSync<\/a>. The callback was registered as an ATK key event listener in <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/at-spi2-atk\/-\/blob\/eac8e935128753f8204ecc3904e6e3e7b231e3ea\/atk-adaptor\/event.c#L1340\">spi_atk_register_event_listeners<\/a>: the function <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/atk\/-\/blob\/d84ddda9106ca78c815a91aa9e9524533245373f\/atk\/atkutil.c#L446\">atk_add_key_event_listener<\/a>, which is part of the ATK API, registers the key event listener, and it internally makes use of the <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/atk\/-\/blob\/d84ddda9106ca78c815a91aa9e9524533245373f\/atk\/atkutil.h#L141\">AtkUtil struct<\/a> as defined in atkutil.h.<\/p>\n<p>AtkUtil functions are not implemented by the ATK library; instead, toolkits must provide them. GTK+ does it in <a href=\"https:\/\/gitlab.gnome.org\/GNOME\/gtk\/-\/blob\/eb2a839892f1b5c55b5aa94e49c3cb9af08db335\/gtk\/a11y\/gtkaccessibilityutil.c#L100\">_gtk_accessibility_override_atk_util<\/a>; in the case of Chromium, the functions that populate the AtkUtil struct are defined in <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/master:ui\/accessibility\/platform\/atk_util_auralinux.cc;drc=c9009e23d9f60cfbbd5a042fb0aedd2412421d0d;l=70\">atk_util_auralinux_class_init<\/a>. The particular function that registers the key event listener in Chromium is <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/master:ui\/accessibility\/platform\/atk_util_auralinux.cc;bpv=1;bpt=1;drc=c9009e23d9f60cfbbd5a042fb0aedd2412421d0d;l=55\">AtkUtilAuraLinuxAddKeyEventListener<\/a>: it gets added to a list, which will later be called when an Atk key event is processed in Chromium, at <a href=\"https:\/\/source.chromium.org\/chromium\/chromium\/src\/+\/master:ui\/accessibility\/platform\/atk_util_auralinux.cc;l=125;drc=c9009e23d9f60cfbbd5a042fb0aedd2412421d0d;bpv=0;bpt=1\">HandleAtkKeyEvent<\/a>.<\/p>\n<h3>Are we there yet?<\/h3>\n<p>There are more pieces of software involved in this issue: Chromium will receive key press events from the X server, involving another IPC connection and API layer, and there&#8217;s all the browser code managing them, where the solution was actually implemented. I will cover those parts, and the actual solution, in a future post (EDIT: it&#8217;s <a href=\"https:\/\/blogs.igalia.com\/jaragunde\/2020\/10\/30\/event-management-in-x11-chromium\/\">here<\/a>!).<\/p>\n<p>Meanwhile, I hope you enjoyed reading this, and happy hacking!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It&#8217;s amazing to think about how much computing goes into something as simple as a keystroke that we just take for granted. Recently, I was fixing a bug related to accessibility key events, and to do this, first I had &hellip; <a href=\"https:\/\/blogs.igalia.com\/jaragunde\/2020\/07\/the-trip-of-a-key-press-event-in-chromium-accessibility\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":17,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[18,23,24,3],"tags":[],"class_list":["post-994","post","type-post","status-publish","format-standard","hentry","category-accessibility","category-browsers","category-chromium","category-igalia"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/posts\/994","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/users\/17"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/comments?post=994"}],"version-history":[{"count":17,"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/posts\/994\/revisions"}],"predecessor-version":[{"id":1107,"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/posts\/994\/revisions\/1107"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/media?parent=994"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/categories?post=994"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/jaragunde\/wp-json\/wp\/v2\/tags?post=994"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}