Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/accessible/atk/UtilInterface.cpp
Line
Count
Source (jump to first uncovered line)
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 "ApplicationAccessibleWrap.h"
8
#include "mozilla/Likely.h"
9
#include "nsAccessibilityService.h"
10
#include "nsMai.h"
11
12
#include <atk/atk.h>
13
#include <gtk/gtk.h>
14
#include <string.h>
15
16
using namespace mozilla;
17
using namespace mozilla::a11y;
18
19
typedef AtkUtil MaiUtil;
20
typedef AtkUtilClass MaiUtilClass;
21
22
0
#define MAI_VERSION MOZILLA_VERSION
23
0
#define MAI_NAME "Gecko"
24
25
extern "C" {
26
static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener,
27
                                               const gchar* event_type);
28
static void (*gail_remove_global_event_listener) (guint remove_listener);
29
static void (*gail_remove_key_event_listener) (guint remove_listener);
30
static AtkObject*  (*gail_get_root)();
31
}
32
33
struct MaiUtilListenerInfo
34
{
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
50
add_listener (GSignalEmissionHook listener,
51
              const gchar *object_type,
52
              const gchar *signal,
53
              const gchar *hook_data,
54
              guint gail_listenerid = 0)
55
0
{
56
0
    GType type;
57
0
    guint signal_id;
58
0
    gint rc = 0;
59
0
60
0
    type = g_type_from_name(object_type);
61
0
    if (type) {
62
0
        signal_id = g_signal_lookup(signal, type);
63
0
        if (signal_id > 0) {
64
0
            MaiUtilListenerInfo *listener_info;
65
0
66
0
            rc = sListener_idx;
67
0
68
0
            listener_info =  (MaiUtilListenerInfo *)
69
0
                g_malloc(sizeof(MaiUtilListenerInfo));
70
0
            listener_info->key = sListener_idx;
71
0
            listener_info->hook_id =
72
0
                g_signal_add_emission_hook(signal_id, 0, listener,
73
0
                                           g_strdup(hook_data),
74
0
                                           (GDestroyNotify)g_free);
75
0
            listener_info->signal_id = signal_id;
76
0
            listener_info->gail_listenerid = gail_listenerid;
77
0
78
0
            g_hash_table_insert(sListener_list, &(listener_info->key),
79
0
                                listener_info);
80
0
            sListener_idx++;
81
0
        }
82
0
        else {
83
0
            g_warning("Invalid signal type %s\n", signal);
84
0
        }
85
0
    }
86
0
    else {
87
0
        g_warning("Invalid object type %s\n", object_type);
88
0
    }
89
0
    return rc;
90
0
}
91
92
static guint
93
mai_util_add_global_event_listener(GSignalEmissionHook listener,
94
                                   const gchar *event_type)
95
0
{
96
0
    guint rc = 0;
97
0
    gchar **split_string;
98
0
99
0
    split_string = g_strsplit (event_type, ":", 3);
100
0
101
0
    if (split_string) {
102
0
        if (!strcmp ("window", split_string[0])) {
103
0
            guint gail_listenerid = 0;
104
0
            if (gail_add_global_event_listener) {
105
0
                // call gail's function to track gtk native window events
106
0
                gail_listenerid =
107
0
                    gail_add_global_event_listener(listener, event_type);
108
0
            }
109
0
110
0
            rc = add_listener (listener, "MaiAtkObject", split_string[1],
111
0
                               event_type, gail_listenerid);
112
0
        }
113
0
        else {
114
0
            rc = add_listener (listener, split_string[1], split_string[2],
115
0
                               event_type);
116
0
        }
117
0
        g_strfreev(split_string);
118
0
    }
119
0
    return rc;
120
0
}
121
122
static void
123
mai_util_remove_global_event_listener(guint remove_listener)
124
0
{
125
0
    if (remove_listener > 0) {
126
0
        MaiUtilListenerInfo *listener_info;
127
0
        gint tmp_idx = remove_listener;
128
0
129
0
        listener_info = (MaiUtilListenerInfo *)
130
0
            g_hash_table_lookup(sListener_list, &tmp_idx);
131
0
132
0
        if (listener_info != nullptr) {
133
0
            if (gail_remove_global_event_listener &&
134
0
                listener_info->gail_listenerid) {
135
0
              gail_remove_global_event_listener(listener_info->gail_listenerid);
136
0
            }
137
0
138
0
            /* Hook id of 0 and signal id of 0 are invalid */
139
0
            if (listener_info->hook_id != 0 && listener_info->signal_id != 0) {
140
0
                /* Remove the emission hook */
141
0
                g_signal_remove_emission_hook(listener_info->signal_id,
142
0
                                              listener_info->hook_id);
143
0
144
0
                /* Remove the element from the hash */
145
0
                g_hash_table_remove(sListener_list, &tmp_idx);
146
0
            }
147
0
            else {
148
0
                g_warning("Invalid listener hook_id %ld or signal_id %d\n",
149
0
                          listener_info->hook_id, listener_info->signal_id);
150
0
            }
151
0
        }
152
0
        else {
153
0
            // atk-bridge is initialized with gail (e.g. yelp)
154
0
            // try gail_remove_global_event_listener
155
0
            if (gail_remove_global_event_listener) {
156
0
                return gail_remove_global_event_listener(remove_listener);
157
0
            }
158
0
159
0
            g_warning("No listener with the specified listener id %d",
160
0
                      remove_listener);
161
0
        }
162
0
    }
163
0
    else {
164
0
        g_warning("Invalid listener_id %d", remove_listener);
165
0
    }
166
0
}
167
168
static AtkKeyEventStruct *
169
atk_key_event_from_gdk_event_key (GdkEventKey *key)
170
0
{
171
0
    AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1);
172
0
    switch (key->type) {
173
0
    case GDK_KEY_PRESS:
174
0
        event->type = ATK_KEY_EVENT_PRESS;
175
0
        break;
176
0
    case GDK_KEY_RELEASE:
177
0
        event->type = ATK_KEY_EVENT_RELEASE;
178
0
        break;
179
0
    default:
180
0
        g_assert_not_reached ();
181
0
        return nullptr;
182
0
    }
183
0
    event->state = key->state;
184
0
    event->keyval = key->keyval;
185
0
    event->length = key->length;
186
0
    if (key->string && key->string [0] &&
187
0
        (key->state & GDK_CONTROL_MASK ||
188
0
         g_unichar_isgraph (g_utf8_get_char (key->string)))) {
189
0
        event->string = key->string;
190
0
    }
191
0
    else if (key->type == GDK_KEY_PRESS ||
192
0
             key->type == GDK_KEY_RELEASE) {
193
0
        event->string = gdk_keyval_name (key->keyval);
194
0
    }
195
0
    event->keycode = key->hardware_keycode;
196
0
    event->timestamp = key->time;
197
0
198
0
    return event;
199
0
}
200
201
struct MaiKeyEventInfo
202
{
203
    AtkKeyEventStruct *key_event;
204
    gpointer func_data;
205
};
206
207
union AtkKeySnoopFuncPointer
208
{
209
    AtkKeySnoopFunc func_ptr;
210
    gpointer data;
211
};
212
213
static gboolean
214
notify_hf(gpointer key, gpointer value, gpointer data)
215
0
{
216
0
    MaiKeyEventInfo *info = (MaiKeyEventInfo *)data;
217
0
    AtkKeySnoopFuncPointer atkKeySnoop;
218
0
    atkKeySnoop.data = value;
219
0
    return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE : FALSE;
220
0
}
221
222
static void
223
insert_hf(gpointer key, gpointer value, gpointer data)
224
0
{
225
0
    GHashTable *new_table = (GHashTable *) data;
226
0
    g_hash_table_insert (new_table, key, value);
227
0
}
228
229
static GHashTable* sKey_listener_list = nullptr;
230
231
static gint
232
mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event, gpointer func_data)
233
0
{
234
0
    /* notify each AtkKeySnoopFunc in turn... */
235
0
236
0
    MaiKeyEventInfo *info = g_new0(MaiKeyEventInfo, 1);
237
0
    gint consumed = 0;
238
0
    if (sKey_listener_list) {
239
0
        GHashTable *new_hash = g_hash_table_new(nullptr, nullptr);
240
0
        g_hash_table_foreach (sKey_listener_list, insert_hf, new_hash);
241
0
        info->key_event = atk_key_event_from_gdk_event_key (event);
242
0
        info->func_data = func_data;
243
0
        consumed = g_hash_table_foreach_steal (new_hash, notify_hf, info);
244
0
        g_hash_table_destroy (new_hash);
245
0
        g_free(info->key_event);
246
0
    }
247
0
    g_free(info);
248
0
    return (consumed ? 1 : 0);
249
0
}
250
251
static guint sKey_snooper_id = 0;
252
253
static guint
254
mai_util_add_key_event_listener(AtkKeySnoopFunc listener, gpointer data)
255
0
{
256
0
  if (MOZ_UNLIKELY(!listener)) {
257
0
    return 0;
258
0
  }
259
0
260
0
  static guint key = 0;
261
0
262
0
  if (!sKey_listener_list) {
263
0
    sKey_listener_list = g_hash_table_new(nullptr, nullptr);
264
0
  }
265
0
266
0
  // If we have no registered event listeners then we need to (re)install the
267
0
  // key event snooper.
268
0
  if (g_hash_table_size(sKey_listener_list) == 0) {
269
0
    sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
270
0
  }
271
0
272
0
  AtkKeySnoopFuncPointer atkKeySnoop;
273
0
  atkKeySnoop.func_ptr = listener;
274
0
  key++;
275
0
  g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key),
