// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/accessibility/browser_accessibility_gtk.h" #include <gtk/gtk.h> #include "base/strings/utf_string_conversions.h" #include "content/browser/accessibility/browser_accessibility_manager_gtk.h" #include "content/common/accessibility_messages.h" namespace content { static gpointer browser_accessibility_parent_class = NULL; static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( BrowserAccessibilityAtk* atk_object) { if (!atk_object) return NULL; return atk_object->m_object; } // // AtkComponent interface. // static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( AtkComponent* atk_object) { if (!IS_BROWSER_ACCESSIBILITY(atk_object)) return NULL; return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object)); } static AtkObject* browser_accessibility_accessible_at_point( AtkComponent* component, gint x, gint y, AtkCoordType coord_type) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component); if (!obj) return NULL; gfx::Point point(x, y); if (!obj->GetGlobalBoundsRect().Contains(point)) return NULL; BrowserAccessibility* result = obj->BrowserAccessibilityForPoint(point); if (!result) return NULL; AtkObject* atk_result = result->ToBrowserAccessibilityGtk()->GetAtkObject(); g_object_ref(atk_result); return atk_result; } static void browser_accessibility_get_extents( AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coord_type) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component); if (!obj) return; gfx::Rect bounds = obj->GetGlobalBoundsRect(); *x = bounds.x(); *y = bounds.y(); *width = bounds.width(); *height = bounds.height(); } static gboolean browser_accessibility_grab_focus(AtkComponent* component) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(component); if (!obj) return false; obj->manager()->SetFocus(obj, true); return true; } static void ComponentInterfaceInit(AtkComponentIface* iface) { iface->ref_accessible_at_point = browser_accessibility_accessible_at_point; iface->get_extents = browser_accessibility_get_extents; iface->grab_focus = browser_accessibility_grab_focus; } static const GInterfaceInfo ComponentInfo = { reinterpret_cast<GInterfaceInitFunc>(ComponentInterfaceInit), 0, 0 }; // // AtkValue interface. // static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( AtkValue* atk_object) { if (!IS_BROWSER_ACCESSIBILITY(atk_object)) return NULL; return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object)); } static void browser_accessibility_get_current_value( AtkValue* atk_object, GValue* value) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return; float float_val; if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_VALUE_FOR_RANGE, &float_val)) { memset(value, 0, sizeof(*value)); g_value_init(value, G_TYPE_FLOAT); g_value_set_float(value, float_val); } } static void browser_accessibility_get_minimum_value( AtkValue* atk_object, GValue* value) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return; float float_val; if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MIN_VALUE_FOR_RANGE, &float_val)) { memset(value, 0, sizeof(*value)); g_value_init(value, G_TYPE_FLOAT); g_value_set_float(value, float_val); } } static void browser_accessibility_get_maximum_value( AtkValue* atk_object, GValue* value) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return; float float_val; if (obj->GetFloatAttribute(AccessibilityNodeData::ATTR_MAX_VALUE_FOR_RANGE, &float_val)) { memset(value, 0, sizeof(*value)); g_value_init(value, G_TYPE_FLOAT); g_value_set_float(value, float_val); } } static void browser_accessibility_get_minimum_increment( AtkValue* atk_object, GValue* value) { // TODO(dmazzoni): get the correct value from an <input type=range>. memset(value, 0, sizeof(*value)); g_value_init(value, G_TYPE_FLOAT); g_value_set_float(value, 1.0); } static void ValueInterfaceInit(AtkValueIface* iface) { iface->get_current_value = browser_accessibility_get_current_value; iface->get_minimum_value = browser_accessibility_get_minimum_value; iface->get_maximum_value = browser_accessibility_get_maximum_value; iface->get_minimum_increment = browser_accessibility_get_minimum_increment; } static const GInterfaceInfo ValueInfo = { reinterpret_cast<GInterfaceInitFunc>(ValueInterfaceInit), 0, 0 }; // // AtkObject interface // static BrowserAccessibilityGtk* ToBrowserAccessibilityGtk( AtkObject* atk_object) { if (!IS_BROWSER_ACCESSIBILITY(atk_object)) return NULL; return ToBrowserAccessibilityGtk(BROWSER_ACCESSIBILITY(atk_object)); } static const gchar* browser_accessibility_get_name(AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return NULL; return obj->GetStringAttribute(AccessibilityNodeData::ATTR_NAME).c_str(); } static const gchar* browser_accessibility_get_description( AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return NULL; return obj->GetStringAttribute( AccessibilityNodeData::ATTR_DESCRIPTION).c_str(); } static AtkObject* browser_accessibility_get_parent(AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return NULL; if (obj->parent()) return obj->parent()->ToBrowserAccessibilityGtk()->GetAtkObject(); BrowserAccessibilityManagerGtk* manager = static_cast<BrowserAccessibilityManagerGtk*>(obj->manager()); return gtk_widget_get_accessible(manager->parent_widget()); } static gint browser_accessibility_get_n_children(AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return 0; return obj->PlatformChildCount(); } static AtkObject* browser_accessibility_ref_child( AtkObject* atk_object, gint index) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return NULL; if (index < 0 || index >= static_cast<gint>(obj->PlatformChildCount())) return NULL; AtkObject* result = obj->children()[index]->ToBrowserAccessibilityGtk()->GetAtkObject(); g_object_ref(result); return result; } static gint browser_accessibility_get_index_in_parent(AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return 0; return obj->index_in_parent(); } static AtkAttributeSet* browser_accessibility_get_attributes( AtkObject* atk_object) { return NULL; } static AtkRole browser_accessibility_get_role(AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return ATK_ROLE_INVALID; return obj->atk_role(); } static AtkStateSet* browser_accessibility_ref_state_set(AtkObject* atk_object) { BrowserAccessibilityGtk* obj = ToBrowserAccessibilityGtk(atk_object); if (!obj) return NULL; AtkStateSet* state_set = ATK_OBJECT_CLASS(browser_accessibility_parent_class)-> ref_state_set(atk_object); int32 state = obj->state(); if (state & (1 << blink::WebAXStateFocusable)) atk_state_set_add_state(state_set, ATK_STATE_FOCUSABLE); if (obj->manager()->GetFocus(NULL) == obj) atk_state_set_add_state(state_set, ATK_STATE_FOCUSED); if (state & (1 << blink::WebAXStateEnabled)) atk_state_set_add_state(state_set, ATK_STATE_ENABLED); return state_set; } static AtkRelationSet* browser_accessibility_ref_relation_set( AtkObject* atk_object) { AtkRelationSet* relation_set = ATK_OBJECT_CLASS(browser_accessibility_parent_class) ->ref_relation_set(atk_object); return relation_set; } // // The rest of the BrowserAccessibilityGtk code, not specific to one // of the Atk* interfaces. // static void browser_accessibility_init(AtkObject* atk_object, gpointer data) { if (ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize) { ATK_OBJECT_CLASS(browser_accessibility_parent_class)->initialize( atk_object, data); } BROWSER_ACCESSIBILITY(atk_object)->m_object = reinterpret_cast<BrowserAccessibilityGtk*>(data); } static void browser_accessibility_finalize(GObject* atk_object) { G_OBJECT_CLASS(browser_accessibility_parent_class)->finalize(atk_object); } static void browser_accessibility_class_init(AtkObjectClass* klass) { GObjectClass* gobject_class = G_OBJECT_CLASS(klass); browser_accessibility_parent_class = g_type_class_peek_parent(klass); gobject_class->finalize = browser_accessibility_finalize; klass->initialize = browser_accessibility_init; klass->get_name = browser_accessibility_get_name; klass->get_description = browser_accessibility_get_description; klass->get_parent = browser_accessibility_get_parent; klass->get_n_children = browser_accessibility_get_n_children; klass->ref_child = browser_accessibility_ref_child; klass->get_role = browser_accessibility_get_role; klass->ref_state_set = browser_accessibility_ref_state_set; klass->get_index_in_parent = browser_accessibility_get_index_in_parent; klass->get_attributes = browser_accessibility_get_attributes; klass->ref_relation_set = browser_accessibility_ref_relation_set; } GType browser_accessibility_get_type() { static volatile gsize type_volatile = 0; if (g_once_init_enter(&type_volatile)) { static const GTypeInfo tinfo = { sizeof(BrowserAccessibilityAtkClass), (GBaseInitFunc) 0, (GBaseFinalizeFunc) 0, (GClassInitFunc) browser_accessibility_class_init, (GClassFinalizeFunc) 0, 0, /* class data */ sizeof(BrowserAccessibilityAtk), /* instance size */ 0, /* nb preallocs */ (GInstanceInitFunc) 0, 0 /* value table */ }; GType type = g_type_register_static( ATK_TYPE_OBJECT, "BrowserAccessibility", &tinfo, GTypeFlags(0)); g_once_init_leave(&type_volatile, type); } return type_volatile; } static const char* GetUniqueAccessibilityTypeName(int interface_mask) { // 20 characters is enough for "Chrome%x" with any integer value. static char name[20]; snprintf(name, sizeof(name), "Chrome%x", interface_mask); return name; } enum AtkInterfaces { ATK_ACTION_INTERFACE, ATK_COMPONENT_INTERFACE, ATK_DOCUMENT_INTERFACE, ATK_EDITABLE_TEXT_INTERFACE, ATK_HYPERLINK_INTERFACE, ATK_HYPERTEXT_INTERFACE, ATK_IMAGE_INTERFACE, ATK_SELECTION_INTERFACE, ATK_TABLE_INTERFACE, ATK_TEXT_INTERFACE, ATK_VALUE_INTERFACE, }; static int GetInterfaceMaskFromObject(BrowserAccessibilityGtk* obj) { int interface_mask = 0; // Component interface is always supported. interface_mask |= 1 << ATK_COMPONENT_INTERFACE; int role = obj->role(); if (role == blink::WebAXRoleProgressIndicator || role == blink::WebAXRoleScrollBar || role == blink::WebAXRoleSlider) { interface_mask |= 1 << ATK_VALUE_INTERFACE; } return interface_mask; } static GType GetAccessibilityTypeFromObject(BrowserAccessibilityGtk* obj) { static const GTypeInfo type_info = { sizeof(BrowserAccessibilityAtkClass), (GBaseInitFunc) 0, (GBaseFinalizeFunc) 0, (GClassInitFunc) 0, (GClassFinalizeFunc) 0, 0, /* class data */ sizeof(BrowserAccessibilityAtk), /* instance size */ 0, /* nb preallocs */ (GInstanceInitFunc) 0, 0 /* value table */ }; int interface_mask = GetInterfaceMaskFromObject(obj); const char* atk_type_name = GetUniqueAccessibilityTypeName(interface_mask); GType type = g_type_from_name(atk_type_name); if (type) return type; type = g_type_register_static(BROWSER_ACCESSIBILITY_TYPE, atk_type_name, &type_info, GTypeFlags(0)); if (interface_mask & (1 << ATK_COMPONENT_INTERFACE)) g_type_add_interface_static(type, ATK_TYPE_COMPONENT, &ComponentInfo); if (interface_mask & (1 << ATK_VALUE_INTERFACE)) g_type_add_interface_static(type, ATK_TYPE_VALUE, &ValueInfo); return type; } BrowserAccessibilityAtk* browser_accessibility_new( BrowserAccessibilityGtk* obj) { GType type = GetAccessibilityTypeFromObject(obj); AtkObject* atk_object = static_cast<AtkObject*>(g_object_new(type, 0)); atk_object_initialize(atk_object, obj); return BROWSER_ACCESSIBILITY(atk_object); } void browser_accessibility_detach(BrowserAccessibilityAtk* atk_object) { atk_object->m_object = NULL; } // static BrowserAccessibility* BrowserAccessibility::Create() { return new BrowserAccessibilityGtk(); } BrowserAccessibilityGtk* BrowserAccessibility::ToBrowserAccessibilityGtk() { return static_cast<BrowserAccessibilityGtk*>(this); } BrowserAccessibilityGtk::BrowserAccessibilityGtk() : atk_object_(NULL) { } BrowserAccessibilityGtk::~BrowserAccessibilityGtk() { browser_accessibility_detach(BROWSER_ACCESSIBILITY(atk_object_)); if (atk_object_) g_object_unref(atk_object_); } AtkObject* BrowserAccessibilityGtk::GetAtkObject() const { if (!G_IS_OBJECT(atk_object_)) return NULL; return atk_object_; } void BrowserAccessibilityGtk::PreInitialize() { BrowserAccessibility::PreInitialize(); InitRoleAndState(); if (atk_object_) { // If the object's role changes and that causes its // interface mask to change, we need to create a new // AtkObject for it. int interface_mask = GetInterfaceMaskFromObject(this); if (interface_mask != interface_mask_) { g_object_unref(atk_object_); atk_object_ = NULL; } } if (!atk_object_) { interface_mask_ = GetInterfaceMaskFromObject(this); atk_object_ = ATK_OBJECT(browser_accessibility_new(this)); if (this->parent()) { atk_object_set_parent( atk_object_, this->parent()->ToBrowserAccessibilityGtk()->GetAtkObject()); } } } bool BrowserAccessibilityGtk::IsNative() const { return true; } void BrowserAccessibilityGtk::InitRoleAndState() { switch(role()) { case blink::WebAXRoleDocument: case blink::WebAXRoleRootWebArea: case blink::WebAXRoleWebArea: atk_role_ = ATK_ROLE_DOCUMENT_WEB; break; case blink::WebAXRoleGroup: case blink::WebAXRoleDiv: atk_role_ = ATK_ROLE_SECTION; break; case blink::WebAXRoleButton: atk_role_ = ATK_ROLE_PUSH_BUTTON; break; case blink::WebAXRoleCheckBox: atk_role_ = ATK_ROLE_CHECK_BOX; break; case blink::WebAXRoleComboBox: atk_role_ = ATK_ROLE_COMBO_BOX; break; case blink::WebAXRoleLink: atk_role_ = ATK_ROLE_LINK; break; case blink::WebAXRoleRadioButton: atk_role_ = ATK_ROLE_RADIO_BUTTON; break; case blink::WebAXRoleStaticText: atk_role_ = ATK_ROLE_TEXT; break; case blink::WebAXRoleTextArea: atk_role_ = ATK_ROLE_ENTRY; break; case blink::WebAXRoleTextField: atk_role_ = ATK_ROLE_ENTRY; break; default: atk_role_ = ATK_ROLE_UNKNOWN; break; } } } // namespace content