OLD | NEW |
| (Empty) |
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 #if HAVE(ACCESSIBILITY) | |
24 | |
25 #include "AccessibilityObject.h" | |
26 #include "AccessibilityRenderObject.h" | |
27 #include "Document.h" | |
28 #include "Element.h" | |
29 #include "HTMLSelectElement.h" | |
30 #include "Range.h" | |
31 #include "TextIterator.h" | |
32 #include "WebKitAccessibleWrapperAtk.h" | |
33 #include <wtf/gobject/GOwnPtr.h> | |
34 | |
35 namespace WebCore { | |
36 | |
37 void AXObjectCache::detachWrapper(AccessibilityObject* obj) | |
38 { | |
39 webkitAccessibleDetach(WEBKIT_ACCESSIBLE(obj->wrapper())); | |
40 } | |
41 | |
42 void AXObjectCache::attachWrapper(AccessibilityObject* obj) | |
43 { | |
44 AtkObject* atkObj = ATK_OBJECT(webkitAccessibleNew(obj)); | |
45 obj->setWrapper(atkObj); | |
46 g_object_unref(atkObj); | |
47 } | |
48 | |
49 static AccessibilityObject* getListObject(AccessibilityObject* object) | |
50 { | |
51 // Only list boxes and menu lists supported so far. | |
52 if (!object->isListBox() && !object->isMenuList()) | |
53 return 0; | |
54 | |
55 // For list boxes the list object is just itself. | |
56 if (object->isListBox()) | |
57 return object; | |
58 | |
59 // For menu lists we need to return the first accessible child, | |
60 // with role MenuListPopupRole, since that's the one holding the list | |
61 // of items with role MenuListOptionRole. | |
62 AccessibilityObject::AccessibilityChildrenVector children = object->children
(); | |
63 if (!children.size()) | |
64 return 0; | |
65 | |
66 AccessibilityObject* listObject = children.at(0).get(); | |
67 if (!listObject->isMenuListPopup()) | |
68 return 0; | |
69 | |
70 return listObject; | |
71 } | |
72 | |
73 static void notifyChildrenSelectionChange(AccessibilityObject* object) | |
74 { | |
75 // This static variables are needed to keep track of the old | |
76 // focused object and its associated list object, as per previous | |
77 // calls to this function, in order to properly decide whether to | |
78 // emit some signals or not. | |
79 DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldListObject, ()); | |
80 DEFINE_STATIC_LOCAL(RefPtr<AccessibilityObject>, oldFocusedObject, ()); | |
81 | |
82 // Only list boxes and menu lists supported so far. | |
83 if (!object || !(object->isListBox() || object->isMenuList())) | |
84 return; | |
85 | |
86 // Only support HTML select elements so far (ARIA selectors not supported). | |
87 Node* node = object->node(); | |
88 if (!node || !node->hasTagName(HTMLNames::selectTag)) | |
89 return; | |
90 | |
91 // Emit signal from the listbox's point of view first. | |
92 g_signal_emit_by_name(object->wrapper(), "selection-changed"); | |
93 | |
94 // Find the item where the selection change was triggered from. | |
95 HTMLSelectElement* select = toHTMLSelectElement(node); | |
96 if (!select) | |
97 return; | |
98 int changedItemIndex = select->activeSelectionStartListIndex(); | |
99 | |
100 AccessibilityObject* listObject = getListObject(object); | |
101 if (!listObject) { | |
102 oldListObject = 0; | |
103 return; | |
104 } | |
105 | |
106 AccessibilityObject::AccessibilityChildrenVector items = listObject->childre
n(); | |
107 if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size(
))) | |
108 return; | |
109 AccessibilityObject* item = items.at(changedItemIndex).get(); | |
110 | |
111 // Ensure the current list object is the same than the old one so | |
112 // further comparisons make sense. Otherwise, just reset | |
113 // oldFocusedObject so it won't be taken into account. | |
114 if (oldListObject != listObject) | |
115 oldFocusedObject = 0; | |
116 | |
117 AtkObject* axItem = item ? item->wrapper() : 0; | |
118 AtkObject* axOldFocusedObject = oldFocusedObject ? oldFocusedObject->wrapper
() : 0; | |
119 | |
120 // Old focused object just lost focus, so emit the events. | |
121 if (axOldFocusedObject && axItem != axOldFocusedObject) { | |
122 g_signal_emit_by_name(axOldFocusedObject, "focus-event", false); | |
123 g_signal_emit_by_name(axOldFocusedObject, "state-change", "focused", fal
se); | |
124 } | |
125 | |
126 // Emit needed events for the currently (un)selected item. | |
127 if (axItem) { | |
128 bool isSelected = item->isSelected(); | |
129 g_signal_emit_by_name(axItem, "state-change", "selected", isSelected); | |
130 g_signal_emit_by_name(axItem, "focus-event", isSelected); | |
131 g_signal_emit_by_name(axItem, "state-change", "focused", isSelected); | |
132 } | |
133 | |
134 // Update pointers to the previously involved objects. | |
135 oldListObject = listObject; | |
136 oldFocusedObject = item; | |
137 } | |
138 | |
139 void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AX
Notification notification) | |
140 { | |
141 AtkObject* axObject = coreObject->wrapper(); | |
142 if (!axObject) | |
143 return; | |
144 | |
145 if (notification == AXCheckedStateChanged) { | |
146 if (!coreObject->isCheckboxOrRadio()) | |
147 return; | |
148 g_signal_emit_by_name(axObject, "state-change", "checked", coreObject->i
sChecked()); | |
149 } else if (notification == AXSelectedChildrenChanged || notification == AXMe
nuListValueChanged) { | |
150 if (notification == AXMenuListValueChanged && coreObject->isMenuList())
{ | |
151 g_signal_emit_by_name(axObject, "focus-event", true); | |
152 g_signal_emit_by_name(axObject, "state-change", "focused", true); | |
153 } | |
154 notifyChildrenSelectionChange(coreObject); | |
155 } else if (notification == AXValueChanged) { | |
156 if (!ATK_IS_VALUE(axObject)) | |
157 return; | |
158 | |
159 AtkPropertyValues propertyValues; | |
160 propertyValues.property_name = "accessible-value"; | |
161 | |
162 memset(&propertyValues.new_value, 0, sizeof(GValue)); | |
163 atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_val
ue); | |
164 | |
165 g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible
-value", &propertyValues, NULL); | |
166 } | |
167 } | |
168 | |
169 void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* obje
ct, AXTextChange textChange, unsigned offset, const String& text) | |
170 { | |
171 if (!object || text.isEmpty()) | |
172 return; | |
173 | |
174 AccessibilityObject* parentObject = object->parentObjectUnignored(); | |
175 if (!parentObject) | |
176 return; | |
177 | |
178 AtkObject* wrapper = parentObject->wrapper(); | |
179 if (!wrapper || !ATK_IS_TEXT(wrapper)) | |
180 return; | |
181 | |
182 Node* node = object->node(); | |
183 if (!node) | |
184 return; | |
185 | |
186 // Ensure document's layout is up-to-date before using TextIterator. | |
187 Document* document = node->document(); | |
188 document->updateLayout(); | |
189 | |
190 // Select the right signal to be emitted | |
191 CString detail; | |
192 switch (textChange) { | |
193 case AXObjectCache::AXTextInserted: | |
194 detail = "text-insert"; | |
195 break; | |
196 case AXObjectCache::AXTextDeleted: | |
197 detail = "text-remove"; | |
198 break; | |
199 } | |
200 | |
201 String textToEmit = text; | |
202 unsigned offsetToEmit = offset; | |
203 | |
204 // If the object we're emitting the signal from represents a | |
205 // password field, we will emit the masked text. | |
206 if (parentObject->isPasswordField()) { | |
207 String maskedText = parentObject->passwordFieldValue(); | |
208 textToEmit = maskedText.substring(offset, text.length()); | |
209 } else { | |
210 // Consider previous text objects that might be present for | |
211 // the current accessibility object to ensure we emit the | |
212 // right offset (e.g. multiline text areas). | |
213 RefPtr<Range> range = Range::create(document, node->parentNode(), 0, nod
e, 0); | |
214 offsetToEmit = offset + TextIterator::rangeLength(range.get()); | |
215 } | |
216 | |
217 g_signal_emit_by_name(wrapper, detail.data(), offsetToEmit, textToEmit.lengt
h(), textToEmit.utf8().data()); | |
218 } | |
219 | |
220 void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* o
bject, AXLoadingEvent loadingEvent) | |
221 { | |
222 if (!object) | |
223 return; | |
224 | |
225 AtkObject* axObject = object->wrapper(); | |
226 if (!axObject || !ATK_IS_DOCUMENT(axObject)) | |
227 return; | |
228 | |
229 switch (loadingEvent) { | |
230 case AXObjectCache::AXLoadingStarted: | |
231 g_signal_emit_by_name(axObject, "state-change", "busy", true); | |
232 break; | |
233 case AXObjectCache::AXLoadingReloaded: | |
234 g_signal_emit_by_name(axObject, "state-change", "busy", true); | |
235 g_signal_emit_by_name(axObject, "reload"); | |
236 break; | |
237 case AXObjectCache::AXLoadingFailed: | |
238 g_signal_emit_by_name(axObject, "load-stopped"); | |
239 g_signal_emit_by_name(axObject, "state-change", "busy", false); | |
240 break; | |
241 case AXObjectCache::AXLoadingFinished: | |
242 g_signal_emit_by_name(axObject, "load-complete"); | |
243 g_signal_emit_by_name(axObject, "state-change", "busy", false); | |
244 break; | |
245 } | |
246 } | |
247 | |
248 void AXObjectCache::handleFocusedUIElementChanged(Node* oldFocusedNode, Node* ne
wFocusedNode) | |
249 { | |
250 RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedNode); | |
251 if (oldObject) { | |
252 g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false); | |
253 g_signal_emit_by_name(oldObject->wrapper(), "state-change", "focused", f
alse); | |
254 } | |
255 RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedNode); | |
256 if (newObject) { | |
257 g_signal_emit_by_name(newObject->wrapper(), "focus-event", true); | |
258 g_signal_emit_by_name(newObject->wrapper(), "state-change", "focused", t
rue); | |
259 } | |
260 } | |
261 | |
262 void AXObjectCache::handleScrolledToAnchor(const Node*) | |
263 { | |
264 } | |
265 | |
266 } // namespace WebCore | |
267 | |
268 #endif | |
OLD | NEW |