276
0
                      atkKeySnoop.data);
277
0
  return key;
278
0
}
279
280
static void
281
mai_util_remove_key_event_listener (guint remove_listener)
282
0
{
283
0
    if (!sKey_listener_list) {
284
0
        // atk-bridge is initialized with gail (e.g. yelp)
285
0
        // try gail_remove_key_event_listener
286
0
        return gail_remove_key_event_listener(remove_listener);
287
0
    }
288
0
289
0
    g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER (remove_listener));
290
0
    if (g_hash_table_size(sKey_listener_list) == 0) {
291
0
        gtk_key_snooper_remove(sKey_snooper_id);
292
0
    }
293
0
}
294
295
static AtkObject*
296
mai_util_get_root()
297
0
{
298
0
  ApplicationAccessible* app = ApplicationAcc();
299
0
  if (app)
300
0
    return app->GetAtkObject();
301
0
302
0
  // We've shutdown, try to use gail instead
303
0
  // (to avoid assert in spi_atk_tidy_windows())
304
0
  // XXX tbsaunde then why didn't we replace the gail atk_util impl?
305
0
  if (gail_get_root)
306
0
    return gail_get_root();
307
0
308
0
  return nullptr;
309
0
}
310
311
static const gchar*
312
mai_util_get_toolkit_name()
313
0
{
314
0
    return MAI_NAME;
315
0
}
316
317
static const gchar*
318
mai_util_get_toolkit_version()
319
0
{
320
0
    return MAI_VERSION;
321
0
}
322
323
static void
324
_listener_info_destroy(gpointer data)
325
0
{
326
0
    g_free(data);
327
0
}
328
329
static void
330
window_added (AtkObject *atk_obj,
331
              guint     index,
332
              AtkObject *child)
