• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Nuanti Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 #include "AXObjectCache.h"
22 
23 #include "AccessibilityObject.h"
24 #include "AccessibilityObjectWrapperAtk.h"
25 #include "GOwnPtr.h"
26 #include "Range.h"
27 #include "SelectElement.h"
28 #include "TextIterator.h"
29 
30 namespace WebCore {
31 
detachWrapper(AccessibilityObject * obj)32 void AXObjectCache::detachWrapper(AccessibilityObject* obj)
33 {
34     webkit_accessible_detach(WEBKIT_ACCESSIBLE(obj->wrapper()));
35 }
36 
attachWrapper(AccessibilityObject * obj)37 void AXObjectCache::attachWrapper(AccessibilityObject* obj)
38 {
39     AtkObject* atkObj = ATK_OBJECT(webkit_accessible_new(obj));
40     obj->setWrapper(atkObj);
41     g_object_unref(atkObj);
42 }
43 
getListObject(AccessibilityObject * object)44 static AccessibilityObject* getListObject(AccessibilityObject* object)
45 {
46     // Only list boxes and menu lists supported so far.
47     if (!object->isListBox() && !object->isMenuList())
48         return 0;
49 
50     // For list boxes the list object is just itself.
51     if (object->isListBox())
52         return object;
53 
54     // For menu lists we need to return the first accessible child,
55     // with role MenuListPopupRole, since that's the one holding the list
56     // of items with role MenuListOptionRole.
57     AccessibilityObject::AccessibilityChildrenVector children = object->children();
58     if (!children.size())
59         return 0;
60 
61     AccessibilityObject* listObject = children.at(0).get();
62     if (!listObject->isMenuListPopup())
63         return 0;
64 
65     return listObject;
66 }
67 
notifyChildrenSelectionChange(AccessibilityObject * object)68 static void notifyChildrenSelectionChange(AccessibilityObject* object)
69 {
70     // This static variables are needed to keep track of the old
71     // focused object and its associated list object, as per previous
72     // calls to this function, in order to properly decide whether to
73     // emit some signals or not.
74     DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldListObject, ());
75     DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldFocusedObject, ());
76 
77     // Only list boxes and menu lists supported so far.
78     if (!object || !(object->isListBox() || object->isMenuList()))
79         return;
80 
81     // Emit signal from the listbox's point of view first.
82     g_signal_emit_by_name(object->wrapper(), "selection-changed");
83 
84     // Find the item where the selection change was triggered from.
85     SelectElement* select = toSelectElement(static_cast<Element*>(object->node()));
86     if (!select)
87         return;
88     int changedItemIndex = select->activeSelectionStartListIndex();
89 
90     AccessibilityObject* listObject = getListObject(object);
91     if (!listObject) {
92         oldListObject = 0;
93         return;
94     }
95 
96     AccessibilityObject::AccessibilityChildrenVector items = listObject->children();
97     if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size()))
98         return;
99     AccessibilityObject* item = items.at(changedItemIndex).get();
100 
101     // Ensure the current list object is the same than the old one so
102     // further comparisons make sense. Otherwise, just reset
103     // oldFocusedObject so it won't be taken into account.
104     if (oldListObject != listObject)
105         oldFocusedObject = 0;
106 
107     AtkObject* axItem = item ? item->wrapper() : 0;
108     AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper() : 0;
109 
110     // Old focused object just lost focus, so emit the events.
111     if (axOldFocusedObject && axItem != axOldFocusedObject) {
112         g_signal_emit_by_name(axOldFocusedObject, "focus-event", false);
113         g_signal_emit_by_name(axOldFocusedObject, "state-change", "focused", false);
114     }
115 
116     // Emit needed events for the currently (un)selected item.
117     if (axItem) {
118         bool isSelected = item->isSelected();
119         g_signal_emit_by_name(axItem, "state-change", "selected", isSelected);
120         g_signal_emit_by_name(axItem, "focus-event", isSelected);
121         g_signal_emit_by_name(axItem, "state-change", "focused", isSelected);
122     }
123 
124     // Update pointers to the previously involved objects.
125     oldListObject = listObject;
126     oldFocusedObject = item;
127 }
128 
postPlatformNotification(AccessibilityObject * coreObject,AXNotification notification)129 void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification)
130 {
131     AtkObject* axObject = coreObject->wrapper();
132     if (!axObject)
133         return;
134 
135     if (notification == AXCheckedStateChanged) {
136         if (!coreObject->isCheckboxOrRadio())
137             return;
138         g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->isChecked());
139     } else if (notification == AXSelectedChildrenChanged || notification == AXMenuListValueChanged) {
140         if (notification == AXMenuListValueChanged && coreObject->isMenuList()) {
141             g_signal_emit_by_name(axObject, "focus-event", true);
142             g_signal_emit_by_name(axObject, "state-change", "focused", true);
143         }
144         notifyChildrenSelectionChange(coreObject);
145     } else if (notification == AXValueChanged) {
146         if (!ATK_IS_VALUE(axObject))
147             return;
148 
149         AtkPropertyValues propertyValues;
150         propertyValues.property_name = "accessible-value";
151 
152         memset(&propertyValues.new_value,  0, sizeof(GValue));
153         atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_value);
154 
155         g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible-value", &propertyValues, NULL);
156     }
157 }
158 
emitTextChanged(AccessibilityObject * object,AXObjectCache::AXTextChange textChange,unsigned offset,unsigned count)159 static void emitTextChanged(AccessibilityObject* object, AXObjectCache::AXTextChange textChange, unsigned offset, unsigned count)
160 {
161     // Get the axObject for the parent object
162     AtkObject* wrapper = object->parentObjectUnignored()->wrapper();
163     if (!wrapper || !ATK_IS_TEXT(wrapper))
164         return;
165 
166     // Select the right signal to be emitted
167     CString detail;
168     switch (textChange) {
169     case AXObjectCache::AXTextInserted:
170         detail = "text-changed::insert";
171         break;
172     case AXObjectCache::AXTextDeleted:
173         detail = "text-changed::delete";
174         break;
175     }
176 
177     if (!detail.isNull())
178         g_signal_emit_by_name(wrapper, detail.data(), offset, count);
179 }
180 
nodeTextChangePlatformNotification(AccessibilityObject * object,AXTextChange textChange,unsigned offset,unsigned count)181 void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, unsigned count)
182 {
183     // Sanity check
184     if (count < 1 || !object || !object->isAccessibilityRenderObject())
185         return;
186 
187     Node* node = object->node();
188     RefPtr<Range> range = Range::create(node->document(),  Position(node->parentNode(), 0), Position(node, 0));
189     emitTextChanged(object, textChange, offset + TextIterator::rangeLength(range.get()), count);
190 }
191 
handleFocusedUIElementChanged(RenderObject * oldFocusedRender,RenderObject * newFocusedRender)192 void AXObjectCache::handleFocusedUIElementChanged(RenderObject* oldFocusedRender, RenderObject* newFocusedRender)
193 {
194     RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedRender);
195     if (oldObject) {
196         g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false);
197         g_signal_emit_by_name(oldObject->wrapper(), "state-change", "focused", false);
198     }
199     RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedRender);
200     if (newObject) {
201         g_signal_emit_by_name(newObject->wrapper(), "focus-event", true);
202         g_signal_emit_by_name(newObject->wrapper(), "state-change", "focused", true);
203     }
204 }
205 
handleScrolledToAnchor(const Node *)206 void AXObjectCache::handleScrolledToAnchor(const Node*)
207 {
208 }
209 
210 } // namespace WebCore
211