[WPE][GTK] Bump minimum versions of GLib, GTK, libsoup, ATK, GStreamer, and Cairo
[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 "WebKitAccessible.h"
33 #include <wtf/NeverDestroyed.h>
34 #include <wtf/glib/GRefPtr.h>
35 #include <wtf/text/CString.h>
36
37 namespace WebCore {
38
39 static AtkObject* wrapperParent(WebKitAccessible* wrapper)
40 {
41     // Look for the right object to emit the signal from, but using the implementation
42     // of atk_object_get_parent from AtkObject class (which uses a cached pointer if set)
43     // since the accessibility hierarchy in WebCore will no longer be navigable.
44     gpointer webkitAccessibleClass = g_type_class_peek_parent(WEBKIT_ACCESSIBLE_GET_CLASS(wrapper));
45     gpointer atkObjectClass = g_type_class_peek_parent(webkitAccessibleClass);
46     AtkObject* atkParent = ATK_OBJECT_CLASS(atkObjectClass)->get_parent(ATK_OBJECT(wrapper));
47     // We don't want to emit any signal from an object outside WebKit's world.
48     return WEBKIT_IS_ACCESSIBLE(atkParent) ? atkParent : nullptr;
49 }
50
51 void AXObjectCache::detachWrapper(AccessibilityObject* obj, AccessibilityDetachmentType detachmentType)
52 {
53     auto* wrapper = obj->wrapper();
54     ASSERT(wrapper);
55
56     // If an object is being detached NOT because of the AXObjectCache being destroyed,
57     // then it's being removed from the accessibility tree and we should emit a signal.
58     if (detachmentType != AccessibilityDetachmentType::CacheDestroyed && obj->document() && wrapperParent(wrapper))
59         m_deferredDetachedWrapperList.add(wrapper);
60
61     webkitAccessibleDetach(WEBKIT_ACCESSIBLE(wrapper));
62 }
63
64 void AXObjectCache::attachWrapper(AccessibilityObject* obj)
65 {
66     GRefPtr<WebKitAccessible> wrapper = adoptGRef(webkitAccessibleNew(obj));
67     obj->setWrapper(wrapper.get());
68
69     // If an object is being attached and we are not in the middle of a layout update, then
70     // we should report ATs by emitting the children-changed::add signal from the parent.
71     Document* document = obj->document();
72     if (!document || document->childNeedsStyleRecalc())
73         return;
74
75     // Don't emit the signal when the actual object being added is not going to be exposed.
76     if (obj->accessibilityIsIgnoredByDefault())
77         return;
78
79     // Don't emit the signal if the object being added is not -- or not yet -- rendered,
80     // which can occur in nested iframes. In these instances we don't want to ignore the
81     // child. But if an assistive technology is listening, AT-SPI2 will attempt to create
82     // and cache the state set for the child upon emission of the signal. If the object
83     // has not yet been rendered, this will result in a crash.
84     if (!obj->renderer())
85         return;
86
87     m_deferredAttachedWrapperObjectList.add(obj);
88 }
89
90 void AXObjectCache::platformPerformDeferredCacheUpdate()
91 {
92     for (auto& coreObject : m_deferredAttachedWrapperObjectList) {
93         auto* wrapper = coreObject->wrapper();
94         if (!wrapper)
95             continue;
96
97         // Don't emit the signal for objects whose parents won't be exposed directly.
98         auto* coreParent = coreObject->parentObjectUnignored();
99         if (!coreParent || coreParent->accessibilityIsIgnoredByDefault())
100             continue;
101
102         // Look for the right object to emit the signal from.
103         auto* atkParent = coreParent->wrapper();
104         if (!atkParent)
105             continue;
106
107         size_t index = coreParent->children(false).find(coreObject);
108         g_signal_emit_by_name(atkParent, "children-changed::add", index != notFound ? index : -1, wrapper);
109     }
110     m_deferredAttachedWrapperObjectList.clear();
111
112     for (auto& wrapper : m_deferredDetachedWrapperList) {
113         if (auto* atkParent = wrapperParent(wrapper.get())) {
114             // The accessibility hierarchy is already invalid, so the parent-children relationships
115             // in the AccessibilityObject tree are not there anymore, so we can't know the offset.
116             g_signal_emit_by_name(atkParent, "children-changed::remove", -1, wrapper.get());
117         }
118     }
119     m_deferredDetachedWrapperList.clear();
120 }
121
122 static AccessibilityObject* getListObject(AccessibilityObject* object)
123 {
124     // Only list boxes and menu lists supported so far.
125     if (!object->isListBox() && !object->isMenuList())
126         return 0;
127
128     // For list boxes the list object is just itself.
129     if (object->isListBox())
130         return object;
131
132     // For menu lists we need to return the first accessible child,
133     // with role MenuListPopupRole, since that's the one holding the list
134     // of items with role MenuListOptionRole.
135     const AccessibilityObject::AccessibilityChildrenVector& children = object->children();
136     if (!children.size())
137         return 0;
138
139     AccessibilityObject* listObject = children.at(0).get();
140     if (!listObject->isMenuListPopup())
141         return 0;
142
143     return listObject;
144 }
145
146 static void notifyChildrenSelectionChange(AccessibilityObject* object)
147 {
148     // This static variables are needed to keep track of the old
149     // focused object and its associated list object, as per previous
150     // calls to this function, in order to properly decide whether to
151     // emit some signals or not.
152     static NeverDestroyed<RefPtr<AccessibilityObject>> oldListObject;
153     static NeverDestroyed<RefPtr<AccessibilityObject>> oldFocusedObject;
154
155     // Only list boxes and menu lists supported so far.
156     if (!object || !(object->isListBox() || object->isMenuList()))
157         return;
158
159     // Only support HTML select elements so far (ARIA selectors not supported).
160     Node* node = object->node();
161     if (!is<HTMLSelectElement>(node))
162         return;
163
164     // Emit signal from the listbox's point of view first.
165     g_signal_emit_by_name(object->wrapper(), "selection-changed");
166
167     // Find the item where the selection change was triggered from.
168     HTMLSelectElement& select = downcast<HTMLSelectElement>(*node);
169     int changedItemIndex = select.activeSelectionStartListIndex();
170
171     AccessibilityObject* listObject = getListObject(object);
172     if (!listObject) {
173         oldListObject.get() = nullptr;
174         return;
175     }
176
177     const AccessibilityObject::AccessibilityChildrenVector& items = listObject->children();
178     if (changedItemIndex < 0 || changedItemIndex >= static_cast<int>(items.size()))
179         return;
180     AccessibilityObject* item = items.at(changedItemIndex).get();
181
182     // Ensure the current list object is the same than the old one so
183     // further comparisons make sense. Otherwise, just reset
184     // oldFocusedObject so it won't be taken into account.
185     if (oldListObject.get() != listObject)
186         oldFocusedObject.get() = nullptr;
187
188     WebKitAccessible* axItem = item ? item->wrapper() : nullptr;
189     WebKitAccessible* axOldFocusedObject = oldFocusedObject.get() ? oldFocusedObject.get()->wrapper() : nullptr;
190
191     // Old focused object just lost focus, so emit the events.
192     if (axOldFocusedObject && axItem != axOldFocusedObject) {
193         g_signal_emit_by_name(axOldFocusedObject, "focus-event", false);
194         atk_object_notify_state_change(ATK_OBJECT(axOldFocusedObject), ATK_STATE_FOCUSED, false);
195     }
196
197     // Emit needed events for the currently (un)selected item.
198     if (axItem) {
199         bool isSelected = item->isSelected();
200         atk_object_notify_state_change(ATK_OBJECT(axItem), ATK_STATE_SELECTED, isSelected);
201         // When the selection changes in a collapsed widget such as a combo box
202         // whose child menu is not showing, that collapsed widget retains focus.
203         if (!object->isCollapsed()) {
204             g_signal_emit_by_name(axItem, "focus-event", isSelected);
205             atk_object_notify_state_change(ATK_OBJECT(axItem), ATK_STATE_FOCUSED, isSelected);
206         }
207     }
208
209     // Update pointers to the previously involved objects.
210     oldListObject.get() = listObject;
211     oldFocusedObject.get() = item;
212 }
213
214 void AXObjectCache::postPlatformNotification(AccessibilityObject* coreObject, AXNotification notification)
215 {
216     auto* axObject = ATK_OBJECT(coreObject->wrapper());
217     if (!axObject)
218         return;
219
220     switch (notification) {
221     case AXCheckedStateChanged:
222         if (!coreObject->isCheckboxOrRadio() && !coreObject->isSwitch())
223             return;
224         atk_object_notify_state_change(axObject, ATK_STATE_CHECKED, coreObject->isChecked());
225         break;
226
227     case AXSelectedChildrenChanged:
228     case AXMenuListValueChanged:
229         // Accessible focus claims should not be made if the associated widget is not focused.
230         if (notification == AXMenuListValueChanged && coreObject->isMenuList() && coreObject->isFocused()) {
231             g_signal_emit_by_name(axObject, "focus-event", true);
232             atk_object_notify_state_change(axObject, ATK_STATE_FOCUSED, true);
233         }
234         notifyChildrenSelectionChange(coreObject);
235         break;
236
237     case AXValueChanged:
238         if (ATK_IS_VALUE(axObject)) {
239             AtkPropertyValues propertyValues;
240             propertyValues.property_name = "accessible-value";
241
242             memset(&propertyValues.new_value,  0, sizeof(GValue));
243
244             double value;
245             atk_value_get_value_and_text(ATK_VALUE(axObject), &value, nullptr);
246             g_value_set_double(g_value_init(&propertyValues.new_value, G_TYPE_DOUBLE), value);
247
248             g_signal_emit_by_name(axObject, "property-change::accessible-value", &propertyValues, NULL);
249         }
250         break;
251
252     case AXInvalidStatusChanged:
253         atk_object_notify_state_change(axObject, ATK_STATE_INVALID_ENTRY, coreObject->invalidStatus() != "false");
254         break;
255
256     case AXElementBusyChanged:
257         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, coreObject->isBusy());
258         break;
259
260     case AXCurrentChanged:
261         atk_object_notify_state_change(axObject, ATK_STATE_ACTIVE, coreObject->currentState() != AccessibilityCurrentState::False);
262         break;
263
264     case AXRowExpanded:
265         atk_object_notify_state_change(axObject, ATK_STATE_EXPANDED, true);
266         break;
267
268     case AXRowCollapsed:
269         atk_object_notify_state_change(axObject, ATK_STATE_EXPANDED, false);
270         break;
271
272     case AXExpandedChanged:
273         atk_object_notify_state_change(axObject, ATK_STATE_EXPANDED, coreObject->isExpanded());
274         break;
275
276     case AXDisabledStateChanged: {
277         bool enabledState = coreObject->isEnabled();
278         atk_object_notify_state_change(axObject, ATK_STATE_ENABLED, enabledState);
279         atk_object_notify_state_change(axObject, ATK_STATE_SENSITIVE, enabledState);
280         break;
281     }
282
283     case AXPressedStateChanged:
284         atk_object_notify_state_change(axObject, ATK_STATE_PRESSED, coreObject->isPressed());
285         break;
286
287     case AXReadOnlyStatusChanged:
288         atk_object_notify_state_change(axObject, ATK_STATE_READ_ONLY, !coreObject->canSetValueAttribute());
289         break;
290
291     case AXRequiredStatusChanged:
292         atk_object_notify_state_change(axObject, ATK_STATE_REQUIRED, coreObject->isRequired());
293         break;
294
295     case AXActiveDescendantChanged:
296         if (AccessibilityObject* descendant = coreObject->activeDescendant())
297             platformHandleFocusedUIElementChanged(nullptr, descendant->node());
298         break;
299
300     default:
301         break;
302     }
303 }
304
305 void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, const String& text)
306 {
307     if (!object || text.isEmpty())
308         return;
309
310     AccessibilityObject* parentObject = object->isNonNativeTextControl() ? object : object->parentObjectUnignored();
311     if (!parentObject)
312         return;
313
314     auto* wrapper = parentObject->wrapper();
315     if (!wrapper || !ATK_IS_TEXT(wrapper))
316         return;
317
318     Node* node = object->node();
319     if (!node)
320         return;
321
322     // Ensure document's layout is up-to-date before using TextIterator.
323     Document& document = node->document();
324     document.updateLayout();
325
326     // Select the right signal to be emitted
327     CString detail;
328     switch (textChange) {
329     case AXTextInserted:
330         detail = "text-insert";
331         break;
332     case AXTextDeleted:
333         detail = "text-remove";
334         break;
335     case AXTextAttributesChanged:
336         detail = "text-attributes-changed";
337         break;
338     }
339
340     String textToEmit = text;
341     unsigned offsetToEmit = offset;
342
343     // If the object we're emitting the signal from represents a
344     // password field, we will emit the masked text.
345     if (parentObject->isPasswordField()) {
346         String maskedText = parentObject->passwordFieldValue();
347         textToEmit = maskedText.substring(offset, text.length());
348     } else {
349         // Consider previous text objects that might be present for
350         // the current accessibility object to ensure we emit the
351         // right offset (e.g. multiline text areas).
352         auto range = Range::create(document, node->parentNode(), 0, node, 0);
353         offsetToEmit = offset + TextIterator::rangeLength(range.ptr());
354     }
355
356     g_signal_emit_by_name(wrapper, detail.data(), offsetToEmit, textToEmit.length(), textToEmit.utf8().data());
357 }
358
359 void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject* object, AXLoadingEvent loadingEvent)
360 {
361     if (!object)
362         return;
363
364     auto* axObject = ATK_OBJECT(object->wrapper());
365     if (!axObject || !ATK_IS_DOCUMENT(axObject))
366         return;
367
368     switch (loadingEvent) {
369     case AXObjectCache::AXLoadingStarted:
370         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, true);
371         break;
372     case AXObjectCache::AXLoadingReloaded:
373         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, true);
374         g_signal_emit_by_name(axObject, "reload");
375         break;
376     case AXObjectCache::AXLoadingFailed:
377         g_signal_emit_by_name(axObject, "load-stopped");
378         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, false);
379         break;
380     case AXObjectCache::AXLoadingFinished:
381         g_signal_emit_by_name(axObject, "load-complete");
382         atk_object_notify_state_change(axObject, ATK_STATE_BUSY, false);
383         break;
384     }
385 }
386
387 void AXObjectCache::platformHandleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode)
388 {
389     RefPtr<AccessibilityObject> oldObject = getOrCreate(oldFocusedNode);
390     if (oldObject) {
391         auto* axObject = oldObject->wrapper();
392         g_signal_emit_by_name(axObject, "focus-event", false);
393         atk_object_notify_state_change(ATK_OBJECT(axObject), ATK_STATE_FOCUSED, false);
394     }
395     RefPtr<AccessibilityObject> newObject = getOrCreate(newFocusedNode);
396     if (newObject) {
397         auto* axObject = newObject->wrapper();
398         g_signal_emit_by_name(axObject, "focus-event", true);
399         atk_object_notify_state_change(ATK_OBJECT(axObject), ATK_STATE_FOCUSED, true);
400     }
401 }
402
403 void AXObjectCache::handleScrolledToAnchor(const Node*)
404 {
405 }
406
407 } // namespace WebCore
408
409 #endif