9c77a04a4ce9a0440daaa0036786c0d19503d0a2
[WebKit-https.git] / Source / WebCore / accessibility / atk / WebKitAccessible.cpp
1 /*
2  * Copyright (C) 2008 Nuanti Ltd.
3  * Copyright (C) 2009 Jan Alonzo
4  * Copyright (C) 2009, 2010, 2011, 2012, 2019 Igalia S.L.
5  * Copyright (C) 2013 Samsung Electronics
6  *
7  * Portions from Mozilla a11y, copyright as follows:
8  *
9  * The Original Code is mozilla.org code.
10  *
11  * The Initial Developer of the Original Code is
12  * Sun Microsystems, Inc.
13  * Portions created by the Initial Developer are Copyright (C) 2002
14  * the Initial Developer. All Rights Reserved.
15  *
16  * This library is free software; you can redistribute it and/or
17  * modify it under the terms of the GNU Library General Public
18  * License as published by the Free Software Foundation; either
19  * version 2 of the License, or (at your option) any later version.
20  *
21  * This library is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24  * Library General Public License for more details.
25  *
26  * You should have received a copy of the GNU Library General Public License
27  * along with this library; see the file COPYING.LIB.  If not, write to
28  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
29  * Boston, MA 02110-1301, USA.
30  */
31
32 #include "config.h"
33 #include "WebKitAccessible.h"
34
35 #if HAVE(ACCESSIBILITY)
36
37 #include "AXObjectCache.h"
38 #include "AccessibilityList.h"
39 #include "AccessibilityListBoxOption.h"
40 #include "AccessibilityTable.h"
41 #include "AccessibilityTableCell.h"
42 #include "AccessibilityTableRow.h"
43 #include "Document.h"
44 #include "Editing.h"
45 #include "Frame.h"
46 #include "FrameView.h"
47 #include "HTMLNames.h"
48 #include "HTMLTableElement.h"
49 #include "HostWindow.h"
50 #include "RenderAncestorIterator.h"
51 #include "RenderBlock.h"
52 #include "RenderObject.h"
53 #include "SVGElement.h"
54 #include "Settings.h"
55 #include "TextIterator.h"
56 #include "VisibleUnits.h"
57 #include "WebKitAccessibleHyperlink.h"
58 #include "WebKitAccessibleInterfaceAction.h"
59 #include "WebKitAccessibleInterfaceComponent.h"
60 #include "WebKitAccessibleInterfaceDocument.h"
61 #include "WebKitAccessibleInterfaceEditableText.h"
62 #include "WebKitAccessibleInterfaceHyperlinkImpl.h"
63 #include "WebKitAccessibleInterfaceHypertext.h"
64 #include "WebKitAccessibleInterfaceImage.h"
65 #include "WebKitAccessibleInterfaceSelection.h"
66 #include "WebKitAccessibleInterfaceTable.h"
67 #include "WebKitAccessibleInterfaceTableCell.h"
68 #include "WebKitAccessibleInterfaceText.h"
69 #include "WebKitAccessibleInterfaceValue.h"
70 #include "WebKitAccessibleUtil.h"
71 #include <glib/gprintf.h>
72 #include <wtf/glib/WTFGType.h>
73 #include <wtf/text/CString.h>
74
75 using namespace WebCore;
76
77 struct _WebKitAccessiblePrivate {
78     AccessibilityObject* object;
79
80     // Cached data for AtkObject.
81     CString accessibleName;
82     CString accessibleDescription;
83
84     // Cached data for AtkAction.
85     CString actionName;
86     CString actionKeyBinding;
87
88     // Cached data for AtkDocument.
89     CString documentLocale;
90     CString documentType;
91     CString documentEncoding;
92     CString documentURI;
93
94     // Cached data for AtkImage.
95     CString imageDescription;
96 };
97
98 WEBKIT_DEFINE_TYPE(WebKitAccessible, webkit_accessible, ATK_TYPE_OBJECT)
99
100 static AccessibilityObject* fallbackObject()
101 {
102     static AccessibilityObject* object = &AccessibilityListBoxOption::create().leakRef();
103     return object;
104 }
105
106 static const gchar* webkitAccessibleGetName(AtkObject* object)
107 {
108     auto* accessible = WEBKIT_ACCESSIBLE(object);
109     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
110
111     Vector<AccessibilityText> textOrder;
112     accessible->priv->object->accessibilityText(textOrder);
113
114     for (const auto& text : textOrder) {
115         // FIXME: This check is here because AccessibilityNodeObject::titleElementText()
116         // appends an empty String for the LabelByElementText source when there is a
117         // titleUIElement(). Removing this check makes some fieldsets lose their name.
118         if (text.text.isEmpty())
119             continue;
120
121         // WebCore Accessibility should provide us with the text alternative computation
122         // in the order defined by that spec. So take the first thing that our platform
123         // does not expose via the AtkObject description.
124         if (text.textSource != AccessibilityTextSource::Help && text.textSource != AccessibilityTextSource::Summary)
125             return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleName, text.text.utf8());
126     }
127
128     return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleName, "");
129 }
130
131 static const gchar* webkitAccessibleGetDescription(AtkObject* object)
132 {
133     auto* accessible = WEBKIT_ACCESSIBLE(object);
134     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
135
136     Vector<AccessibilityText> textOrder;
137     accessible->priv->object->accessibilityText(textOrder);
138
139     bool nameTextAvailable = false;
140     for (const auto& text : textOrder) {
141         // WebCore Accessibility should provide us with the text alternative computation
142         // in the order defined by that spec. So take the first thing that our platform
143         // does not expose via the AtkObject name.
144         if (text.textSource == AccessibilityTextSource::Help || text.textSource == AccessibilityTextSource::Summary)
145             return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleDescription, text.text.utf8());
146
147         // If there is no other text alternative, the title tag contents will have been
148         // used for the AtkObject name. We don't want to duplicate it here.
149         if (text.textSource == AccessibilityTextSource::TitleTag && nameTextAvailable)
150             return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleDescription, text.text.utf8());
151
152         nameTextAvailable = true;
153     }
154
155     return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleDescription, "");
156 }
157
158 static void removeAtkRelationByType(AtkRelationSet* relationSet, AtkRelationType relationType)
159 {
160     int count = atk_relation_set_get_n_relations(relationSet);
161     for (int i = 0; i < count; i++) {
162         AtkRelation* relation = atk_relation_set_get_relation(relationSet, i);
163         if (atk_relation_get_relation_type(relation) == relationType) {
164             atk_relation_set_remove(relationSet, relation);
165             break;
166         }
167     }
168 }
169
170 static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
171 {
172     // Elements with aria-labelledby should have the labelled-by relation as per the ARIA AAM spec.
173     // Controls with a label element and fieldsets with a legend element should also use this relation
174     // as per the HTML AAM spec. The reciprocal label-for relation should also be used.
175     removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY);
176     removeAtkRelationByType(relationSet, ATK_RELATION_LABEL_FOR);
177     if (coreObject->isControl()) {
178         if (AccessibilityObject* label = coreObject->correspondingLabelForControlElement())
179             atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, ATK_OBJECT(label->wrapper()));
180     } else if (coreObject->isFieldset()) {
181         if (AccessibilityObject* label = coreObject->titleUIElement())
182             atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, ATK_OBJECT(label->wrapper()));
183     } else if (coreObject->roleValue() == AccessibilityRole::Legend) {
184         if (RenderBlock* renderFieldset = ancestorsOfType<RenderBlock>(*coreObject->renderer()).first()) {
185             if (renderFieldset->isFieldset()) {
186                 AccessibilityObject* fieldset = coreObject->axObjectCache()->getOrCreate(renderFieldset);
187                 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, ATK_OBJECT(fieldset->wrapper()));
188             }
189         }
190     } else if (AccessibilityObject* control = coreObject->correspondingControlForLabelElement())
191         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, ATK_OBJECT(control->wrapper()));
192     else {
193         AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements;
194         coreObject->ariaLabelledByElements(ariaLabelledByElements);
195         for (const auto& accessibilityObject : ariaLabelledByElements)
196             atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, ATK_OBJECT(accessibilityObject->wrapper()));
197     }
198
199     // Elements referenced by aria-labelledby should have the label-for relation as per the ARIA AAM spec.
200     AccessibilityObject::AccessibilityChildrenVector labels;
201     coreObject->ariaLabelledByReferencingElements(labels);
202     for (const auto& accessibilityObject : labels)
203         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, ATK_OBJECT(accessibilityObject->wrapper()));
204
205     // Elements with aria-flowto should have the flows-to relation as per the ARIA AAM spec.
206     removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_TO);
207     AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements;
208     coreObject->ariaFlowToElements(ariaFlowToElements);
209     for (const auto& accessibilityObject : ariaFlowToElements)
210         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_TO, ATK_OBJECT(accessibilityObject->wrapper()));
211
212     // Elements referenced by aria-flowto should have the flows-from relation as per the ARIA AAM spec.
213     removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_FROM);
214     AccessibilityObject::AccessibilityChildrenVector flowFrom;
215     coreObject->ariaFlowToReferencingElements(flowFrom);
216     for (const auto& accessibilityObject : flowFrom)
217         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_FROM, ATK_OBJECT(accessibilityObject->wrapper()));
218
219     // Elements with aria-describedby should have the described-by relation as per the ARIA AAM spec.
220     removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIBED_BY);
221     AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements;
222     coreObject->ariaDescribedByElements(ariaDescribedByElements);
223     for (const auto& accessibilityObject : ariaDescribedByElements)
224         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY, ATK_OBJECT(accessibilityObject->wrapper()));
225
226     // Elements referenced by aria-describedby should have the description-for relation as per the ARIA AAM spec.
227     removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIPTION_FOR);
228     AccessibilityObject::AccessibilityChildrenVector describers;
229     coreObject->ariaDescribedByReferencingElements(describers);
230     for (const auto& accessibilityObject : describers)
231         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIPTION_FOR, ATK_OBJECT(accessibilityObject->wrapper()));
232
233     // Elements with aria-controls should have the controller-for relation as per the ARIA AAM spec.
234     removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLER_FOR);
235     AccessibilityObject::AccessibilityChildrenVector ariaControls;
236     coreObject->ariaControlsElements(ariaControls);
237     for (const auto& accessibilityObject : ariaControls)
238         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLER_FOR, ATK_OBJECT(accessibilityObject->wrapper()));
239
240     // Elements referenced by aria-controls should have the controlled-by relation as per the ARIA AAM spec.
241     removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLED_BY);
242     AccessibilityObject::AccessibilityChildrenVector controllers;
243     coreObject->ariaControlsReferencingElements(controllers);
244     for (const auto& accessibilityObject : controllers)
245         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLED_BY, ATK_OBJECT(accessibilityObject->wrapper()));
246
247     // Elements with aria-owns should have the node-parent-of relation as per the ARIA AAM spec.
248     removeAtkRelationByType(relationSet, ATK_RELATION_NODE_PARENT_OF);
249     AccessibilityObject::AccessibilityChildrenVector ariaOwns;
250     coreObject->ariaOwnsElements(ariaOwns);
251     for (const auto& accessibilityObject : ariaOwns)
252         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_NODE_PARENT_OF, ATK_OBJECT(accessibilityObject->wrapper()));
253
254     // Elements referenced by aria-owns should have the node-child-of relation as per the ARIA AAM spec.
255     removeAtkRelationByType(relationSet, ATK_RELATION_NODE_CHILD_OF);
256     AccessibilityObject::AccessibilityChildrenVector owners;
257     coreObject->ariaOwnsReferencingElements(owners);
258     for (const auto& accessibilityObject : owners)
259         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_NODE_CHILD_OF, ATK_OBJECT(accessibilityObject->wrapper()));
260
261 #if ATK_CHECK_VERSION(2, 25, 2)
262     // Elements with aria-details should have the details relation as per the ARIA AAM spec.
263     removeAtkRelationByType(relationSet, ATK_RELATION_DETAILS);
264     AccessibilityObject::AccessibilityChildrenVector ariaDetails;
265     coreObject->ariaDetailsElements(ariaDetails);
266     for (const auto& accessibilityObject : ariaDetails)
267         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DETAILS, ATK_OBJECT(accessibilityObject->wrapper()));
268
269     // Elements referenced by aria-details should have the details-for relation as per the ARIA AAM spec.
270     removeAtkRelationByType(relationSet, ATK_RELATION_DETAILS_FOR);
271     AccessibilityObject::AccessibilityChildrenVector details;
272     coreObject->ariaDetailsReferencingElements(details);
273     for (const auto& accessibilityObject : details)
274         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DETAILS_FOR, ATK_OBJECT(accessibilityObject->wrapper()));
275
276     // Elements with aria-errormessage should have the error-message relation as per the ARIA AAM spec.
277     removeAtkRelationByType(relationSet, ATK_RELATION_ERROR_MESSAGE);
278     AccessibilityObject::AccessibilityChildrenVector ariaErrorMessage;
279     coreObject->ariaErrorMessageElements(ariaErrorMessage);
280     for (const auto& accessibilityObject : ariaErrorMessage)
281         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_ERROR_MESSAGE, ATK_OBJECT(accessibilityObject->wrapper()));
282
283     // Elements referenced by aria-errormessage should have the error-for relation as per the ARIA AAM spec.
284     removeAtkRelationByType(relationSet, ATK_RELATION_ERROR_FOR);
285     AccessibilityObject::AccessibilityChildrenVector errors;
286     coreObject->ariaErrorMessageReferencingElements(errors);
287     for (const auto& accessibilityObject : errors)
288         atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_ERROR_FOR, ATK_OBJECT(accessibilityObject->wrapper()));
289 #endif
290 }
291
292 static bool isRootObject(AccessibilityObject* coreObject)
293 {
294     // The root accessible object in WebCore is always an object with
295     // the ScrolledArea role with one child with the WebArea role.
296     if (!coreObject || !coreObject->isScrollView())
297         return false;
298
299     AccessibilityObject* firstChild = coreObject->firstChild();
300     return firstChild && firstChild->isWebArea();
301 }
302
303 static AtkObject* webkitAccessibleGetParent(AtkObject* object)
304 {
305     auto* accessible = WEBKIT_ACCESSIBLE(object);
306     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
307
308     // Check first if the parent has been already set.
309     AtkObject* accessibleParent = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->get_parent(object);
310     if (accessibleParent)
311         return accessibleParent;
312
313     // Parent not set yet, so try to find it in the hierarchy.
314     auto* coreObject = accessible->priv->object;
315     auto* coreParent = coreObject->parentObjectUnignored();
316     if (!coreParent && isRootObject(coreObject)) {
317         // The top level object claims to not have a parent. This makes it
318         // impossible for assistive technologies to ascend the accessible
319         // hierarchy all the way to the application. (Bug 30489)
320         if (!coreObject->document())
321             return nullptr;
322     }
323
324     return coreParent ? ATK_OBJECT(coreParent->wrapper()) : nullptr;
325 }
326
327 static gint webkitAccessibleGetNChildren(AtkObject* object)
328 {
329     auto* accessible = WEBKIT_ACCESSIBLE(object);
330     returnValIfWebKitAccessibleIsInvalid(accessible, 0);
331
332     return accessible->priv->object->children().size();
333 }
334
335 static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index)
336 {
337     auto* accessible = WEBKIT_ACCESSIBLE(object);
338     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
339
340     if (index < 0)
341         return nullptr;
342
343     const auto& children = accessible->priv->object->children();
344     if (static_cast<size_t>(index) >= children.size())
345         return nullptr;
346
347     auto& coreChild = children[index];
348     if (!coreChild)
349         return nullptr;
350
351     auto* child = coreChild->wrapper();
352     if (!child)
353         return nullptr;
354
355     atk_object_set_parent(ATK_OBJECT(child), object);
356     return ATK_OBJECT(g_object_ref(child));
357 }
358
359 static gint webkitAccessibleGetIndexInParent(AtkObject* object)
360 {
361     auto* accessible = WEBKIT_ACCESSIBLE(object);
362     returnValIfWebKitAccessibleIsInvalid(accessible, -1);
363
364     auto* coreObject = accessible->priv->object;
365     auto* parent = coreObject->parentObjectUnignored();
366     if (!parent && isRootObject(coreObject)) {
367         if (!coreObject->document())
368             return -1;
369
370         auto* atkParent = parent ? ATK_OBJECT(parent->wrapper()) : nullptr;
371         if (!atkParent)
372             return -1;
373
374         unsigned count = atk_object_get_n_accessible_children(atkParent);
375         for (unsigned i = 0; i < count; ++i) {
376             GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(atkParent, i));
377             if (child.get() == object)
378                 return i;
379         }
380     }
381
382     if (!parent)
383         return -1;
384
385     size_t index = parent->children().find(coreObject);
386     return (index == WTF::notFound) ? -1 : index;
387 }
388
389 static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object)
390 {
391     auto* accessible = WEBKIT_ACCESSIBLE(object);
392     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
393
394     AtkAttributeSet* attributeSet = nullptr;
395 #if PLATFORM(GTK)
396     attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk");
397 #elif PLATFORM(WPE)
398     attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WPEWebKit");
399 #endif
400
401     auto* coreObject = accessible->priv->object;
402
403     // Hack needed for WebKit2 tests because obtaining an element by its ID
404     // cannot be done from the UIProcess. Assistive technologies have no need
405     // for this information.
406     Element* element = coreObject->element() ? coreObject->element() : coreObject->actionElement();
407     if (element) {
408         String tagName = element->tagName();
409         if (!tagName.isEmpty())
410             attributeSet = addToAtkAttributeSet(attributeSet, "tag", tagName.convertToASCIILowercase().utf8().data());
411         String id = element->getIdAttribute().string();
412         if (!id.isEmpty())
413             attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data());
414     }
415
416     int level = coreObject->isHeading() ? coreObject->headingLevel() : coreObject->hierarchicalLevel();
417     if (level) {
418         String value = String::number(level);
419         attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data());
420     }
421
422     if (coreObject->roleValue() == AccessibilityRole::MathElement) {
423         if (coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript))
424             attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "pre");
425         else if (coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript))
426             attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "post");
427     }
428
429     if (is<AccessibilityTable>(*coreObject) && downcast<AccessibilityTable>(*coreObject).isExposableThroughAccessibility()) {
430         auto& table = downcast<AccessibilityTable>(*coreObject);
431         int rowCount = table.axRowCount();
432         if (rowCount)
433             attributeSet = addToAtkAttributeSet(attributeSet, "rowcount", String::number(rowCount).utf8().data());
434
435         int columnCount = table.axColumnCount();
436         if (columnCount)
437             attributeSet = addToAtkAttributeSet(attributeSet, "colcount", String::number(columnCount).utf8().data());
438     } else if (is<AccessibilityTableRow>(*coreObject)) {
439         auto& row = downcast<AccessibilityTableRow>(*coreObject);
440         int rowIndex = row.axRowIndex();
441         if (rowIndex != -1)
442             attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data());
443     } else if (is<AccessibilityTableCell>(*coreObject)) {
444         auto& cell = downcast<AccessibilityTableCell>(*coreObject);
445         int rowIndex = cell.axRowIndex();
446         if (rowIndex != -1)
447             attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data());
448
449         int columnIndex = cell.axColumnIndex();
450         if (columnIndex != -1)
451             attributeSet = addToAtkAttributeSet(attributeSet, "colindex", String::number(columnIndex).utf8().data());
452
453         int rowSpan = cell.axRowSpan();
454         if (rowSpan != -1)
455             attributeSet = addToAtkAttributeSet(attributeSet, "rowspan", String::number(rowSpan).utf8().data());
456
457         int columnSpan = cell.axColumnSpan();
458         if (columnSpan != -1)
459             attributeSet = addToAtkAttributeSet(attributeSet, "colspan", String::number(columnSpan).utf8().data());
460     }
461
462     String placeholder = coreObject->placeholderValue();
463     if (!placeholder.isEmpty())
464         attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data());
465
466     if (coreObject->supportsAutoComplete())
467         attributeSet = addToAtkAttributeSet(attributeSet, "autocomplete", coreObject->autoCompleteValue().utf8().data());
468
469     if (coreObject->supportsHasPopup())
470         attributeSet = addToAtkAttributeSet(attributeSet, "haspopup", coreObject->hasPopupValue().utf8().data());
471
472     if (coreObject->supportsCurrent())
473         attributeSet = addToAtkAttributeSet(attributeSet, "current", coreObject->currentValue().utf8().data());
474
475     // The Core AAM states that an explicitly-set value should be exposed, including "none".
476     if (coreObject->hasAttribute(HTMLNames::aria_sortAttr)) {
477         switch (coreObject->sortDirection()) {
478         case AccessibilitySortDirection::Invalid:
479             break;
480         case AccessibilitySortDirection::Ascending:
481             attributeSet = addToAtkAttributeSet(attributeSet, "sort", "ascending");
482             break;
483         case AccessibilitySortDirection::Descending:
484             attributeSet = addToAtkAttributeSet(attributeSet, "sort", "descending");
485             break;
486         case AccessibilitySortDirection::Other:
487             attributeSet = addToAtkAttributeSet(attributeSet, "sort", "other");
488             break;
489         case AccessibilitySortDirection::None:
490             attributeSet = addToAtkAttributeSet(attributeSet, "sort", "none");
491         }
492     }
493
494     if (coreObject->supportsPosInSet())
495         attributeSet = addToAtkAttributeSet(attributeSet, "posinset", String::number(coreObject->posInSet()).utf8().data());
496
497     if (coreObject->supportsSetSize())
498         attributeSet = addToAtkAttributeSet(attributeSet, "setsize", String::number(coreObject->setSize()).utf8().data());
499
500     String isReadOnly = coreObject->readOnlyValue();
501     if (!isReadOnly.isEmpty())
502         attributeSet = addToAtkAttributeSet(attributeSet, "readonly", isReadOnly.utf8().data());
503
504     String valueDescription = coreObject->valueDescription();
505     if (!valueDescription.isEmpty())
506         attributeSet = addToAtkAttributeSet(attributeSet, "valuetext", valueDescription.utf8().data());
507
508     // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules:
509     // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so."
510     // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string").
511     // We cannot use the computedRoleString for this purpose because it is not limited to elements
512     // with ARIA roles, and it might not contain the actual ARIA role value (e.g. DPub ARIA).
513     String roleString = coreObject->getAttribute(HTMLNames::roleAttr);
514     if (!roleString.isEmpty())
515         attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", roleString.utf8().data());
516
517     String computedRoleString = coreObject->computedRoleString();
518     if (!computedRoleString.isEmpty()) {
519         attributeSet = addToAtkAttributeSet(attributeSet, "computed-role", computedRoleString.utf8().data());
520
521         // The HTML AAM maps several elements to ARIA landmark roles. In order for the type of landmark
522         // to be obtainable in the same fashion as an ARIA landmark, fall back on the computedRoleString.
523         if (coreObject->ariaRoleAttribute() == AccessibilityRole::Unknown && coreObject->isLandmark())
524             attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", computedRoleString.utf8().data());
525     }
526
527     String roleDescription = coreObject->roleDescription();
528     if (!roleDescription.isEmpty())
529         attributeSet = addToAtkAttributeSet(attributeSet, "roledescription", roleDescription.utf8().data());
530
531     // We need to expose the live region attributes even if the live region is currently disabled/off.
532     if (auto liveContainer = coreObject->liveRegionAncestor(false)) {
533         String liveStatus = liveContainer->liveRegionStatus();
534         String relevant = liveContainer->liveRegionRelevant();
535         bool isAtom = liveContainer->liveRegionAtomic();
536         String liveRole = roleString.isEmpty() ? computedRoleString : roleString;
537
538         // According to the Core AAM, we need to expose the above properties with "container-" prefixed
539         // object attributes regardless of whether the container is this object, or an ancestor of it.
540         attributeSet = addToAtkAttributeSet(attributeSet, "container-live", liveStatus.utf8().data());
541         attributeSet = addToAtkAttributeSet(attributeSet, "container-relevant", relevant.utf8().data());
542         if (isAtom)
543             attributeSet = addToAtkAttributeSet(attributeSet, "container-atomic", "true");
544         if (!liveRole.isEmpty())
545             attributeSet = addToAtkAttributeSet(attributeSet, "container-live-role", liveRole.utf8().data());
546
547         // According to the Core AAM, if this object is the live region (rather than its descendant),
548         // we must expose the above properties on the object without a "container-" prefix.
549         if (liveContainer == coreObject) {
550             attributeSet = addToAtkAttributeSet(attributeSet, "live", liveStatus.utf8().data());
551             attributeSet = addToAtkAttributeSet(attributeSet, "relevant", relevant.utf8().data());
552             if (isAtom)
553                 attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true");
554         } else if (!isAtom && coreObject->liveRegionAtomic())
555             attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true");
556     }
557
558     // The Core AAM states the author-provided value should be exposed as-is.
559     String dropEffect = coreObject->getAttribute(HTMLNames::aria_dropeffectAttr);
560     if (!dropEffect.isEmpty())
561         attributeSet = addToAtkAttributeSet(attributeSet, "dropeffect", dropEffect.utf8().data());
562
563     if (coreObject->isARIAGrabbed())
564         attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "true");
565     else if (coreObject->supportsARIADragging())
566         attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "false");
567
568     // The Core AAM states the author-provided value should be exposed as-is.
569     const AtomString& keyShortcuts = coreObject->keyShortcutsValue();
570     if (!keyShortcuts.isEmpty())
571         attributeSet = addToAtkAttributeSet(attributeSet, "keyshortcuts", keyShortcuts.string().utf8().data());
572
573     return attributeSet;
574 }
575
576 static AtkRole atkRole(AccessibilityObject* coreObject)
577 {
578     switch (coreObject->roleValue()) {
579     case AccessibilityRole::ApplicationAlert:
580         return ATK_ROLE_ALERT;
581     case AccessibilityRole::ApplicationAlertDialog:
582     case AccessibilityRole::ApplicationDialog:
583         return ATK_ROLE_DIALOG;
584     case AccessibilityRole::ApplicationStatus:
585         return ATK_ROLE_STATUSBAR;
586     case AccessibilityRole::Unknown:
587         return ATK_ROLE_UNKNOWN;
588     case AccessibilityRole::Audio:
589 #if ATK_CHECK_VERSION(2, 11, 3)
590         return ATK_ROLE_AUDIO;
591 #endif
592     case AccessibilityRole::Video:
593 #if ATK_CHECK_VERSION(2, 11, 3)
594         return ATK_ROLE_VIDEO;
595 #endif
596         return ATK_ROLE_EMBEDDED;
597     case AccessibilityRole::Button:
598         return ATK_ROLE_PUSH_BUTTON;
599     case AccessibilityRole::Switch:
600     case AccessibilityRole::ToggleButton:
601         return ATK_ROLE_TOGGLE_BUTTON;
602     case AccessibilityRole::RadioButton:
603         return ATK_ROLE_RADIO_BUTTON;
604     case AccessibilityRole::CheckBox:
605         return ATK_ROLE_CHECK_BOX;
606     case AccessibilityRole::Slider:
607         return ATK_ROLE_SLIDER;
608     case AccessibilityRole::TabGroup:
609     case AccessibilityRole::TabList:
610         return ATK_ROLE_PAGE_TAB_LIST;
611     case AccessibilityRole::TextField:
612     case AccessibilityRole::TextArea:
613     case AccessibilityRole::SearchField:
614         return ATK_ROLE_ENTRY;
615     case AccessibilityRole::StaticText:
616 #if ATK_CHECK_VERSION(2, 15, 2)
617         return ATK_ROLE_STATIC;
618 #else
619         return ATK_ROLE_TEXT;
620 #endif
621     case AccessibilityRole::Outline:
622     case AccessibilityRole::Tree:
623         return ATK_ROLE_TREE;
624     case AccessibilityRole::TreeItem:
625         return ATK_ROLE_TREE_ITEM;
626     case AccessibilityRole::MenuBar:
627         return ATK_ROLE_MENU_BAR;
628     case AccessibilityRole::MenuListPopup:
629     case AccessibilityRole::Menu:
630         return ATK_ROLE_MENU;
631     case AccessibilityRole::MenuListOption:
632     case AccessibilityRole::MenuItem:
633     case AccessibilityRole::MenuButton:
634         return ATK_ROLE_MENU_ITEM;
635     case AccessibilityRole::MenuItemCheckbox:
636         return ATK_ROLE_CHECK_MENU_ITEM;
637     case AccessibilityRole::MenuItemRadio:
638         return ATK_ROLE_RADIO_MENU_ITEM;
639     case AccessibilityRole::Column:
640         // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
641         return ATK_ROLE_UNKNOWN; // Matches Mozilla
642     case AccessibilityRole::Row:
643         return ATK_ROLE_TABLE_ROW;
644     case AccessibilityRole::Toolbar:
645         return ATK_ROLE_TOOL_BAR;
646     case AccessibilityRole::Meter:
647         return ATK_ROLE_LEVEL_BAR;
648     case AccessibilityRole::BusyIndicator:
649     case AccessibilityRole::ProgressIndicator:
650         return ATK_ROLE_PROGRESS_BAR;
651     case AccessibilityRole::Window:
652         return ATK_ROLE_WINDOW;
653     case AccessibilityRole::PopUpButton:
654         return coreObject->hasPopup() ? ATK_ROLE_PUSH_BUTTON : ATK_ROLE_COMBO_BOX;
655     case AccessibilityRole::ComboBox:
656         return ATK_ROLE_COMBO_BOX;
657     case AccessibilityRole::SplitGroup:
658         return ATK_ROLE_SPLIT_PANE;
659     case AccessibilityRole::Splitter:
660         return ATK_ROLE_SEPARATOR;
661     case AccessibilityRole::ColorWell:
662 #if PLATFORM(GTK)
663         // ATK_ROLE_COLOR_CHOOSER is defined as a dialog (i.e. it's what appears when you push the button).
664         return ATK_ROLE_PUSH_BUTTON;
665 #endif
666     case AccessibilityRole::List:
667         return ATK_ROLE_LIST;
668     case AccessibilityRole::ScrollBar:
669         return ATK_ROLE_SCROLL_BAR;
670     case AccessibilityRole::ScrollArea:
671     case AccessibilityRole::TabPanel:
672         return ATK_ROLE_SCROLL_PANE;
673     case AccessibilityRole::Grid:
674     case AccessibilityRole::Table:
675         return ATK_ROLE_TABLE;
676     case AccessibilityRole::TreeGrid:
677         return ATK_ROLE_TREE_TABLE;
678     case AccessibilityRole::Application:
679         return ATK_ROLE_APPLICATION;
680     case AccessibilityRole::ApplicationGroup:
681     case AccessibilityRole::Feed:
682     case AccessibilityRole::Figure:
683     case AccessibilityRole::GraphicsObject:
684     case AccessibilityRole::Group:
685     case AccessibilityRole::RadioGroup:
686     case AccessibilityRole::SVGRoot:
687         return ATK_ROLE_PANEL;
688     case AccessibilityRole::RowHeader:
689         return ATK_ROLE_ROW_HEADER;
690     case AccessibilityRole::ColumnHeader:
691         return ATK_ROLE_COLUMN_HEADER;
692     case AccessibilityRole::Caption:
693         return ATK_ROLE_CAPTION;
694     case AccessibilityRole::Cell:
695     case AccessibilityRole::GridCell:
696         return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_TABLE_CELL;
697     case AccessibilityRole::Link:
698     case AccessibilityRole::WebCoreLink:
699     case AccessibilityRole::ImageMapLink:
700         return ATK_ROLE_LINK;
701     case AccessibilityRole::ImageMap:
702         return ATK_ROLE_IMAGE_MAP;
703     case AccessibilityRole::GraphicsSymbol:
704     case AccessibilityRole::Image:
705         return ATK_ROLE_IMAGE;
706     case AccessibilityRole::ListMarker:
707         return ATK_ROLE_TEXT;
708     case AccessibilityRole::DocumentArticle:
709 #if ATK_CHECK_VERSION(2, 11, 3)
710         return ATK_ROLE_ARTICLE;
711 #endif
712     case AccessibilityRole::Document:
713     case AccessibilityRole::GraphicsDocument:
714         return ATK_ROLE_DOCUMENT_FRAME;
715     case AccessibilityRole::DocumentNote:
716         return ATK_ROLE_COMMENT;
717     case AccessibilityRole::Heading:
718         return ATK_ROLE_HEADING;
719     case AccessibilityRole::ListBox:
720         // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-listbox
721         return coreObject->isDescendantOfRole(AccessibilityRole::ComboBox) ? ATK_ROLE_MENU : ATK_ROLE_LIST_BOX;
722     case AccessibilityRole::ListItem:
723         return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_LIST_ITEM;
724     case AccessibilityRole::ListBoxOption:
725         return coreObject->isDescendantOfRole(AccessibilityRole::ComboBox) ? ATK_ROLE_MENU_ITEM : ATK_ROLE_LIST_ITEM;
726     case AccessibilityRole::Paragraph:
727         return ATK_ROLE_PARAGRAPH;
728     case AccessibilityRole::Label:
729     case AccessibilityRole::Legend:
730         return ATK_ROLE_LABEL;
731     case AccessibilityRole::Blockquote:
732 #if ATK_CHECK_VERSION(2, 11, 3)
733         return ATK_ROLE_BLOCK_QUOTE;
734 #endif
735     case AccessibilityRole::Footnote:
736 #if ATK_CHECK_VERSION(2, 25, 2)
737         return ATK_ROLE_FOOTNOTE;
738 #endif
739     case AccessibilityRole::ApplicationTextGroup:
740     case AccessibilityRole::Div:
741     case AccessibilityRole::Pre:
742     case AccessibilityRole::SVGText:
743     case AccessibilityRole::TextGroup:
744         return ATK_ROLE_SECTION;
745     case AccessibilityRole::Footer:
746         return ATK_ROLE_FOOTER;
747     case AccessibilityRole::Form:
748 #if ATK_CHECK_VERSION(2, 11, 3)
749         if (coreObject->ariaRoleAttribute() != AccessibilityRole::Unknown)
750             return ATK_ROLE_LANDMARK;
751 #endif
752         return ATK_ROLE_FORM;
753     case AccessibilityRole::Canvas:
754         return ATK_ROLE_CANVAS;
755     case AccessibilityRole::HorizontalRule:
756         return ATK_ROLE_SEPARATOR;
757     case AccessibilityRole::SpinButton:
758         return ATK_ROLE_SPIN_BUTTON;
759     case AccessibilityRole::Tab:
760         return ATK_ROLE_PAGE_TAB;
761     case AccessibilityRole::UserInterfaceTooltip:
762         return ATK_ROLE_TOOL_TIP;
763     case AccessibilityRole::WebArea:
764         return ATK_ROLE_DOCUMENT_WEB;
765     case AccessibilityRole::WebApplication:
766         return ATK_ROLE_EMBEDDED;
767 #if ATK_CHECK_VERSION(2, 11, 3)
768     case AccessibilityRole::ApplicationLog:
769         return ATK_ROLE_LOG;
770     case AccessibilityRole::ApplicationMarquee:
771         return ATK_ROLE_MARQUEE;
772     case AccessibilityRole::ApplicationTimer:
773         return ATK_ROLE_TIMER;
774     case AccessibilityRole::Definition:
775         return ATK_ROLE_DEFINITION;
776     case AccessibilityRole::DocumentMath:
777         return ATK_ROLE_MATH;
778     case AccessibilityRole::MathElement:
779         if (coreObject->isMathRow())
780             return ATK_ROLE_PANEL;
781         if (coreObject->isMathTable())
782             return ATK_ROLE_TABLE;
783         if (coreObject->isMathTableRow())
784             return ATK_ROLE_TABLE_ROW;
785         if (coreObject->isMathTableCell())
786             return ATK_ROLE_TABLE_CELL;
787         if (coreObject->isMathSubscriptSuperscript() || coreObject->isMathMultiscript())
788             return ATK_ROLE_SECTION;
789 #if ATK_CHECK_VERSION(2, 15, 4)
790         if (coreObject->isMathFraction())
791             return ATK_ROLE_MATH_FRACTION;
792         if (coreObject->isMathSquareRoot() || coreObject->isMathRoot())
793             return ATK_ROLE_MATH_ROOT;
794         if (coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Subscript)
795             || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript))
796             return ATK_ROLE_SUBSCRIPT;
797         if (coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Superscript)
798             || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript))
799             return ATK_ROLE_SUPERSCRIPT;
800 #endif
801 #if ATK_CHECK_VERSION(2, 15, 2)
802         if (coreObject->isMathToken())
803             return ATK_ROLE_STATIC;
804 #endif
805         return ATK_ROLE_UNKNOWN;
806     case AccessibilityRole::LandmarkBanner:
807     case AccessibilityRole::LandmarkComplementary:
808     case AccessibilityRole::LandmarkContentInfo:
809     case AccessibilityRole::LandmarkDocRegion:
810     case AccessibilityRole::LandmarkMain:
811     case AccessibilityRole::LandmarkNavigation:
812     case AccessibilityRole::LandmarkRegion:
813     case AccessibilityRole::LandmarkSearch:
814         return ATK_ROLE_LANDMARK;
815 #endif
816 #if ATK_CHECK_VERSION(2, 11, 4)
817     case AccessibilityRole::DescriptionList:
818         return ATK_ROLE_DESCRIPTION_LIST;
819     case AccessibilityRole::Term:
820     case AccessibilityRole::DescriptionListTerm:
821         return ATK_ROLE_DESCRIPTION_TERM;
822     case AccessibilityRole::DescriptionListDetail:
823         return ATK_ROLE_DESCRIPTION_VALUE;
824 #endif
825     case AccessibilityRole::Inline:
826 #if ATK_CHECK_VERSION(2, 15, 4)
827         if (coreObject->isSubscriptStyleGroup())
828             return ATK_ROLE_SUBSCRIPT;
829         if (coreObject->isSuperscriptStyleGroup())
830             return ATK_ROLE_SUPERSCRIPT;
831 #endif
832 #if ATK_CHECK_VERSION(2, 15, 2)
833         return ATK_ROLE_STATIC;
834     case AccessibilityRole::SVGTextPath:
835     case AccessibilityRole::SVGTSpan:
836     case AccessibilityRole::Time:
837         return ATK_ROLE_STATIC;
838 #endif
839     default:
840         return ATK_ROLE_UNKNOWN;
841     }
842 }
843
844 static AtkRole webkitAccessibleGetRole(AtkObject* object)
845 {
846     // ATK_ROLE_UNKNOWN should only be applied in cases where there is a valid
847     // WebCore accessible object for which the platform role mapping is unknown.
848     auto* accessible = WEBKIT_ACCESSIBLE(object);
849     returnValIfWebKitAccessibleIsInvalid(accessible, ATK_ROLE_INVALID);
850
851     // Note: Why doesn't WebCore have a password field for this
852     if (accessible->priv->object->isPasswordField())
853         return ATK_ROLE_PASSWORD_TEXT;
854
855     return atkRole(accessible->priv->object);
856 }
857
858 static bool isTextWithCaret(AccessibilityObject* coreObject)
859 {
860     if (!coreObject || !coreObject->isAccessibilityRenderObject())
861         return false;
862
863     Document* document = coreObject->document();
864     if (!document)
865         return false;
866
867     Frame* frame = document->frame();
868     if (!frame)
869         return false;
870
871     if (!frame->settings().caretBrowsingEnabled())
872         return false;
873
874     // Check text objects and paragraphs only.
875     auto* axObject = coreObject->wrapper();
876     AtkRole role = axObject ? atk_object_get_role(ATK_OBJECT(axObject)) : ATK_ROLE_INVALID;
877     if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
878         return false;
879
880     // Finally, check whether the caret is set in the current object.
881     VisibleSelection selection = coreObject->selection();
882     if (!selection.isCaret())
883         return false;
884
885     return selectionBelongsToObject(coreObject, selection);
886 }
887
888 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
889 {
890     AccessibilityObject* parent = coreObject->parentObject();
891     bool isListBoxOption = parent && parent->isListBox();
892
893     // Please keep the state list in alphabetical order
894     if ((isListBoxOption && coreObject->isSelectedOptionActive())
895         || coreObject->currentState() != AccessibilityCurrentState::False)
896         atk_state_set_add_state(stateSet, ATK_STATE_ACTIVE);
897
898     if (coreObject->isBusy())
899         atk_state_set_add_state(stateSet, ATK_STATE_BUSY);
900
901 #if ATK_CHECK_VERSION(2,11,2)
902     if (coreObject->supportsChecked() && coreObject->canSetValueAttribute())
903         atk_state_set_add_state(stateSet, ATK_STATE_CHECKABLE);
904 #endif
905
906     if (coreObject->isChecked())
907         atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
908
909     if ((coreObject->isTextControl() || coreObject->isNonNativeTextControl()) && coreObject->canSetValueAttribute())
910         atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
911
912     // FIXME: Put both ENABLED and SENSITIVE together here for now
913     if (coreObject->isEnabled()) {
914         atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
915         atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
916     }
917
918     if (coreObject->canSetExpandedAttribute())
919         atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
920
921     if (coreObject->isExpanded())
922         atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
923
924     if (coreObject->canSetFocusAttribute())
925         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
926
927     // According to the Core AAM, if the element which is focused has a valid aria-activedescendant,
928     // we should not expose the focused state on the element which is actually focused, but instead
929     // on its active descendant.
930     if ((coreObject->isFocused() && !coreObject->activeDescendant()) || isTextWithCaret(coreObject))
931         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
932     else if (coreObject->isActiveDescendantOfFocusedContainer()) {
933         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
934         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
935     }
936
937     if (coreObject->orientation() == AccessibilityOrientation::Horizontal)
938         atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL);
939     else if (coreObject->orientation() == AccessibilityOrientation::Vertical)
940         atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL);
941
942     if (coreObject->hasPopup())
943         atk_state_set_add_state(stateSet, ATK_STATE_HAS_POPUP);
944
945     if (coreObject->isIndeterminate())
946         atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
947     else if (coreObject->isCheckboxOrRadio() || coreObject->isMenuItem() || coreObject->isToggleButton()) {
948         if (coreObject->checkboxOrRadioValue() == AccessibilityButtonState::Mixed)
949             atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
950     }
951
952     if (coreObject->isModalNode())
953         atk_state_set_add_state(stateSet, ATK_STATE_MODAL);
954
955     if (coreObject->invalidStatus() != "false")
956         atk_state_set_add_state(stateSet, ATK_STATE_INVALID_ENTRY);
957
958     if (coreObject->isMultiSelectable())
959         atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
960
961     // TODO: ATK_STATE_OPAQUE
962
963     if (coreObject->isPressed())
964         atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
965
966 #if ATK_CHECK_VERSION(2,15,3)
967     if (!coreObject->canSetValueAttribute() && (coreObject->supportsReadOnly()))
968         atk_state_set_add_state(stateSet, ATK_STATE_READ_ONLY);
969 #endif
970
971     if (coreObject->isRequired())
972         atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED);
973
974     // TODO: ATK_STATE_SELECTABLE_TEXT
975
976     if (coreObject->canSetSelectedAttribute()) {
977         atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
978         // Items in focusable lists have both STATE_SELECT{ABLE,ED}
979         // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on
980         // the former.
981         if (isListBoxOption)
982             atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
983     }
984
985     if (coreObject->isSelected()) {
986         atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
987         // Items in focusable lists have both STATE_SELECT{ABLE,ED}
988         // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
989         // former.
990         if (isListBoxOption)
991             atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
992     }
993
994     // FIXME: Group both SHOWING and VISIBLE here for now
995     // Not sure how to handle this in WebKit, see bug
996     // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
997     // issues with SHOWING vs VISIBLE.
998     if (!coreObject->isOffScreen()) {
999         atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
1000         atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
1001     }
1002
1003     // Mutually exclusive, so we group these two
1004     if (coreObject->roleValue() == AccessibilityRole::TextArea || coreObject->ariaIsMultiline())
1005         atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
1006     else if (coreObject->roleValue() == AccessibilityRole::TextField || coreObject->roleValue() == AccessibilityRole::SearchField)
1007         atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
1008
1009     // TODO: ATK_STATE_SENSITIVE
1010
1011     if (coreObject->supportsAutoComplete() && coreObject->autoCompleteValue() != "none")
1012         atk_state_set_add_state(stateSet, ATK_STATE_SUPPORTS_AUTOCOMPLETION);
1013
1014     if (coreObject->isVisited())
1015         atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
1016 }
1017
1018 static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object)
1019 {
1020     auto* accessible = WEBKIT_ACCESSIBLE(object);
1021     AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object);
1022
1023     // Make sure the layout is updated to really know whether the object
1024     // is defunct or not, so we can return the proper state.
1025     accessible->priv->object->updateBackingStore();
1026
1027     if (accessible->priv->object == fallbackObject()) {
1028         atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
1029         return stateSet;
1030     }
1031
1032     // Text objects must be focusable.
1033     AtkRole role = atk_object_get_role(object);
1034     if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
1035         atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
1036
1037     setAtkStateSetFromCoreObject(accessible->priv->object, stateSet);
1038     return stateSet;
1039 }
1040
1041 static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object)
1042 {
1043     auto* accessible = WEBKIT_ACCESSIBLE(object);
1044     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
1045
1046     AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object);
1047     setAtkRelationSetFromCoreObject(accessible->priv->object, relationSet);
1048     return relationSet;
1049 }
1050
1051 static void webkitAccessibleInit(AtkObject* object, gpointer data)
1052 {
1053     if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize)
1054         ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data);
1055
1056     WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object);
1057     accessible->priv->object = reinterpret_cast<AccessibilityObject*>(data);
1058 }
1059
1060 static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object)
1061 {
1062     auto* accessible = WEBKIT_ACCESSIBLE(object);
1063     returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
1064
1065     if (ATK_IS_DOCUMENT(object)) {
1066         // TODO: Should we fall back on lang xml:lang when the following comes up empty?
1067         String language = accessible->priv->object->language();
1068         if (!language.isEmpty())
1069             return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedDocumentLocale, language.utf8());
1070
1071     } else if (ATK_IS_TEXT(object)) {
1072         const gchar* locale = nullptr;
1073
1074         AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object));
1075         for (auto* attributes = textAttributes; attributes; attributes = attributes->next) {
1076             auto* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
1077             if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) {
1078                 locale = webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedDocumentLocale, atkAttribute->value);
1079                 break;
1080             }
1081         }
1082         atk_attribute_set_free(textAttributes);
1083
1084         return locale;
1085     }
1086
1087     return nullptr;
1088 }
1089
1090 static void webkit_accessible_class_init(WebKitAccessibleClass* klass)
1091 {
1092     auto* atkObjectClass = ATK_OBJECT_CLASS(klass);
1093     atkObjectClass->initialize = webkitAccessibleInit;
1094     atkObjectClass->get_name = webkitAccessibleGetName;
1095     atkObjectClass->get_description = webkitAccessibleGetDescription;
1096     atkObjectClass->get_parent = webkitAccessibleGetParent;
1097     atkObjectClass->get_n_children = webkitAccessibleGetNChildren;
1098     atkObjectClass->ref_child = webkitAccessibleRefChild;
1099     atkObjectClass->get_role = webkitAccessibleGetRole;
1100     atkObjectClass->ref_state_set = webkitAccessibleRefStateSet;
1101     atkObjectClass->get_index_in_parent = webkitAccessibleGetIndexInParent;
1102     atkObjectClass->get_attributes = webkitAccessibleGetAttributes;
1103     atkObjectClass->ref_relation_set = webkitAccessibleRefRelationSet;
1104     atkObjectClass->get_object_locale = webkitAccessibleGetObjectLocale;
1105 }
1106
1107 static const GInterfaceInfo atkInterfacesInitFunctions[] = {
1108     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleActionInterfaceInit)), nullptr, nullptr},
1109     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleSelectionInterfaceInit)), nullptr, nullptr},
1110     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleEditableTextInterfaceInit)), nullptr, nullptr},
1111     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTextInterfaceInit)), nullptr, nullptr},
1112     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleComponentInterfaceInit)), nullptr, nullptr},
1113     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleImageInterfaceInit)), nullptr, nullptr},
1114     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTableInterfaceInit)), nullptr, nullptr},
1115 #if ATK_CHECK_VERSION(2,11,90)
1116     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTableCellInterfaceInit)), nullptr, nullptr},
1117 #endif
1118     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleHypertextInterfaceInit)), nullptr, nullptr},
1119     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleHyperlinkImplInterfaceInit)), nullptr, nullptr},
1120     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleDocumentInterfaceInit)), nullptr, nullptr},
1121     {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleValueInterfaceInit)), nullptr, nullptr}
1122 };
1123
1124 enum WAIType {
1125     WAIAction,
1126     WAISelection,
1127     WAIEditableText,
1128     WAIText,
1129     WAIComponent,
1130     WAIImage,
1131     WAITable,
1132 #if ATK_CHECK_VERSION(2,11,90)
1133     WAITableCell,
1134 #endif
1135     WAIHypertext,
1136     WAIHyperlink,
1137     WAIDocument,
1138     WAIValue,
1139 };
1140
1141 static GType atkInterfaceTypeFromWAIType(WAIType type)
1142 {
1143     switch (type) {
1144     case WAIAction:
1145         return ATK_TYPE_ACTION;
1146     case WAISelection:
1147         return ATK_TYPE_SELECTION;
1148     case WAIEditableText:
1149         return ATK_TYPE_EDITABLE_TEXT;
1150     case WAIText:
1151         return ATK_TYPE_TEXT;
1152     case WAIComponent:
1153         return ATK_TYPE_COMPONENT;
1154     case WAIImage:
1155         return ATK_TYPE_IMAGE;
1156     case WAITable:
1157         return ATK_TYPE_TABLE;
1158 #if ATK_CHECK_VERSION(2,11,90)
1159     case WAITableCell:
1160         return ATK_TYPE_TABLE_CELL;
1161 #endif
1162     case WAIHypertext:
1163         return ATK_TYPE_HYPERTEXT;
1164     case WAIHyperlink:
1165         return ATK_TYPE_HYPERLINK_IMPL;
1166     case WAIDocument:
1167         return ATK_TYPE_DOCUMENT;
1168     case WAIValue:
1169         return ATK_TYPE_VALUE;
1170     }
1171
1172     return G_TYPE_INVALID;
1173 }
1174
1175 static bool roleIsTextType(AccessibilityRole role)
1176 {
1177     return role == AccessibilityRole::Paragraph
1178         || role == AccessibilityRole::Heading
1179         || role == AccessibilityRole::Div
1180         || role == AccessibilityRole::Cell
1181         || role == AccessibilityRole::Link
1182         || role == AccessibilityRole::WebCoreLink
1183         || role == AccessibilityRole::ListItem
1184         || role == AccessibilityRole::Pre
1185         || role == AccessibilityRole::GridCell
1186         || role == AccessibilityRole::TextGroup
1187         || role == AccessibilityRole::ApplicationTextGroup
1188         || role == AccessibilityRole::ApplicationGroup;
1189 }
1190
1191 static guint16 interfaceMaskFromObject(AccessibilityObject* coreObject)
1192 {
1193     guint16 interfaceMask = 0;
1194
1195     // Component interface is always supported
1196     interfaceMask |= 1 << WAIComponent;
1197
1198     AccessibilityRole role = coreObject->roleValue();
1199
1200     // Action
1201     // As the implementation of the AtkAction interface is a very
1202     // basic one (just relays in executing the default action for each
1203     // object, and only supports having one action per object), it is
1204     // better just to implement this interface for every instance of
1205     // the WebKitAccessible class and let WebCore decide what to do.
1206     interfaceMask |= 1 << WAIAction;
1207
1208     // Selection
1209     if (coreObject->canHaveSelectedChildren() || coreObject->isMenuList())
1210         interfaceMask |= 1 << WAISelection;
1211
1212     // Get renderer if available.
1213     RenderObject* renderer = nullptr;
1214     if (coreObject->isAccessibilityRenderObject())
1215         renderer = coreObject->renderer();
1216
1217     // Hyperlink (links and embedded objects).
1218     if (coreObject->isLink() || (renderer && renderer->isReplaced()))
1219         interfaceMask |= 1 << WAIHyperlink;
1220
1221     // Text, Editable Text & Hypertext
1222     if (role == AccessibilityRole::StaticText || coreObject->isMenuListOption())
1223         interfaceMask |= 1 << WAIText;
1224     else if (coreObject->isTextControl() || coreObject->isNonNativeTextControl()) {
1225         interfaceMask |= 1 << WAIText;
1226         if (coreObject->canSetValueAttribute())
1227             interfaceMask |= 1 << WAIEditableText;
1228     } else if (!coreObject->isWebArea()) {
1229         if (role != AccessibilityRole::Table) {
1230             interfaceMask |= 1 << WAIHypertext;
1231             if ((renderer && renderer->childrenInline()) || roleIsTextType(role) || coreObject->isMathToken())
1232                 interfaceMask |= 1 << WAIText;
1233         }
1234
1235         // Add the TEXT interface for list items whose
1236         // first accessible child has a text renderer
1237         if (role == AccessibilityRole::ListItem) {
1238             const auto& children = coreObject->children();
1239             if (!children.isEmpty())
1240                 interfaceMask |= interfaceMaskFromObject(children[0].get());
1241         }
1242     }
1243
1244     // Image
1245     if (coreObject->isImage())
1246         interfaceMask |= 1 << WAIImage;
1247
1248     // Table
1249     if (coreObject->isTable())
1250         interfaceMask |= 1 << WAITable;
1251
1252 #if ATK_CHECK_VERSION(2,11,90)
1253     if (role == AccessibilityRole::Cell || role == AccessibilityRole::GridCell || role == AccessibilityRole::ColumnHeader || role == AccessibilityRole::RowHeader)
1254         interfaceMask |= 1 << WAITableCell;
1255 #endif
1256
1257     // Document
1258     if (role == AccessibilityRole::WebArea)
1259         interfaceMask |= 1 << WAIDocument;
1260
1261     // Value
1262     if (coreObject->supportsRangeValue())
1263         interfaceMask |= 1 << WAIValue;
1264
1265 #if ENABLE(INPUT_TYPE_COLOR)
1266     // Color type.
1267     if (role == AccessibilityRole::ColorWell)
1268         interfaceMask |= 1 << WAIText;
1269 #endif
1270
1271     return interfaceMask;
1272 }
1273
1274 static const char* uniqueAccessibilityTypeName(guint16 interfaceMask)
1275 {
1276 #define WAI_TYPE_NAME_LEN (30) // Enough for prefix + 5 hex characters (max).
1277     static char name[WAI_TYPE_NAME_LEN + 1];
1278
1279     g_sprintf(name, "WAIType%x", interfaceMask);
1280     name[WAI_TYPE_NAME_LEN] = '\0';
1281
1282     return name;
1283 }
1284
1285 static GType accessibilityTypeFromObject(AccessibilityObject* coreObject)
1286 {
1287     static const GTypeInfo typeInfo = {
1288         sizeof(WebKitAccessibleClass),
1289         nullptr, // GBaseInitFunc
1290         nullptr, // GBaseFinalizeFunc
1291         nullptr, // GClassInitFunc
1292         nullptr, // GClassFinalizeFunc
1293         nullptr, // class data
1294         sizeof(WebKitAccessible), // instance size
1295         0, // nb preallocs
1296         nullptr, // GInstanceInitFunc
1297         nullptr // value table
1298     };
1299
1300     guint16 interfaceMask = interfaceMaskFromObject(coreObject);
1301     const char* atkTypeName = uniqueAccessibilityTypeName(interfaceMask);
1302     if (GType type = g_type_from_name(atkTypeName))
1303         return type;
1304
1305     GType type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, static_cast<GTypeFlags>(0));
1306     for (unsigned i = 0; i < G_N_ELEMENTS(atkInterfacesInitFunctions); ++i) {
1307         if (interfaceMask & (1 << i)) {
1308             g_type_add_interface_static(type,
1309                 atkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
1310                 &atkInterfacesInitFunctions[i]);
1311         }
1312     }
1313
1314     return type;
1315 }
1316
1317 WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject)
1318 {
1319     auto* object = ATK_OBJECT(g_object_new(accessibilityTypeFromObject(coreObject), nullptr));
1320     atk_object_initialize(object, coreObject);
1321     return WEBKIT_ACCESSIBLE(object);
1322 }
1323
1324 AccessibilityObject& webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible)
1325 {
1326     ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1327     return *accessible->priv->object;
1328 }
1329
1330 void webkitAccessibleDetach(WebKitAccessible* accessible)
1331 {
1332     ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1333     ASSERT(accessible->priv->object != fallbackObject());
1334
1335     if (accessible->priv->object->roleValue() == AccessibilityRole::WebArea)
1336         atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_DEFUNCT, TRUE);
1337
1338     // We replace the WebCore AccessibilityObject with a fallback object that
1339     // provides default implementations to avoid repetitive null-checking after
1340     // detachment.
1341     accessible->priv->object = fallbackObject();
1342 }
1343
1344 bool webkitAccessibleIsDetached(WebKitAccessible* accessible)
1345 {
1346     ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1347     return accessible->priv->object == fallbackObject();
1348 }
1349
1350 const char* webkitAccessibleCacheAndReturnAtkProperty(WebKitAccessible* accessible, AtkCachedProperty property, CString&& value)
1351 {
1352     ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1353
1354     WebKitAccessiblePrivate* priv = accessible->priv;
1355     CString* propertyPtr = nullptr;
1356
1357     switch (property) {
1358     case AtkCachedAccessibleName:
1359         propertyPtr = &priv->accessibleName;
1360         break;
1361     case AtkCachedAccessibleDescription:
1362         propertyPtr = &priv->accessibleDescription;
1363         break;
1364     case AtkCachedActionName:
1365         propertyPtr = &priv->actionName;
1366         break;
1367     case AtkCachedActionKeyBinding:
1368         propertyPtr = &priv->actionKeyBinding;
1369         break;
1370     case AtkCachedDocumentLocale:
1371         propertyPtr = &priv->documentLocale;
1372         break;
1373     case AtkCachedDocumentType:
1374         propertyPtr = &priv->documentType;
1375         break;
1376     case AtkCachedDocumentEncoding:
1377         propertyPtr = &priv->documentEncoding;
1378         break;
1379     case AtkCachedDocumentURI:
1380         propertyPtr = &priv->documentURI;
1381         break;
1382     case AtkCachedImageDescription:
1383         propertyPtr = &priv->imageDescription;
1384         break;
1385     default:
1386         ASSERT_NOT_REACHED();
1387     }
1388
1389     // Don't invalidate old memory if not stricly needed, since other
1390     // callers might be still holding on to it.
1391     if (*propertyPtr != value)
1392         *propertyPtr = WTFMove(value);
1393
1394     return (*propertyPtr).data();
1395 }
1396
1397 #endif // HAVE(ACCESSIBILITY)