1// Copyright (c) 2012 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/browser_accessibility_gtk.h"
6
7#include <gtk/gtk.h>
8
9#include "base/strings/utf_string_conversions.h"
10#include "content/browser/accessibility/browser_accessibility_manager_gtk.h"
11#include "content/common/accessibility_messages.h"
12
13namespace content {
14
15static gpointer browser_accessibility_parent_class = NULL;
16
17static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
18    BrowserAccessibilityAtk* atk_object) {
19  if (!atk_object)
20    return NULL;
21
22  return atk_object->m_object;
23}
24
25//
26// AtkComponent interface.
27//
28
29static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
30    AtkComponent* atk_object) {
31  if (!IS_BROWSER_ACCESSIBILITY(atk_object))
32    return NULL;
33
34  return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object));
35}
36
37static AtkObject* browser_accessibility_accessible_at_point(
38    AtkComponent* component, gint x, gint y, AtkCoordType coord_type) {
39  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component);
40  if (!obj)
41    return NULL;
42
43  gfx::Point point(x, y);
44  if (!obj->GetGlobalBoundsRect().Contains(point))
45    return NULL;
46
47  BrowserAccessibility* result = obj->BrowserAccessibilityForPoint(point);
48  if (!result)
49    return NULL;
50
51  AtkObject* atk_result = result->ToBrowserAccessibilityGtk()->GetAtkObject();
52  g_object_ref(atk_result);
53  return atk_result;
54}
55
56static void browser_accessibility_get_extents(
57    AtkComponent* component, gint* x, gint* y, gint* width, gint* height,
58    AtkCoordType coord_type) {
59  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component);
60  if (!obj)
61    return;
62
63  gfx::Rect bounds = obj->GetGlobalBoundsRect();
64  *x = bounds.x();
65  *y = bounds.y();
66  *width = bounds.width();
67  *height = bounds.height();
68}
69
70static gboolean browser_accessibility_grab_focus(AtkComponent* component) {
71  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component);
72  if (!obj)
73    return false;
74
75  obj->manager()->SetFocus(obj, true);
76  return true;
77}
78
79static void ComponentInterfaceInit(AtkComponentIface* iface) {
80  iface->ref_accessible_at_point = browser_accessibility_accessible_at_point;
81  iface->get_extents = browser_accessibility_get_extents;
82  iface->grab_focus = browser_accessibility_grab_focus;
83}
84
85static const GInterfaceInfo ComponentInfo = {
86  reinterpret_cast<GInterfaceInitFunc>(ComponentInterfaceInit), 0, 0
87};
88
89//
90// AtkValue interface.
91//
92
93static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
94    AtkValue* atk_object) {
95  if (!IS_BROWSER_ACCESSIBILITY(atk_object))
96    return NULL;
97
98  return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object));
99}
100
101static void browser_accessibility_get_current_value(
102    AtkValue* atk_object, GValue* value) {
103  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
104  if (!obj)
105    return;
106
107  float float_val;
108  if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE,
109                             &float_val)) {
110    memset(value, 0, sizeof(*value));
111    g_value_init(value, G_TYPE_FLOAT);
112    g_value_set_float(value, float_val);
113  }
114}
115
116static void browser_accessibility_get_minimum_value(
117    AtkValue* atk_object, GValue* value) {
118  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
119  if (!obj)
120    return;
121
122  float float_val;
123  if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE,
124                             &float_val)) {
125    memset(value, 0, sizeof(*value));
126    g_value_init(value, G_TYPE_FLOAT);
127    g_value_set_float(value, float_val);
128  }
129}
130
131static void browser_accessibility_get_maximum_value(
132    AtkValue* atk_object, GValue* value) {
133  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
134  if (!obj)
135    return;
136
137  float float_val;
138  if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE,
139                             &float_val)) {
140    memset(value, 0, sizeof(*value));
141    g_value_init(value, G_TYPE_FLOAT);
142    g_value_set_float(value, float_val);
143  }
144}
145
146static void browser_accessibility_get_minimum_increment(
147    AtkValue* atk_object, GValue* value) {
148  // TODO(dmazzoni): get the correct value from an <input type=range>.
149  memset(value, 0, sizeof(*value));
150  g_value_init(value, G_TYPE_FLOAT);
151  g_value_set_float(value, 1.0);
152}
153
154static void ValueInterfaceInit(AtkValueIface* iface) {
155  iface->get_current_value = browser_accessibility_get_current_value;
156  iface->get_minimum_value = browser_accessibility_get_minimum_value;
157  iface->get_maximum_value = browser_accessibility_get_maximum_value;
158  iface->get_minimum_increment = browser_accessibility_get_minimum_increment;
159}
160
161static const GInterfaceInfo ValueInfo = {
162  reinterpret_cast<GInterfaceInitFunc>(ValueInterfaceInit), 0, 0
163};
164
165//
166// AtkObject interface
167//
168
169static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk(
170    AtkObject* atk_object) {
171  if (!IS_BROWSER_ACCESSIBILITY(atk_object))
172    return NULL;
173
174  return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object));
175}
176
177static const gchar* browser_accessibility_get_name(AtkObject* atk_object) {
178  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
179  if (!obj)
180    return NULL;
181
182  return obj->GetStringAttribute(AccessibilityNodeData::ATTR_NAME).c_str();
183}
184
185static const gchar* browser_accessibility_get_description(
186    AtkObject* atk_object) {
187  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
188  if (!obj)
189    return NULL;
190
191  return obj->GetStringAttribute(
192      AccessibilityNodeData::ATTR_DESCRIPTION).c_str();
193}
194
195static AtkObject* browser_accessibility_get_parent(AtkObject* atk_object) {
196  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
197  if (!obj)
198    return NULL;
199  if (obj->parent())
200    return obj->parent()->ToBrowserAccessibilityGtk()->GetAtkObject();
201
202  BrowserAccessibilityManagerGtk* manager =
203      static_cast<BrowserAccessibilityManagerGtk*>(obj->manager());
204  return gtk_widget_get_accessible(manager->parent_widget());
205}
206
207static gint browser_accessibility_get_n_children(AtkObject* atk_object) {
208  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
209  if (!obj)
210    return 0;
211
212  return obj->PlatformChildCount();
213}
214
215static AtkObject* browser_accessibility_ref_child(
216    AtkObject* atk_object, gint index) {
217  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
218  if (!obj)
219    return NULL;
220
221  if (index < 0 || index >= static_cast<gint>(obj->PlatformChildCount()))
222    return NULL;
223
224  AtkObject* result =
225      obj->children()[index]->ToBrowserAccessibilityGtk()->GetAtkObject();
226  g_object_ref(result);
227  return result;
228}
229
230static gint browser_accessibility_get_index_in_parent(AtkObject* atk_object) {
231  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
232  if (!obj)
233    return 0;
234  return obj->index_in_parent();
235}
236
237static AtkAttributeSet* browser_accessibility_get_attributes(
238    AtkObject* atk_object) {
239  return NULL;
240}
241
242static AtkRole browser_accessibility_get_role(AtkObject* atk_object) {
243  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
244  if (!obj)
245    return ATK_ROLE_INVALID;
246  return obj->atk_role();
247}
248
249static AtkStateSet* browser_accessibility_ref_state_set(AtkObject* atk_object) {
250  BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object);
251  if (!obj)
252    return NULL;
253  AtkStateSet* state_set =
254      ATK_OBJECT_CLASS(browser_accessibility_parent_class)->
255          ref_state_set(atk_object);
256  int32 state = obj->state();
257
258  if (state & (1 << blink::WebAXStateFocusable))
259    atk_state_set_add_state(state_set, ATK_STATE_FOCUSABLE);
260  if (obj->manager()->GetFocus(NULL) == obj)
261    atk_state_set_add_state(state_set, ATK_STATE_FOCUSED);
262  if (state & (1 << blink::WebAXStateEnabled))
263    atk_state_set_add_state(state_set, ATK_STATE_ENABLED);
264
265  return state_set;
266}
267
268static AtkRelationSet* browser_accessibility_ref_relation_set(
269    AtkObject* atk_object) {
270  AtkRelationSet* relation_set =
271      ATK_OBJECT_CLASS(browser_accessibility_parent_class)
272          ->ref_relation_set(atk_object);
273  return relation_set;
274}
275
276//
277// The rest of the BrowserAccessibilityGtk code, not specific to one
278// of the Atk* interfaces.
279//
280
281static void browser_accessibility_init(AtkObject* atk_object, gpointer data) {
282  if (ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize) {
283    ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize(
284        atk_object, data);
285  }
286
287  BROWSER_ACCESSIBILITY(atk_object)->m_object =
288      reinterpret_cast<BrowserAccessibilityGtk*>(data);
289}
290
291static void browser_accessibility_finalize(GObject* atk_object) {
292  G_OBJECT_CLASS(browser_accessibility_parent_class)->finalize(atk_object);
293}
294
295static void browser_accessibility_class_init(AtkObjectClass* klass) {
296  GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
297  browser_accessibility_parent_class = g_type_class_peek_parent(klass);
298
299  gobject_class->finalize = browser_accessibility_finalize;
300  klass->initialize = browser_accessibility_init;
301  klass->get_name = browser_accessibility_get_name;
302  klass->get_description = browser_accessibility_get_description;
303  klass->get_parent = browser_accessibility_get_parent;
304  klass->get_n_children = browser_accessibility_get_n_children;
305  klass->ref_child = browser_accessibility_ref_child;
306  klass->get_role = browser_accessibility_get_role;
307  klass->ref_state_set = browser_accessibility_ref_state_set;
308  klass->get_index_in_parent = browser_accessibility_get_index_in_parent;
309  klass->get_attributes = browser_accessibility_get_attributes;
310  klass->ref_relation_set = browser_accessibility_ref_relation_set;
311}
312
313GType browser_accessibility_get_type() {
314  static volatile gsize type_volatile = 0;
315
316  if (g_once_init_enter(&type_volatile)) {
317    static const GTypeInfo tinfo = {
318      sizeof(BrowserAccessibilityAtkClass),
319      (GBaseInitFunc) 0,
320      (GBaseFinalizeFunc) 0,
321      (GClassInitFunc) browser_accessibility_class_init,
322      (GClassFinalizeFunc) 0,
323      0, /* class data */
324      sizeof(BrowserAccessibilityAtk), /* instance size */
325      0, /* nb preallocs */
326      (GInstanceInitFunc) 0,
327      0 /* value table */
328    };
329
330    GType type = g_type_register_static(
331        ATK_TYPE_OBJECT, "BrowserAccessibility", &tinfo, GTypeFlags(0));
332    g_once_init_leave(&type_volatile, type);
333  }
334
335  return type_volatile;
336}
337
338static const char* GetUniqueAccessibilityTypeName(int interface_mask)
339{
340  // 20 characters is enough for "Chrome%x" with any integer value.
341  static char name[20];
342  snprintf(name, sizeof(name), "Chrome%x", interface_mask);
343  return name;
344}
345
346enum AtkInterfaces {
347  ATK_ACTION_INTERFACE,
348  ATK_COMPONENT_INTERFACE,
349  ATK_DOCUMENT_INTERFACE,
350  ATK_EDITABLE_TEXT_INTERFACE,
351  ATK_HYPERLINK_INTERFACE,
352  ATK_HYPERTEXT_INTERFACE,
353  ATK_IMAGE_INTERFACE,
354  ATK_SELECTION_INTERFACE,
355  ATK_TABLE_INTERFACE,
356  ATK_TEXT_INTERFACE,
357  ATK_VALUE_INTERFACE,
358};
359
360static int GetInterfaceMaskFromObject(BrowserAccessibilityGtk* obj) {
361  int interface_mask = 0;
362
363  // Component interface is always supported.
364  interface_mask |= 1 << ATK_COMPONENT_INTERFACE;
365
366  int role = obj->role();
367  if (role == blink::WebAXRoleProgressIndicator ||
368      role == blink::WebAXRoleScrollBar ||
369      role == blink::WebAXRoleSlider) {
370    interface_mask |= 1 << ATK_VALUE_INTERFACE;
371  }
372
373  return interface_mask;
374}
375
376static GType GetAccessibilityTypeFromObject(BrowserAccessibilityGtk* obj) {
377  static const GTypeInfo type_info = {
378    sizeof(BrowserAccessibilityAtkClass),
379    (GBaseInitFunc) 0,
380    (GBaseFinalizeFunc) 0,
381    (GClassInitFunc) 0,
382    (GClassFinalizeFunc) 0,
383    0, /* class data */
384    sizeof(BrowserAccessibilityAtk), /* instance size */
385    0, /* nb preallocs */
386    (GInstanceInitFunc) 0,
387    0 /* value table */
388  };
389
390  int interface_mask = GetInterfaceMaskFromObject(obj);
391  const char* atk_type_name = GetUniqueAccessibilityTypeName(interface_mask);
392  GType type = g_type_from_name(atk_type_name);
393  if (type)
394    return type;
395
396  type = g_type_register_static(BROWSER_ACCESSIBILITY_TYPE,
397                                atk_type_name,
398                                &type_info,
399                                GTypeFlags(0));
400  if (interface_mask & (1 << ATK_COMPONENT_INTERFACE))
401    g_type_add_interface_static(type, ATK_TYPE_COMPONENT, &ComponentInfo);
402  if (interface_mask & (1 << ATK_VALUE_INTERFACE))
403    g_type_add_interface_static(type, ATK_TYPE_VALUE, &ValueInfo);
404
405  return type;
406}
407
408BrowserAccessibilityAtk* browser_accessibility_new(
409    BrowserAccessibilityGtk* obj) {
410  GType type = GetAccessibilityTypeFromObject(obj);
411  AtkObject* atk_object = static_cast<AtkObject*>(g_object_new(type, 0));
412
413  atk_object_initialize(atk_object, obj);
414
415  return BROWSER_ACCESSIBILITY(atk_object);
416}
417
418void browser_accessibility_detach(BrowserAccessibilityAtk* atk_object) {
419  atk_object->m_object = NULL;
420}
421
422// static
423BrowserAccessibility* BrowserAccessibility::Create() {
424  return new BrowserAccessibilityGtk();
425}
426
427BrowserAccessibilityGtk* BrowserAccessibility::ToBrowserAccessibilityGtk() {
428  return static_cast<BrowserAccessibilityGtk*>(this);
429}
430
431BrowserAccessibilityGtk::BrowserAccessibilityGtk()
432    : atk_object_(NULL) {
433}
434
435BrowserAccessibilityGtk::~BrowserAccessibilityGtk() {
436  browser_accessibility_detach(BROWSER_ACCESSIBILITY(atk_object_));
437  if (atk_object_)
438    g_object_unref(atk_object_);
439}
440
441AtkObject* BrowserAccessibilityGtk::GetAtkObject() const {
442  if (!G_IS_OBJECT(atk_object_))
443    return NULL;
444  return atk_object_;
445}
446
447void BrowserAccessibilityGtk::PreInitialize() {
448  BrowserAccessibility::PreInitialize();
449  InitRoleAndState();
450
451  if (atk_object_) {
452    // If the object's role changes and that causes its
453    // interface mask to change, we need to create a new
454    // AtkObject for it.
455    int interface_mask = GetInterfaceMaskFromObject(this);
456    if (interface_mask != interface_mask_) {
457      g_object_unref(atk_object_);
458      atk_object_ = NULL;
459    }
460  }
461
462  if (!atk_object_) {
463    interface_mask_ = GetInterfaceMaskFromObject(this);
464    atk_object_ = ATK_OBJECT(browser_accessibility_new(this));
465    if (this->parent()) {
466      atk_object_set_parent(
467          atk_object_,
468          this->parent()->ToBrowserAccessibilityGtk()->GetAtkObject());
469    }
470  }
471}
472
473bool BrowserAccessibilityGtk::IsNative() const {
474  return true;
475}
476
477void BrowserAccessibilityGtk::InitRoleAndState() {
478  switch(role()) {
479    case blink::WebAXRoleDocument:
480    case blink::WebAXRoleRootWebArea:
481    case blink::WebAXRoleWebArea:
482      atk_role_ = ATK_ROLE_DOCUMENT_WEB;
483      break;
484    case blink::WebAXRoleGroup:
485    case blink::WebAXRoleDiv:
486      atk_role_ = ATK_ROLE_SECTION;
487      break;
488    case blink::WebAXRoleButton:
489      atk_role_ = ATK_ROLE_PUSH_BUTTON;
490      break;
491    case blink::WebAXRoleCheckBox:
492      atk_role_ = ATK_ROLE_CHECK_BOX;
493      break;
494    case blink::WebAXRoleComboBox:
495      atk_role_ = ATK_ROLE_COMBO_BOX;
496      break;
497    case blink::WebAXRoleLink:
498      atk_role_ = ATK_ROLE_LINK;
499      break;
500    case blink::WebAXRoleRadioButton:
501      atk_role_ = ATK_ROLE_RADIO_BUTTON;
502      break;
503    case blink::WebAXRoleStaticText:
504      atk_role_ = ATK_ROLE_TEXT;
505      break;
506    case blink::WebAXRoleTextArea:
507      atk_role_ = ATK_ROLE_ENTRY;
508      break;
509    case blink::WebAXRoleTextField:
510      atk_role_ = ATK_ROLE_ENTRY;
511      break;
512    default:
513      atk_role_ = ATK_ROLE_UNKNOWN;
514      break;
515  }
516}
517
518}  // namespace content
519