333
0
{
334
0
  if (!IS_MAI_OBJECT(child))
335
0
      return;
336
0
337
0
  static guint id =  g_signal_lookup ("create", MAI_TYPE_ATK_OBJECT);
338
0
  g_signal_emit (child, id, 0);
339
0
}
340
341
static void
342
window_removed (AtkObject *atk_obj,
343
                guint     index,
344
                AtkObject *child)
345
0
{
346
0
  if (!IS_MAI_OBJECT(child))
347
0
      return;
348
0
349
0
  static guint id =  g_signal_lookup ("destroy", MAI_TYPE_ATK_OBJECT);
350
0
  g_signal_emit (child, id, 0);
351
0
}
352
353
  static void
354
UtilInterfaceInit(MaiUtilClass* klass)
355
0
{
356
0
    AtkUtilClass *atk_class;
357
0
    gpointer data;
358
0
359
0
    data = g_type_class_peek(ATK_TYPE_UTIL);
360
0
    atk_class = ATK_UTIL_CLASS(data);
361
0
362
0
    // save gail function pointer
363
0
    gail_add_global_event_listener = atk_class->add_global_event_listener;
364
0
    gail_remove_global_event_listener = atk_class->remove_global_event_listener;
365
0
    gail_remove_key_event_listener = atk_class->remove_key_event_listener;
366
0
    gail_get_root = atk_class->get_root;
367
0
368
0
    atk_class->add_global_event_listener =
369
0
        mai_util_add_global_event_listener;
370
0
    atk_class->remove_global_event_listener =
371
0
        mai_util_remove_global_event_listener;
372
0
    atk_class->add_key_event_listener = mai_util_add_key_event_listener;
373
0
    atk_class->remove_key_event_listener = mai_util_remove_key_event_listener;
374
0
    atk_class->get_root = mai_util_get_root;
375
0
    atk_class->get_toolkit_name = mai_util_get_toolkit_name;
376
0
    atk_class->get_toolkit_version = mai_util_get_toolkit_version;
377
0
378
0
    sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr,
379
0
                                           _listener_info_destroy);
380
0
    // Keep track of added/removed windows.
381
0
    AtkObject *root = atk_get_root ();
382
0
    g_signal_connect (root, "children-changed::add", (GCallback) window_added, nullptr);
383
0
    g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, nullptr);
384
0
}
385
}
386
387
GType
388
mai_util_get_type()
389
0
{
390
0
    static GType type = 0;
391
0
392
0
    if (!type) {
393
0
        static const GTypeInfo tinfo = {
394
0
            sizeof(MaiUtilClass),
395
0
            (GBaseInitFunc) nullptr, /* base init */
396
0
            (GBaseFinalizeFunc) nullptr, /* base finalize */
397
0
            (GClassInitFunc) UtilInterfaceInit, /* class init */
398
0
            (GClassFinalizeFunc) nullptr, /* class finalize */
399
0
            nullptr, /* class data */
400
0
            sizeof(MaiUtil), /* instance size */
401
0
            0, /* nb preallocs */
402
0
            (GInstanceInitFunc) nullptr, /* instance init */
403
0
            nullptr /* value table */
404
0
        };
405
0
406
0
        type = g_type_register_static(ATK_TYPE_UTIL,
407
0
                                      "MaiUtil", &tinfo, GTypeFlags(0));
408
0
    }
409
0
    return type;
410
0
}
411