Have is<>(T*) function do a null check on the pointer argument
[WebKit-https.git] / Source / WebCore / accessibility / atk / AXObjectCacheAtk.cpp
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/NeverDestroyed.h>
34 #include <wtf/gobject/GRefPtr.h>
35 #include <wtf/text/CString.h>
36
37 namespace WebCore {
38
39 void AXObjectCache::detachWrapper(AccessibilityObject* obj, AccessibilityDetachmentType detachmentType)
40 {
41     AtkObject* wrapper = obj->wrapper();
42     ASSERT(wrapper);
43
44     // If an object is being detached NOT because of the AXObjectCache being destroyed,
45     // then it's being removed from the accessibility tree and we should emit a signal.
46     if (detachmentType != CacheDestroyed) {
47         if (obj->document()) {
48             // Look for the right object to emit the signal from, but using the implementation
49             // of atk_object_get_parent from AtkObject class (which uses a cached pointer if set)
50             // since the accessibility hierarchy in WebCore will no longer be navigable.
51             gpointer webkitAccessibleClass = g_type_class_peek_parent(WEBKIT_ACCESSIBLE_GET_CLASS(wrapper));
52             gpointer atkObjectClass = g_type_class_peek_parent(webkitAccessibleClass);
53             AtkObject* atkParent = ATK_OBJECT_CLASS(atkObjectClass)->get_parent(ATK_OBJECT(wrapper));
54
55             // We don't want to emit any signal from an object outside WebKit's world.
56             if (WEBKIT_IS_ACCESSIBLE(atkParent)) {
57                 // The accessibility hierarchy is already invalid, so the parent-children relationships
58                 // in the AccessibilityObject tree are not there anymore, so we can't know the offset.
59                 g_signal_emit_by_name(atkParent, "children-changed::remove", -1, wrapper);
60             }
61         }
62     }
63
64     webkitAccessibleDetach(WEBKIT_ACCESSIBLE(wrapper));
65 }
66
67 void AXObjectCache::attachWrapper(AccessibilityObject* obj)
68 {
69     AtkObject* atkObj = ATK_OBJECT(webkitAccessibleNew(obj));
70     obj->setWrapper(atkObj);
71     g_object_unref(atkObj);
72
73     // If an object is being attached and we are not in the middle of a layout update, then
74     // we should report ATs by emitting the children-changed::add signal from the parent.
75     Document* document = obj->document();
76     if (!document || document->childNeedsStyleRecalc())
77         return;
78
79     // Don't emit the signal when the actual object being added is not going to be exposed.
80     if (obj->accessibilityIsIgnoredByDefault())
81         return;
82
83     // Don't emit the signal for objects whose parents won't be exposed directly.
84     AccessibilityObject* coreParent = obj->parentObjectUnignored();
85     if (!coreParent || coreParent->accessibilityIsIgnoredByDefault())
86         return;
87
88     // Look for the right object to emit the signal from.
89     AtkObject* atkParent = coreParent->wrapper();
90     if (!atkParent)
91         return;
92
93     size_t index = coreParent->children(false).find(obj);
94     g_signal_emit_by_name(atkParent, "children-changed::add", index, atkObj);
95 }
96
97 static AccessibilityObject* getListObject(AccessibilityObject* object)
98 {
99     // Only list boxes and menu lists supported so far.
100     if (!object->isListBox() && !object->isMenuList())
101         return 0;
102
103     // For list boxes the list object is just itself.
104     if (object->isListBox())
105         return object;
106
107     // For menu lists we need to return the first accessible child,
108     // with role MenuListPopupRole, since that's the one holding the list
109     // of items with role MenuListOptionRole.
110     const AccessibilityObject::AccessibilityChildrenVector& children = object->children();
111     if (!children.size())
112         return 0;
113
114     AccessibilityObject* listObject = children.at(0).get();
115     if (!listObject->isMenuListPopup())
116         return 0;
117
118     return listObject;
119 }
120
121 static void notifyChildrenSelectionChange(AccessibilityObject* object)
122 {
123     // This static variables are needed to keep track of the old
124     // focused object and its associated list object, as per previous
125     // calls to this function, in order to properly decide whether to
126     // emit some signals or not.
127     static NeverDestroyed<RefPtr<AccessibilityObject>> oldListObject;
128     static NeverDestroyed<RefPtr<AccessibilityObject>> oldFocusedObject;
129
130     // Only list boxes and menu lists supported so far.
131     if (!object || !(object->isListBox() || object->isMenuList()))
132         return;
133
134     // Only support HTML select elements so far (ARIA selectors not supported).
135     Node* node = object->node();
136     if (!is<HTMLSelectElement>(node))
137         return;
138
139     // Emit signal from the listbox's point of view first.
140     g_signal_emit_by_name(object->wrapper(), "selection-changed");
141
142     // Find the item where the selection change was triggered from.
143     HTMLSelectElement& select = downcast<HTMLSelectElement>(*node);
144     int changedItemIndex = select.activeSelectionStartListIndex();
145
146     AccessibilityObject* listObject = getListObject(object);
147     if (!listObject) {
148         oldListObject.get() = 0;
149         return;
150     }
151
152     const AccessibilityObject::AccessibilityChildrenVector& items = listObject->children();
153     if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size()))
154         return;
155     AccessibilityObject* item = items.at(changedItemIndex).get();
156
157     // Ensure the current list object is the same than the old one so
158     // further comparisons make sense. Otherwise, just reset
159     // oldFocusedObject so it won't be taken into account.
160     if (oldListObject.get() != listObject)
161         oldFocusedObject.get() = 0;
162
163     AtkObject* axItem = item ? item->wrapper() : 0;
164     AtkObject* axOldFocusedObject = oldFocusedObject.get() ? oldFocusedObject.get()->wrapper() : 0;
165
166     // Old focused object just lost focus, so emit the events.
167     if (axOldFocusedObject && axItem != axOldFocusedObject) {
168         g_signal_emit_by_name(axOldFocusedObject, "focus-event", false);
169         atk_object_notify_state_change(axOldFocusedObject, ATK_STATE_FOCUSED, false);
170     }
171
172     // Emit needed events for the currently (un)selected item.
173     if (axItem) {
174         bool isSelected = item->isSelected();
175         atk_object_notify_state_change(axItem, ATK_STATE_SELECTED, isSelected);
176         g_signal_emit_by_name(axItem, "focus-event", isSelected);
177         atk_object_notify_state_change(axItem, ATK_STATE_FOCUSED, isSelected);
178     }
179
180     // Update pointers to the previously involved objects.
181     oldListObject.get() = listObject;
182     oldFocusedObject.get() = item;
183 }
184
185 void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification)
186 {
187     AtkObject* axObject = coreObject->wrapper();
188     if (!axObject)
189         return;
190
191     switch (notification) {
192     case AXCheckedStateChanged:
193         if (!coreObject->isCheckboxOrRadio())
194             return;
195         atk_object_notify_state_change(axObject, ATK_STATE_CHECKED, coreObject->isChecked());
196         break;
197
198     case AXSelectedChildrenChanged:
199     case AXMenuListValueChanged:
200         if (notification == AXMenuListValueChanged && coreObject->isMenuList()) {
201             g_signal_emit_by_name(axObject, "focus-event", true);
202             atk_object_notify_state_change(axObject, ATK_STATE_FOCUSED, true);
203         }
204         notifyChildrenSelectionChange(coreObject);
205         break;
206
207     case AXValueChanged:
208         if (ATK_IS_VALUE(axObject)) {
209             AtkPropertyValues propertyValues;
210             propertyValues.property_name = "accessible-value";
211
212             memset(&propertyValues.new_value,  0, sizeof(GValue));
213 #if ATK_CHECK_VERSION(2,11,92)
214             double value;
215             atk_value_get_value_and_text(ATK_VALUE(axObject), &value, nullptr);
216             g_value_set_double(g_value_init(&propertyValues.new_value, G_TYPE_DOUBLE), value);
217 #else
218             atk_value_get_current_value(ATK_VALUE(axObject), &propertyValues.new_value);
219 #endif
220
221             g_signal_emit_by_name(ATK_OBJECT(axObject), "property-change::accessible-value", &propertyValues, NULL);
222         }
223         break;
224
225     case AXInvalidStatusChanged:
226         atk_object_notify_state_change(axObject, ATK_STATE_INVALID_ENTRY, coreObject->invalidStatus() != "false");
227         break;
228
229     default:
230         break;
231     }
232 }
233
234 void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, const String& text)
235 {
236     if (!object || text.isEmpty())
237         return;
238
239     AccessibilityObject* parentObject = object->parentObjectUnignored();
240     if (!parentObject)
241         return;
242
243     AtkObject* wrapper = parentObject->wrapper();
244     if (!wrapper || !ATK_IS_TEXT(wrapper))
245         return;
246
247     Node* node = object->node();
248     if (!node)
249         return;
250
251     // Ensure document's layout is up-to-date before using TextIterator.
252     Document& document = node->document();
253     document.updateLayout();
254
255     // Select the right signal to be emitted
256     CString detail;
257     switch (textChange) {
258     case AXObjectCache::AXTextInserted:
259         detail = "text-insert";
260         break;
261     case AXObjectCache::AXTextDeleted:
262         detail = "text-remove";
263         break;
264     }
265
266     String textToEmit = text;
267     unsigned offsetToEmit = offset;
268
269     // If the object we're emitting the signal from represents a
270     // password field, we will emit the masked text.
271     if (parentObject->isPasswordField()) {
272         String maskedText = parentObject->passwordFieldValue();
273         textToEmit = maskedText.substring(offset, text.length());
274     } else {
275         // Consider previous text objects that might be present for
276         // the current accessibility object to ensure we emit the
277         // right offset (e.g. multiline text areas).
278         RefPtr<Range> range = Range::create(document, node->parentNode(), 0, node, 0);
279         offsetToEmit = offset + TextIterator::rangeLength(range.get());
280     }
281
282     g_signal_emit_by_name(wrapper, detail.data(), offsetToEmit, textToEmit.length(), textToEmit.utf8().data());
283 }
284
285 void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* object, AXLoadingEvent loadingEvent)
286 {
287     if (!object)
288         return;
289
290     AtkObject* axObject = object->wrapper();
291     if (!axObject || !ATK_IS_DOCUMENT(axObject))
292         return;
293
294     switch (loadingEvent) {
295     case AXObjectCache::AXLoadingStarted:
296         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, true);
297         break;
298     case AXObjectCache::AXLoadingReloaded:
299         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, true);
300         g_signal_emit_by_name(axObject, "reload");
301         break;
302     case AXObjectCache::AXLoadingFailed:
303         g_signal_emit_by_name(axObject, "load-stopped");
304         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, false);
305         break;
306     case AXObjectCache::AXLoadingFinished:
307         g_signal_emit_by_name(axObject, "load-complete");
308         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, false);
309         break;
310     }
311 }
312
313 void AXObjectCache::platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode)
314 {
315     RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedNode);
316     if (oldObject) {
317         g_signal_emit_by_name(oldObject->wrapper(), "focus-event", false);
318         atk_object_notify_state_change(oldObject->wrapper(), ATK_STATE_FOCUSED, false);
319     }
320     RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedNode);
321     if (newObject) {
322         g_signal_emit_by_name(newObject->wrapper(), "focus-event", true);
323         atk_object_notify_state_change(newObject->wrapper(), ATK_STATE_FOCUSED, true);
324     }
325 }
326
327 void AXObjectCache::handleScrolledToAnchor(const Node*)
328 {
329 }
330
331 } // namespace WebCore
332
333 #endif