tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

UtilInterface.cpp (11595B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ApplicationAccessible.h"
      8 #include "mozilla/Likely.h"
      9 #include "nsAccessibilityService.h"
     10 #include "nsMai.h"
     11 
     12 #include <atk/atkobject.h>
     13 #include <atk/atkutil.h>
     14 #include <gtk/gtk.h>
     15 #include <string.h>
     16 
     17 using namespace mozilla;
     18 using namespace mozilla::a11y;
     19 
     20 typedef AtkUtil MaiUtil;
     21 typedef AtkUtilClass MaiUtilClass;
     22 
     23 #define MAI_VERSION MOZILLA_VERSION
     24 #define MAI_NAME "Gecko"
     25 
     26 extern "C" {
     27 static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener,
     28                                               const gchar* event_type);
     29 static void (*gail_remove_global_event_listener)(guint remove_listener);
     30 static void (*gail_remove_key_event_listener)(guint remove_listener);
     31 static AtkObject* (*gail_get_root)();
     32 }
     33 
     34 struct MaiUtilListenerInfo {
     35  gint key;
     36  guint signal_id;
     37  gulong hook_id;
     38  // For window create/destory/minimize/maximize/restore/activate/deactivate
     39  // events, we'll chain gail_util's add/remove_global_event_listener.
     40  // So we store the listenerid returned by gail's add_global_event_listener
     41  // in this structure to call gail's remove_global_event_listener later.
     42  guint gail_listenerid;
     43 };
     44 
     45 static GHashTable* sListener_list = nullptr;
     46 static gint sListener_idx = 1;
     47 
     48 extern "C" {
     49 static guint add_listener(GSignalEmissionHook listener,
     50                          const gchar* object_type, const gchar* signal,
     51                          const gchar* hook_data, guint gail_listenerid = 0) {
     52  GType type;
     53  guint signal_id;
     54  gint rc = 0;
     55 
     56  type = g_type_from_name(object_type);
     57  if (type) {
     58    signal_id = g_signal_lookup(signal, type);
     59    if (signal_id > 0) {
     60      MaiUtilListenerInfo* listener_info;
     61 
     62      rc = sListener_idx;
     63 
     64      listener_info =
     65          (MaiUtilListenerInfo*)g_malloc(sizeof(MaiUtilListenerInfo));
     66      listener_info->key = sListener_idx;
     67      listener_info->hook_id = g_signal_add_emission_hook(
     68          signal_id, 0, listener, g_strdup(hook_data), (GDestroyNotify)g_free);
     69      listener_info->signal_id = signal_id;
     70      listener_info->gail_listenerid = gail_listenerid;
     71 
     72      g_hash_table_insert(sListener_list, &(listener_info->key), listener_info);
     73      sListener_idx++;
     74    } else {
     75      g_warning("Invalid signal type %s\n", signal);
     76    }
     77  } else {
     78    g_warning("Invalid object type %s\n", object_type);
     79  }
     80  return rc;
     81 }
     82 
     83 static guint mai_util_add_global_event_listener(GSignalEmissionHook listener,
     84                                                const gchar* event_type) {
     85  guint rc = 0;
     86  gchar** split_string;
     87 
     88  split_string = g_strsplit(event_type, ":", 3);
     89 
     90  if (split_string) {
     91    if (!strcmp("window", split_string[0])) {
     92      guint gail_listenerid = 0;
     93      if (gail_add_global_event_listener) {
     94        // call gail's function to track gtk native window events
     95        gail_listenerid = gail_add_global_event_listener(listener, event_type);
     96      }
     97 
     98      rc = add_listener(listener, "MaiAtkObject", split_string[1], event_type,
     99                        gail_listenerid);
    100    } else {
    101      rc = add_listener(listener, split_string[1], split_string[2], event_type);
    102    }
    103    g_strfreev(split_string);
    104  }
    105  return rc;
    106 }
    107 
    108 static void mai_util_remove_global_event_listener(guint remove_listener) {
    109  if (remove_listener > 0) {
    110    MaiUtilListenerInfo* listener_info;
    111    gint tmp_idx = remove_listener;
    112 
    113    listener_info =
    114        (MaiUtilListenerInfo*)g_hash_table_lookup(sListener_list, &tmp_idx);
    115 
    116    if (listener_info != nullptr) {
    117      if (gail_remove_global_event_listener && listener_info->gail_listenerid) {
    118        gail_remove_global_event_listener(listener_info->gail_listenerid);
    119      }
    120 
    121      /* Hook id of 0 and signal id of 0 are invalid */
    122      if (listener_info->hook_id != 0 && listener_info->signal_id != 0) {
    123        /* Remove the emission hook */
    124        g_signal_remove_emission_hook(listener_info->signal_id,
    125                                      listener_info->hook_id);
    126 
    127        /* Remove the element from the hash */
    128        g_hash_table_remove(sListener_list, &tmp_idx);
    129      } else {
    130        g_warning("Invalid listener hook_id %ld or signal_id %d\n",
    131                  listener_info->hook_id, listener_info->signal_id);
    132      }
    133    } else {
    134      // atk-bridge is initialized with gail (e.g. yelp)
    135      // try gail_remove_global_event_listener
    136      if (gail_remove_global_event_listener) {
    137        return gail_remove_global_event_listener(remove_listener);
    138      }
    139 
    140      g_warning("No listener with the specified listener id %d",
    141                remove_listener);
    142    }
    143  } else {
    144    g_warning("Invalid listener_id %d", remove_listener);
    145  }
    146 }
    147 
    148 static AtkKeyEventStruct* atk_key_event_from_gdk_event_key(GdkEventKey* key) {
    149  AtkKeyEventStruct* event = g_new0(AtkKeyEventStruct, 1);
    150  switch (key->type) {
    151    case GDK_KEY_PRESS:
    152      event->type = ATK_KEY_EVENT_PRESS;
    153      break;
    154    case GDK_KEY_RELEASE:
    155      event->type = ATK_KEY_EVENT_RELEASE;
    156      break;
    157    default:
    158      g_assert_not_reached();
    159      return nullptr;
    160  }
    161  event->state = key->state;
    162  event->keyval = key->keyval;
    163  event->length = key->length;
    164  if (key->string && key->string[0] &&
    165      g_unichar_isgraph(g_utf8_get_char(key->string))) {
    166    event->string = key->string;
    167  } else if (key->type == GDK_KEY_PRESS || key->type == GDK_KEY_RELEASE) {
    168    event->string = gdk_keyval_name(key->keyval);
    169  }
    170  event->keycode = key->hardware_keycode;
    171  event->timestamp = key->time;
    172 
    173  return event;
    174 }
    175 
    176 struct MaiKeyEventInfo {
    177  AtkKeyEventStruct* key_event;
    178  gpointer func_data;
    179 };
    180 
    181 union AtkKeySnoopFuncPointer {
    182  AtkKeySnoopFunc func_ptr;
    183  gpointer data;
    184 };
    185 
    186 static gboolean notify_hf(gpointer key, gpointer value, gpointer data) {
    187  MaiKeyEventInfo* info = (MaiKeyEventInfo*)data;
    188  AtkKeySnoopFuncPointer atkKeySnoop;
    189  atkKeySnoop.data = value;
    190  return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE
    191                                                                  : FALSE;
    192 }
    193 
    194 static void insert_hf(gpointer key, gpointer value, gpointer data) {
    195  GHashTable* new_table = (GHashTable*)data;
    196  g_hash_table_insert(new_table, key, value);
    197 }
    198 
    199 static GHashTable* sKey_listener_list = nullptr;
    200 
    201 static gint mai_key_snooper(GtkWidget* the_widget, GdkEventKey* event,
    202                            gpointer func_data) {
    203  /* notify each AtkKeySnoopFunc in turn... */
    204 
    205  MaiKeyEventInfo* info = g_new0(MaiKeyEventInfo, 1);
    206  gint consumed = 0;
    207  if (sKey_listener_list) {
    208    GHashTable* new_hash = g_hash_table_new(nullptr, nullptr);
    209    g_hash_table_foreach(sKey_listener_list, insert_hf, new_hash);
    210    info->key_event = atk_key_event_from_gdk_event_key(event);
    211    info->func_data = func_data;
    212    consumed = g_hash_table_foreach_steal(new_hash, notify_hf, info);
    213    g_hash_table_destroy(new_hash);
    214    g_free(info->key_event);
    215  }
    216  g_free(info);
    217  return (consumed ? 1 : 0);
    218 }
    219 
    220 static guint sKey_snooper_id = 0;
    221 
    222 static guint mai_util_add_key_event_listener(AtkKeySnoopFunc listener,
    223                                             gpointer data) {
    224  if (MOZ_UNLIKELY(!listener)) {
    225    return 0;
    226  }
    227 
    228  static guint key = 0;
    229 
    230  if (!sKey_listener_list) {
    231    sKey_listener_list = g_hash_table_new(nullptr, nullptr);
    232  }
    233 
    234  // If we have no registered event listeners then we need to (re)install the
    235  // key event snooper.
    236  if (g_hash_table_size(sKey_listener_list) == 0) {
    237    sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
    238  }
    239 
    240  AtkKeySnoopFuncPointer atkKeySnoop;
    241  atkKeySnoop.func_ptr = listener;
    242  key++;
    243  g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key),
    244                      atkKeySnoop.data);
    245  return key;
    246 }
    247 
    248 static void mai_util_remove_key_event_listener(guint remove_listener) {
    249  if (!sKey_listener_list) {
    250    // atk-bridge is initialized with gail (e.g. yelp)
    251    // try gail_remove_key_event_listener
    252    return gail_remove_key_event_listener(remove_listener);
    253  }
    254 
    255  g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER(remove_listener));
    256  if (g_hash_table_size(sKey_listener_list) == 0) {
    257    gtk_key_snooper_remove(sKey_snooper_id);
    258  }
    259 }
    260 
    261 static AtkObject* mai_util_get_root() {
    262  ApplicationAccessible* app = ApplicationAcc();
    263  if (app) return app->GetAtkObject();
    264 
    265  // We've shutdown, try to use gail instead
    266  // (to avoid assert in spi_atk_tidy_windows())
    267  // XXX tbsaunde then why didn't we replace the gail atk_util impl?
    268  if (gail_get_root) return gail_get_root();
    269 
    270  return nullptr;
    271 }
    272 
    273 static const gchar* mai_util_get_toolkit_name() { return MAI_NAME; }
    274 
    275 static const gchar* mai_util_get_toolkit_version() { return MAI_VERSION; }
    276 
    277 static void _listener_info_destroy(gpointer data) { g_free(data); }
    278 
    279 static void window_added(AtkObject* atk_obj, guint index, AtkObject* child) {
    280  if (!IS_MAI_OBJECT(child)) return;
    281 
    282  static guint id = g_signal_lookup("create", MAI_TYPE_ATK_OBJECT);
    283  g_signal_emit(child, id, 0);
    284 }
    285 
    286 static void window_removed(AtkObject* atk_obj, guint index, AtkObject* child) {
    287  if (!IS_MAI_OBJECT(child)) return;
    288 
    289  static guint id = g_signal_lookup("destroy", MAI_TYPE_ATK_OBJECT);
    290  g_signal_emit(child, id, 0);
    291 }
    292 
    293 static void UtilInterfaceInit(MaiUtilClass* klass) {
    294  AtkUtilClass* atk_class;
    295  gpointer data;
    296 
    297  data = g_type_class_peek(ATK_TYPE_UTIL);
    298  atk_class = ATK_UTIL_CLASS(data);
    299 
    300  // save gail function pointer
    301  gail_add_global_event_listener = atk_class->add_global_event_listener;
    302  gail_remove_global_event_listener = atk_class->remove_global_event_listener;
    303  gail_remove_key_event_listener = atk_class->remove_key_event_listener;
    304  gail_get_root = atk_class->get_root;
    305 
    306  atk_class->add_global_event_listener = mai_util_add_global_event_listener;
    307  atk_class->remove_global_event_listener =
    308      mai_util_remove_global_event_listener;
    309  atk_class->add_key_event_listener = mai_util_add_key_event_listener;
    310  atk_class->remove_key_event_listener = mai_util_remove_key_event_listener;
    311  atk_class->get_root = mai_util_get_root;
    312  atk_class->get_toolkit_name = mai_util_get_toolkit_name;
    313  atk_class->get_toolkit_version = mai_util_get_toolkit_version;
    314 
    315  sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr,
    316                                         _listener_info_destroy);
    317  // Keep track of added/removed windows.
    318  AtkObject* root = atk_get_root();
    319  g_signal_connect(root, "children-changed::add", (GCallback)window_added,
    320                   nullptr);
    321  g_signal_connect(root, "children-changed::remove", (GCallback)window_removed,
    322                   nullptr);
    323 }
    324 }
    325 
    326 GType mai_util_get_type() {
    327  static GType type = 0;
    328 
    329  if (!type) {
    330    static const GTypeInfo tinfo = {
    331        sizeof(MaiUtilClass),
    332        (GBaseInitFunc) nullptr,           /* base init */
    333        (GBaseFinalizeFunc) nullptr,       /* base finalize */
    334        (GClassInitFunc)UtilInterfaceInit, /* class init */
    335        (GClassFinalizeFunc) nullptr,      /* class finalize */
    336        nullptr,                           /* class data */
    337        sizeof(MaiUtil),                   /* instance size */
    338        0,                                 /* nb preallocs */
    339        (GInstanceInitFunc) nullptr,       /* instance init */
    340        nullptr                            /* value table */
    341    };
    342 
    343    type =
    344        g_type_register_static(ATK_TYPE_UTIL, "MaiUtil", &tinfo, GTypeFlags(0));
    345  }
    346  return type;
    347 }