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
75using namespace WebCore;
76
77struct _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
98WEBKIT_DEFINE_TYPE(WebKitAccessible, webkit_accessible, ATK_TYPE_OBJECT)
99
100static AccessibilityObject* fallbackObject()
101{
102 static AccessibilityObject* object = &AccessibilityListBoxOption::create().leakRef();
103 return object;
104}
105
106static 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
131static 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
158static 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
170static 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
292static 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
303static 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
327static 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
335static 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
359static 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
389static 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#endif
398
399 auto* coreObject = accessible->priv->object;
400
401 // Hack needed for WebKit2 tests because obtaining an element by its ID
402 // cannot be done from the UIProcess. Assistive technologies have no need
403 // for this information.
404 Element* element = coreObject->element() ? coreObject->element() : coreObject->actionElement();
405 if (element) {
406 String tagName = element->tagName();
407 if (!tagName.isEmpty())
408 attributeSet = addToAtkAttributeSet(attributeSet, "tag", tagName.convertToASCIILowercase().utf8().data());
409 String id = element->getIdAttribute().string();
410 if (!id.isEmpty())
411 attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data());
412 }
413
414 int level = coreObject->isHeading() ? coreObject->headingLevel() : coreObject->hierarchicalLevel();
415 if (level) {
416 String value = String::number(level);
417 attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data());
418 }
419
420 if (coreObject->roleValue() == AccessibilityRole::MathElement) {
421 if (coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript))
422 attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "pre");
423 else if (coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript))
424 attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "post");
425 }
426
427 if (is<AccessibilityTable>(*coreObject) && downcast<AccessibilityTable>(*coreObject).isExposableThroughAccessibility()) {
428 auto& table = downcast<AccessibilityTable>(*coreObject);
429 int rowCount = table.axRowCount();
430 if (rowCount)
431 attributeSet = addToAtkAttributeSet(attributeSet, "rowcount", String::number(rowCount).utf8().data());
432
433 int columnCount = table.axColumnCount();
434 if (columnCount)
435 attributeSet = addToAtkAttributeSet(attributeSet, "colcount", String::number(columnCount).utf8().data());
436 } else if (is<AccessibilityTableRow>(*coreObject)) {
437 auto& row = downcast<AccessibilityTableRow>(*coreObject);
438 int rowIndex = row.axRowIndex();
439 if (rowIndex != -1)
440 attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data());
441 } else if (is<AccessibilityTableCell>(*coreObject)) {
442 auto& cell = downcast<AccessibilityTableCell>(*coreObject);
443 int rowIndex = cell.axRowIndex();
444 if (rowIndex != -1)
445 attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data());
446
447 int columnIndex = cell.axColumnIndex();
448 if (columnIndex != -1)
449 attributeSet = addToAtkAttributeSet(attributeSet, "colindex", String::number(columnIndex).utf8().data());
450
451 int rowSpan = cell.axRowSpan();
452 if (rowSpan != -1)
453 attributeSet = addToAtkAttributeSet(attributeSet, "rowspan", String::number(rowSpan).utf8().data());
454
455 int columnSpan = cell.axColumnSpan();
456 if (columnSpan != -1)
457 attributeSet = addToAtkAttributeSet(attributeSet, "colspan", String::number(columnSpan).utf8().data());
458 }
459
460 String placeholder = coreObject->placeholderValue();
461 if (!placeholder.isEmpty())
462 attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data());
463
464 if (coreObject->supportsAutoComplete())
465 attributeSet = addToAtkAttributeSet(attributeSet, "autocomplete", coreObject->autoCompleteValue().utf8().data());
466
467 if (coreObject->supportsHasPopup())
468 attributeSet = addToAtkAttributeSet(attributeSet, "haspopup", coreObject->hasPopupValue().utf8().data());
469
470 if (coreObject->supportsCurrent())
471 attributeSet = addToAtkAttributeSet(attributeSet, "current", coreObject->currentValue().utf8().data());
472
473 // The Core AAM states that an explicitly-set value should be exposed, including "none".
474 if (coreObject->hasAttribute(HTMLNames::aria_sortAttr)) {
475 switch (coreObject->sortDirection()) {
476 case AccessibilitySortDirection::Invalid:
477 break;
478 case AccessibilitySortDirection::Ascending:
479 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "ascending");
480 break;
481 case AccessibilitySortDirection::Descending:
482 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "descending");
483 break;
484 case AccessibilitySortDirection::Other:
485 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "other");
486 break;
487 case AccessibilitySortDirection::None:
488 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "none");
489 }
490 }
491
492 if (coreObject->supportsPosInSet())
493 attributeSet = addToAtkAttributeSet(attributeSet, "posinset", String::number(coreObject->posInSet()).utf8().data());
494
495 if (coreObject->supportsSetSize())
496 attributeSet = addToAtkAttributeSet(attributeSet, "setsize", String::number(coreObject->setSize()).utf8().data());
497
498 String isReadOnly = coreObject->readOnlyValue();
499 if (!isReadOnly.isEmpty())
500 attributeSet = addToAtkAttributeSet(attributeSet, "readonly", isReadOnly.utf8().data());
501
502 String valueDescription = coreObject->valueDescription();
503 if (!valueDescription.isEmpty())
504 attributeSet = addToAtkAttributeSet(attributeSet, "valuetext", valueDescription.utf8().data());
505
506 // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules:
507 // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so."
508 // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string").
509 // We cannot use the computedRoleString for this purpose because it is not limited to elements
510 // with ARIA roles, and it might not contain the actual ARIA role value (e.g. DPub ARIA).
511 String roleString = coreObject->getAttribute(HTMLNames::roleAttr);
512 if (!roleString.isEmpty())
513 attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", roleString.utf8().data());
514
515 String computedRoleString = coreObject->computedRoleString();
516 if (!computedRoleString.isEmpty()) {
517 attributeSet = addToAtkAttributeSet(attributeSet, "computed-role", computedRoleString.utf8().data());
518
519 // The HTML AAM maps several elements to ARIA landmark roles. In order for the type of landmark
520 // to be obtainable in the same fashion as an ARIA landmark, fall back on the computedRoleString.
521 if (coreObject->ariaRoleAttribute() == AccessibilityRole::Unknown && coreObject->isLandmark())
522 attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", computedRoleString.utf8().data());
523 }
524
525 String roleDescription = coreObject->roleDescription();
526 if (!roleDescription.isEmpty())
527 attributeSet = addToAtkAttributeSet(attributeSet, "roledescription", roleDescription.utf8().data());
528
529 // We need to expose the live region attributes even if the live region is currently disabled/off.
530 if (auto liveContainer = coreObject->liveRegionAncestor(false)) {
531 String liveStatus = liveContainer->liveRegionStatus();
532 String relevant = liveContainer->liveRegionRelevant();
533 bool isAtomic = liveContainer->liveRegionAtomic();
534 String liveRole = roleString.isEmpty() ? computedRoleString : roleString;
535
536 // According to the Core AAM, we need to expose the above properties with "container-" prefixed
537 // object attributes regardless of whether the container is this object, or an ancestor of it.
538 attributeSet = addToAtkAttributeSet(attributeSet, "container-live", liveStatus.utf8().data());
539 attributeSet = addToAtkAttributeSet(attributeSet, "container-relevant", relevant.utf8().data());
540 if (isAtomic)
541 attributeSet = addToAtkAttributeSet(attributeSet, "container-atomic", "true");
542 if (!liveRole.isEmpty())
543 attributeSet = addToAtkAttributeSet(attributeSet, "container-live-role", liveRole.utf8().data());
544
545 // According to the Core AAM, if this object is the live region (rather than its descendant),
546 // we must expose the above properties on the object without a "container-" prefix.
547 if (liveContainer == coreObject) {
548 attributeSet = addToAtkAttributeSet(attributeSet, "live", liveStatus.utf8().data());
549 attributeSet = addToAtkAttributeSet(attributeSet, "relevant", relevant.utf8().data());
550 if (isAtomic)
551 attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true");
552 } else if (!isAtomic && coreObject->liveRegionAtomic())
553 attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true");
554 }
555
556 // The Core AAM states the author-provided value should be exposed as-is.
557 String dropEffect = coreObject->getAttribute(HTMLNames::aria_dropeffectAttr);
558 if (!dropEffect.isEmpty())
559 attributeSet = addToAtkAttributeSet(attributeSet, "dropeffect", dropEffect.utf8().data());
560
561 if (coreObject->isARIAGrabbed())
562 attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "true");
563 else if (coreObject->supportsARIADragging())
564 attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "false");
565
566 // The Core AAM states the author-provided value should be exposed as-is.
567 const AtomicString& keyShortcuts = coreObject->keyShortcutsValue();
568 if (!keyShortcuts.isEmpty())
569 attributeSet = addToAtkAttributeSet(attributeSet, "keyshortcuts", keyShortcuts.string().utf8().data());
570
571 return attributeSet;
572}
573
574static AtkRole atkRole(AccessibilityObject* coreObject)
575{
576 switch (coreObject->roleValue()) {
577 case AccessibilityRole::ApplicationAlert:
578 return ATK_ROLE_ALERT;
579 case AccessibilityRole::ApplicationAlertDialog:
580 case AccessibilityRole::ApplicationDialog:
581 return ATK_ROLE_DIALOG;
582 case AccessibilityRole::ApplicationStatus:
583 return ATK_ROLE_STATUSBAR;
584 case AccessibilityRole::Unknown:
585 return ATK_ROLE_UNKNOWN;
586 case AccessibilityRole::Audio:
587#if ATK_CHECK_VERSION(2, 11, 3)
588 return ATK_ROLE_AUDIO;
589#endif
590 case AccessibilityRole::Video:
591#if ATK_CHECK_VERSION(2, 11, 3)
592 return ATK_ROLE_VIDEO;
593#endif
594 return ATK_ROLE_EMBEDDED;
595 case AccessibilityRole::Button:
596 return ATK_ROLE_PUSH_BUTTON;
597 case AccessibilityRole::Switch:
598 case AccessibilityRole::ToggleButton:
599 return ATK_ROLE_TOGGLE_BUTTON;
600 case AccessibilityRole::RadioButton:
601 return ATK_ROLE_RADIO_BUTTON;
602 case AccessibilityRole::CheckBox:
603 return ATK_ROLE_CHECK_BOX;
604 case AccessibilityRole::Slider:
605 return ATK_ROLE_SLIDER;
606 case AccessibilityRole::TabGroup:
607 case AccessibilityRole::TabList:
608 return ATK_ROLE_PAGE_TAB_LIST;
609 case AccessibilityRole::TextField:
610 case AccessibilityRole::TextArea:
611 case AccessibilityRole::SearchField:
612 return ATK_ROLE_ENTRY;
613 case AccessibilityRole::StaticText:
614#if ATK_CHECK_VERSION(2, 15, 2)
615 return ATK_ROLE_STATIC;
616#else
617 return ATK_ROLE_TEXT;
618#endif
619 case AccessibilityRole::Outline:
620 case AccessibilityRole::Tree:
621 return ATK_ROLE_TREE;
622 case AccessibilityRole::TreeItem:
623 return ATK_ROLE_TREE_ITEM;
624 case AccessibilityRole::MenuBar:
625 return ATK_ROLE_MENU_BAR;
626 case AccessibilityRole::MenuListPopup:
627 case AccessibilityRole::Menu:
628 return ATK_ROLE_MENU;
629 case AccessibilityRole::MenuListOption:
630 case AccessibilityRole::MenuItem:
631 case AccessibilityRole::MenuButton:
632 return ATK_ROLE_MENU_ITEM;
633 case AccessibilityRole::MenuItemCheckbox:
634 return ATK_ROLE_CHECK_MENU_ITEM;
635 case AccessibilityRole::MenuItemRadio:
636 return ATK_ROLE_RADIO_MENU_ITEM;
637 case AccessibilityRole::Column:
638 // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
639 return ATK_ROLE_UNKNOWN; // Matches Mozilla
640 case AccessibilityRole::Row:
641 return ATK_ROLE_TABLE_ROW;
642 case AccessibilityRole::Toolbar:
643 return ATK_ROLE_TOOL_BAR;
644 case AccessibilityRole::Meter:
645 return ATK_ROLE_LEVEL_BAR;
646 case AccessibilityRole::BusyIndicator:
647 case AccessibilityRole::ProgressIndicator:
648 return ATK_ROLE_PROGRESS_BAR;
649 case AccessibilityRole::Window:
650 return ATK_ROLE_WINDOW;
651 case AccessibilityRole::PopUpButton:
652 return coreObject->hasPopup() ? ATK_ROLE_PUSH_BUTTON : ATK_ROLE_COMBO_BOX;
653 case AccessibilityRole::ComboBox:
654 return ATK_ROLE_COMBO_BOX;
655 case AccessibilityRole::SplitGroup:
656 return ATK_ROLE_SPLIT_PANE;
657 case AccessibilityRole::Splitter:
658 return ATK_ROLE_SEPARATOR;
659 case AccessibilityRole::ColorWell:
660#if PLATFORM(GTK)
661 // ATK_ROLE_COLOR_CHOOSER is defined as a dialog (i.e. it's what appears when you push the button).
662 return ATK_ROLE_PUSH_BUTTON;
663#endif
664 case AccessibilityRole::List:
665 return ATK_ROLE_LIST;
666 case AccessibilityRole::ScrollBar:
667 return ATK_ROLE_SCROLL_BAR;
668 case AccessibilityRole::ScrollArea:
669 case AccessibilityRole::TabPanel:
670 return ATK_ROLE_SCROLL_PANE;
671 case AccessibilityRole::Grid:
672 case AccessibilityRole::Table:
673 return ATK_ROLE_TABLE;
674 case AccessibilityRole::TreeGrid:
675 return ATK_ROLE_TREE_TABLE;
676 case AccessibilityRole::Application:
677 return ATK_ROLE_APPLICATION;
678 case AccessibilityRole::ApplicationGroup:
679 case AccessibilityRole::Feed:
680 case AccessibilityRole::Figure:
681 case AccessibilityRole::GraphicsObject:
682 case AccessibilityRole::Group:
683 case AccessibilityRole::RadioGroup:
684 case AccessibilityRole::SVGRoot:
685 return ATK_ROLE_PANEL;
686 case AccessibilityRole::RowHeader:
687 return ATK_ROLE_ROW_HEADER;
688 case AccessibilityRole::ColumnHeader:
689 return ATK_ROLE_COLUMN_HEADER;
690 case AccessibilityRole::Caption:
691 return ATK_ROLE_CAPTION;
692 case AccessibilityRole::Cell:
693 case AccessibilityRole::GridCell:
694 return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_TABLE_CELL;
695 case AccessibilityRole::Link:
696 case AccessibilityRole::WebCoreLink:
697 case AccessibilityRole::ImageMapLink:
698 return ATK_ROLE_LINK;
699 case AccessibilityRole::ImageMap:
700 return ATK_ROLE_IMAGE_MAP;
701 case AccessibilityRole::GraphicsSymbol:
702 case AccessibilityRole::Image:
703 return ATK_ROLE_IMAGE;
704 case AccessibilityRole::ListMarker:
705 return ATK_ROLE_TEXT;
706 case AccessibilityRole::DocumentArticle:
707#if ATK_CHECK_VERSION(2, 11, 3)
708 return ATK_ROLE_ARTICLE;
709#endif
710 case AccessibilityRole::Document:
711 case AccessibilityRole::GraphicsDocument:
712 return ATK_ROLE_DOCUMENT_FRAME;
713 case AccessibilityRole::DocumentNote:
714 return ATK_ROLE_COMMENT;
715 case AccessibilityRole::Heading:
716 return ATK_ROLE_HEADING;
717 case AccessibilityRole::ListBox:
718 // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-listbox
719 return coreObject->isDescendantOfRole(AccessibilityRole::ComboBox) ? ATK_ROLE_MENU : ATK_ROLE_LIST_BOX;
720 case AccessibilityRole::ListItem:
721 return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_LIST_ITEM;
722 case AccessibilityRole::ListBoxOption:
723 return coreObject->isDescendantOfRole(AccessibilityRole::ComboBox) ? ATK_ROLE_MENU_ITEM : ATK_ROLE_LIST_ITEM;
724 case AccessibilityRole::Paragraph:
725 return ATK_ROLE_PARAGRAPH;
726 case AccessibilityRole::Label:
727 case AccessibilityRole::Legend:
728 return ATK_ROLE_LABEL;
729 case AccessibilityRole::Blockquote:
730#if ATK_CHECK_VERSION(2, 11, 3)
731 return ATK_ROLE_BLOCK_QUOTE;
732#endif
733 case AccessibilityRole::Footnote:
734#if ATK_CHECK_VERSION(2, 25, 2)
735 return ATK_ROLE_FOOTNOTE;
736#endif
737 case AccessibilityRole::ApplicationTextGroup:
738 case AccessibilityRole::Div:
739 case AccessibilityRole::Pre:
740 case AccessibilityRole::SVGText:
741 case AccessibilityRole::TextGroup:
742 return ATK_ROLE_SECTION;
743 case AccessibilityRole::Footer:
744 return ATK_ROLE_FOOTER;
745 case AccessibilityRole::Form:
746#if ATK_CHECK_VERSION(2, 11, 3)
747 if (coreObject->ariaRoleAttribute() != AccessibilityRole::Unknown)
748 return ATK_ROLE_LANDMARK;
749#endif
750 return ATK_ROLE_FORM;
751 case AccessibilityRole::Canvas:
752 return ATK_ROLE_CANVAS;
753 case AccessibilityRole::HorizontalRule:
754 return ATK_ROLE_SEPARATOR;
755 case AccessibilityRole::SpinButton:
756 return ATK_ROLE_SPIN_BUTTON;
757 case AccessibilityRole::Tab:
758 return ATK_ROLE_PAGE_TAB;
759 case AccessibilityRole::UserInterfaceTooltip:
760 return ATK_ROLE_TOOL_TIP;
761 case AccessibilityRole::WebArea:
762 return ATK_ROLE_DOCUMENT_WEB;
763 case AccessibilityRole::WebApplication:
764 return ATK_ROLE_EMBEDDED;
765#if ATK_CHECK_VERSION(2, 11, 3)
766 case AccessibilityRole::ApplicationLog:
767 return ATK_ROLE_LOG;
768 case AccessibilityRole::ApplicationMarquee:
769 return ATK_ROLE_MARQUEE;
770 case AccessibilityRole::ApplicationTimer:
771 return ATK_ROLE_TIMER;
772 case AccessibilityRole::Definition:
773 return ATK_ROLE_DEFINITION;
774 case AccessibilityRole::DocumentMath:
775 return ATK_ROLE_MATH;
776 case AccessibilityRole::MathElement:
777 if (coreObject->isMathRow())
778 return ATK_ROLE_PANEL;
779 if (coreObject->isMathTable())
780 return ATK_ROLE_TABLE;
781 if (coreObject->isMathTableRow())
782 return ATK_ROLE_TABLE_ROW;
783 if (coreObject->isMathTableCell())
784 return ATK_ROLE_TABLE_CELL;
785 if (coreObject->isMathSubscriptSuperscript() || coreObject->isMathMultiscript())
786 return ATK_ROLE_SECTION;
787#if ATK_CHECK_VERSION(2, 15, 4)
788 if (coreObject->isMathFraction())
789 return ATK_ROLE_MATH_FRACTION;
790 if (coreObject->isMathSquareRoot() || coreObject->isMathRoot())
791 return ATK_ROLE_MATH_ROOT;
792 if (coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Subscript)
793 || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript))
794 return ATK_ROLE_SUBSCRIPT;
795 if (coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Superscript)
796 || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript))
797 return ATK_ROLE_SUPERSCRIPT;
798#endif
799#if ATK_CHECK_VERSION(2, 15, 2)
800 if (coreObject->isMathToken())
801 return ATK_ROLE_STATIC;
802#endif
803 return ATK_ROLE_UNKNOWN;
804 case AccessibilityRole::LandmarkBanner:
805 case AccessibilityRole::LandmarkComplementary:
806 case AccessibilityRole::LandmarkContentInfo:
807 case AccessibilityRole::LandmarkDocRegion:
808 case AccessibilityRole::LandmarkMain:
809 case AccessibilityRole::LandmarkNavigation:
810 case AccessibilityRole::LandmarkRegion:
811 case AccessibilityRole::LandmarkSearch:
812 return ATK_ROLE_LANDMARK;
813#endif
814#if ATK_CHECK_VERSION(2, 11, 4)
815 case AccessibilityRole::DescriptionList:
816 return ATK_ROLE_DESCRIPTION_LIST;
817 case AccessibilityRole::Term:
818 case AccessibilityRole::DescriptionListTerm:
819 return ATK_ROLE_DESCRIPTION_TERM;
820 case AccessibilityRole::DescriptionListDetail:
821 return ATK_ROLE_DESCRIPTION_VALUE;
822#endif
823 case AccessibilityRole::Inline:
824#if ATK_CHECK_VERSION(2, 15, 4)
825 if (coreObject->isSubscriptStyleGroup())
826 return ATK_ROLE_SUBSCRIPT;
827 if (coreObject->isSuperscriptStyleGroup())
828 return ATK_ROLE_SUPERSCRIPT;
829#endif
830#if ATK_CHECK_VERSION(2, 15, 2)
831 return ATK_ROLE_STATIC;
832 case AccessibilityRole::SVGTextPath:
833 case AccessibilityRole::SVGTSpan:
834 case AccessibilityRole::Time:
835 return ATK_ROLE_STATIC;
836#endif
837 default:
838 return ATK_ROLE_UNKNOWN;
839 }
840}
841
842static AtkRole webkitAccessibleGetRole(AtkObject* object)
843{
844 // ATK_ROLE_UNKNOWN should only be applied in cases where there is a valid
845 // WebCore accessible object for which the platform role mapping is unknown.
846 auto* accessible = WEBKIT_ACCESSIBLE(object);
847 returnValIfWebKitAccessibleIsInvalid(accessible, ATK_ROLE_INVALID);
848
849 // Note: Why doesn't WebCore have a password field for this
850 if (accessible->priv->object->isPasswordField())
851 return ATK_ROLE_PASSWORD_TEXT;
852
853 return atkRole(accessible->priv->object);
854}
855
856static bool isTextWithCaret(AccessibilityObject* coreObject)
857{
858 if (!coreObject || !coreObject->isAccessibilityRenderObject())
859 return false;
860
861 Document* document = coreObject->document();
862 if (!document)
863 return false;
864
865 Frame* frame = document->frame();
866 if (!frame)
867 return false;
868
869 if (!frame->settings().caretBrowsingEnabled())
870 return false;
871
872 // Check text objects and paragraphs only.
873 auto* axObject = coreObject->wrapper();
874 AtkRole role = axObject ? atk_object_get_role(ATK_OBJECT(axObject)) : ATK_ROLE_INVALID;
875 if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
876 return false;
877
878 // Finally, check whether the caret is set in the current object.
879 VisibleSelection selection = coreObject->selection();
880 if (!selection.isCaret())
881 return false;
882
883 return selectionBelongsToObject(coreObject, selection);
884}
885
886static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
887{
888 AccessibilityObject* parent = coreObject->parentObject();
889 bool isListBoxOption = parent && parent->isListBox();
890
891 // Please keep the state list in alphabetical order
892 if ((isListBoxOption && coreObject->isSelectedOptionActive())
893 || coreObject->currentState() != AccessibilityCurrentState::False)
894 atk_state_set_add_state(stateSet, ATK_STATE_ACTIVE);
895
896 if (coreObject->isBusy())
897 atk_state_set_add_state(stateSet, ATK_STATE_BUSY);
898
899#if ATK_CHECK_VERSION(2,11,2)
900 if (coreObject->supportsChecked() && coreObject->canSetValueAttribute())
901 atk_state_set_add_state(stateSet, ATK_STATE_CHECKABLE);
902#endif
903
904 if (coreObject->isChecked())
905 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
906
907 if ((coreObject->isTextControl() || coreObject->isNonNativeTextControl()) && coreObject->canSetValueAttribute())
908 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
909
910 // FIXME: Put both ENABLED and SENSITIVE together here for now
911 if (coreObject->isEnabled()) {
912 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
913 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
914 }
915
916 if (coreObject->canSetExpandedAttribute())
917 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
918
919 if (coreObject->isExpanded())
920 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
921
922 if (coreObject->canSetFocusAttribute())
923 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
924
925 // According to the Core AAM, if the element which is focused has a valid aria-activedescendant,
926 // we should not expose the focused state on the element which is actually focused, but instead
927 // on its active descendant.
928 if ((coreObject->isFocused() && !coreObject->activeDescendant()) || isTextWithCaret(coreObject))
929 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
930 else if (coreObject->isActiveDescendantOfFocusedContainer()) {
931 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
932 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
933 }
934
935 if (coreObject->orientation() == AccessibilityOrientation::Horizontal)
936 atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL);
937 else if (coreObject->orientation() == AccessibilityOrientation::Vertical)
938 atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL);
939
940 if (coreObject->hasPopup())
941 atk_state_set_add_state(stateSet, ATK_STATE_HAS_POPUP);
942
943 if (coreObject->isIndeterminate())
944 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
945 else if (coreObject->isCheckboxOrRadio() || coreObject->isMenuItem() || coreObject->isToggleButton()) {
946 if (coreObject->checkboxOrRadioValue() == AccessibilityButtonState::Mixed)
947 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
948 }
949
950 if (coreObject->isModalNode())
951 atk_state_set_add_state(stateSet, ATK_STATE_MODAL);
952
953 if (coreObject->invalidStatus() != "false")
954 atk_state_set_add_state(stateSet, ATK_STATE_INVALID_ENTRY);
955
956 if (coreObject->isMultiSelectable())
957 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
958
959 // TODO: ATK_STATE_OPAQUE
960
961 if (coreObject->isPressed())
962 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
963
964#if ATK_CHECK_VERSION(2,15,3)
965 if (!coreObject->canSetValueAttribute() && (coreObject->supportsReadOnly()))
966 atk_state_set_add_state(stateSet, ATK_STATE_READ_ONLY);
967#endif
968
969 if (coreObject->isRequired())
970 atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED);
971
972 // TODO: ATK_STATE_SELECTABLE_TEXT
973
974 if (coreObject->canSetSelectedAttribute()) {
975 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
976 // Items in focusable lists have both STATE_SELECT{ABLE,ED}
977 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on
978 // the former.
979 if (isListBoxOption)
980 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
981 }
982
983 if (coreObject->isSelected()) {
984 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
985 // Items in focusable lists have both STATE_SELECT{ABLE,ED}
986 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
987 // former.
988 if (isListBoxOption)
989 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
990 }
991
992 // FIXME: Group both SHOWING and VISIBLE here for now
993 // Not sure how to handle this in WebKit, see bug
994 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
995 // issues with SHOWING vs VISIBLE.
996 if (!coreObject->isOffScreen()) {
997 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
998 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
999 }
1000
1001 // Mutually exclusive, so we group these two
1002 if (coreObject->roleValue() == AccessibilityRole::TextArea || coreObject->ariaIsMultiline())
1003 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
1004 else if (coreObject->roleValue() == AccessibilityRole::TextField || coreObject->roleValue() == AccessibilityRole::SearchField)
1005 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
1006
1007 // TODO: ATK_STATE_SENSITIVE
1008
1009 if (coreObject->supportsAutoComplete() && coreObject->autoCompleteValue() != "none")
1010 atk_state_set_add_state(stateSet, ATK_STATE_SUPPORTS_AUTOCOMPLETION);
1011
1012 if (coreObject->isVisited())
1013 atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
1014}
1015
1016static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object)
1017{
1018 auto* accessible = WEBKIT_ACCESSIBLE(object);
1019 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object);
1020
1021 // Make sure the layout is updated to really know whether the object
1022 // is defunct or not, so we can return the proper state.
1023 accessible->priv->object->updateBackingStore();
1024
1025 if (accessible->priv->object == fallbackObject()) {
1026 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
1027 return stateSet;
1028 }
1029
1030 // Text objects must be focusable.
1031 AtkRole role = atk_object_get_role(object);
1032 if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
1033 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
1034
1035 setAtkStateSetFromCoreObject(accessible->priv->object, stateSet);
1036 return stateSet;
1037}
1038
1039static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object)
1040{
1041 auto* accessible = WEBKIT_ACCESSIBLE(object);
1042 returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
1043
1044 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object);
1045 setAtkRelationSetFromCoreObject(accessible->priv->object, relationSet);
1046 return relationSet;
1047}
1048
1049static void webkitAccessibleInit(AtkObject* object, gpointer data)
1050{
1051 if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize)
1052 ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data);
1053
1054 WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object);
1055 accessible->priv->object = reinterpret_cast<AccessibilityObject*>(data);
1056}
1057
1058static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object)
1059{
1060 auto* accessible = WEBKIT_ACCESSIBLE(object);
1061 returnValIfWebKitAccessibleIsInvalid(accessible, nullptr);
1062
1063 if (ATK_IS_DOCUMENT(object)) {
1064 // TODO: Should we fall back on lang xml:lang when the following comes up empty?
1065 String language = accessible->priv->object->language();
1066 if (!language.isEmpty())
1067 return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedDocumentLocale, language.utf8());
1068
1069 } else if (ATK_IS_TEXT(object)) {
1070 const gchar* locale = nullptr;
1071
1072 AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object));
1073 for (auto* attributes = textAttributes; attributes; attributes = attributes->next) {
1074 auto* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
1075 if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) {
1076 locale = webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedDocumentLocale, atkAttribute->value);
1077 break;
1078 }
1079 }
1080 atk_attribute_set_free(textAttributes);
1081
1082 return locale;
1083 }
1084
1085 return nullptr;
1086}
1087
1088static void webkit_accessible_class_init(WebKitAccessibleClass* klass)
1089{
1090 auto* atkObjectClass = ATK_OBJECT_CLASS(klass);
1091 atkObjectClass->initialize = webkitAccessibleInit;
1092 atkObjectClass->get_name = webkitAccessibleGetName;
1093 atkObjectClass->get_description = webkitAccessibleGetDescription;
1094 atkObjectClass->get_parent = webkitAccessibleGetParent;
1095 atkObjectClass->get_n_children = webkitAccessibleGetNChildren;
1096 atkObjectClass->ref_child = webkitAccessibleRefChild;
1097 atkObjectClass->get_role = webkitAccessibleGetRole;
1098 atkObjectClass->ref_state_set = webkitAccessibleRefStateSet;
1099 atkObjectClass->get_index_in_parent = webkitAccessibleGetIndexInParent;
1100 atkObjectClass->get_attributes = webkitAccessibleGetAttributes;
1101 atkObjectClass->ref_relation_set = webkitAccessibleRefRelationSet;
1102 atkObjectClass->get_object_locale = webkitAccessibleGetObjectLocale;
1103}
1104
1105static const GInterfaceInfo atkInterfacesInitFunctions[] = {
1106 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleActionInterfaceInit)), nullptr, nullptr},
1107 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleSelectionInterfaceInit)), nullptr, nullptr},
1108 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleEditableTextInterfaceInit)), nullptr, nullptr},
1109 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTextInterfaceInit)), nullptr, nullptr},
1110 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleComponentInterfaceInit)), nullptr, nullptr},
1111 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleImageInterfaceInit)), nullptr, nullptr},
1112 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTableInterfaceInit)), nullptr, nullptr},
1113#if ATK_CHECK_VERSION(2,11,90)
1114 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTableCellInterfaceInit)), nullptr, nullptr},
1115#endif
1116 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleHypertextInterfaceInit)), nullptr, nullptr},
1117 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleHyperlinkImplInterfaceInit)), nullptr, nullptr},
1118 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleDocumentInterfaceInit)), nullptr, nullptr},
1119 {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleValueInterfaceInit)), nullptr, nullptr}
1120};
1121
1122enum WAIType {
1123 WAIAction,
1124 WAISelection,
1125 WAIEditableText,
1126 WAIText,
1127 WAIComponent,
1128 WAIImage,
1129 WAITable,
1130#if ATK_CHECK_VERSION(2,11,90)
1131 WAITableCell,
1132#endif
1133 WAIHypertext,
1134 WAIHyperlink,
1135 WAIDocument,
1136 WAIValue,
1137};
1138
1139static GType atkInterfaceTypeFromWAIType(WAIType type)
1140{
1141 switch (type) {
1142 case WAIAction:
1143 return ATK_TYPE_ACTION;
1144 case WAISelection:
1145 return ATK_TYPE_SELECTION;
1146 case WAIEditableText:
1147 return ATK_TYPE_EDITABLE_TEXT;
1148 case WAIText:
1149 return ATK_TYPE_TEXT;
1150 case WAIComponent:
1151 return ATK_TYPE_COMPONENT;
1152 case WAIImage:
1153 return ATK_TYPE_IMAGE;
1154 case WAITable:
1155 return ATK_TYPE_TABLE;
1156#if ATK_CHECK_VERSION(2,11,90)
1157 case WAITableCell:
1158 return ATK_TYPE_TABLE_CELL;
1159#endif
1160 case WAIHypertext:
1161 return ATK_TYPE_HYPERTEXT;
1162 case WAIHyperlink:
1163 return ATK_TYPE_HYPERLINK_IMPL;
1164 case WAIDocument:
1165 return ATK_TYPE_DOCUMENT;
1166 case WAIValue:
1167 return ATK_TYPE_VALUE;
1168 }
1169
1170 return G_TYPE_INVALID;
1171}
1172
1173static bool roleIsTextType(AccessibilityRole role)
1174{
1175 return role == AccessibilityRole::Paragraph
1176 || role == AccessibilityRole::Heading
1177 || role == AccessibilityRole::Div
1178 || role == AccessibilityRole::Cell
1179 || role == AccessibilityRole::Link
1180 || role == AccessibilityRole::WebCoreLink
1181 || role == AccessibilityRole::ListItem
1182 || role == AccessibilityRole::Pre
1183 || role == AccessibilityRole::GridCell
1184 || role == AccessibilityRole::TextGroup
1185 || role == AccessibilityRole::ApplicationTextGroup
1186 || role == AccessibilityRole::ApplicationGroup;
1187}
1188
1189static guint16 interfaceMaskFromObject(AccessibilityObject* coreObject)
1190{
1191 guint16 interfaceMask = 0;
1192
1193 // Component interface is always supported
1194 interfaceMask |= 1 << WAIComponent;
1195
1196 AccessibilityRole role = coreObject->roleValue();
1197
1198 // Action
1199 // As the implementation of the AtkAction interface is a very
1200 // basic one (just relays in executing the default action for each
1201 // object, and only supports having one action per object), it is
1202 // better just to implement this interface for every instance of
1203 // the WebKitAccessible class and let WebCore decide what to do.
1204 interfaceMask |= 1 << WAIAction;
1205
1206 // Selection
1207 if (coreObject->canHaveSelectedChildren() || coreObject->isMenuList())
1208 interfaceMask |= 1 << WAISelection;
1209
1210 // Get renderer if available.
1211 RenderObject* renderer = nullptr;
1212 if (coreObject->isAccessibilityRenderObject())
1213 renderer = coreObject->renderer();
1214
1215 // Hyperlink (links and embedded objects).
1216 if (coreObject->isLink() || (renderer && renderer->isReplaced()))
1217 interfaceMask |= 1 << WAIHyperlink;
1218
1219 // Text, Editable Text & Hypertext
1220 if (role == AccessibilityRole::StaticText || coreObject->isMenuListOption())
1221 interfaceMask |= 1 << WAIText;
1222 else if (coreObject->isTextControl() || coreObject->isNonNativeTextControl()) {
1223 interfaceMask |= 1 << WAIText;
1224 if (coreObject->canSetValueAttribute())
1225 interfaceMask |= 1 << WAIEditableText;
1226 } else if (!coreObject->isWebArea()) {
1227 if (role != AccessibilityRole::Table) {
1228 interfaceMask |= 1 << WAIHypertext;
1229 if ((renderer && renderer->childrenInline()) || roleIsTextType(role) || coreObject->isMathToken())
1230 interfaceMask |= 1 << WAIText;
1231 }
1232
1233 // Add the TEXT interface for list items whose
1234 // first accessible child has a text renderer
1235 if (role == AccessibilityRole::ListItem) {
1236 const auto& children = coreObject->children();
1237 if (!children.isEmpty())
1238 interfaceMask |= interfaceMaskFromObject(children[0].get());
1239 }
1240 }
1241
1242 // Image
1243 if (coreObject->isImage())
1244 interfaceMask |= 1 << WAIImage;
1245
1246 // Table
1247 if (coreObject->isTable())
1248 interfaceMask |= 1 << WAITable;
1249
1250#if ATK_CHECK_VERSION(2,11,90)
1251 if (role == AccessibilityRole::Cell || role == AccessibilityRole::GridCell || role == AccessibilityRole::ColumnHeader || role == AccessibilityRole::RowHeader)
1252 interfaceMask |= 1 << WAITableCell;
1253#endif
1254
1255 // Document
1256 if (role == AccessibilityRole::WebArea)
1257 interfaceMask |= 1 << WAIDocument;
1258
1259 // Value
1260 if (coreObject->supportsRangeValue())
1261 interfaceMask |= 1 << WAIValue;
1262
1263#if ENABLE(INPUT_TYPE_COLOR)
1264 // Color type.
1265 if (role == AccessibilityRole::ColorWell)
1266 interfaceMask |= 1 << WAIText;
1267#endif
1268
1269 return interfaceMask;
1270}
1271
1272static const char* uniqueAccessibilityTypeName(guint16 interfaceMask)
1273{
1274#define WAI_TYPE_NAME_LEN (30) // Enough for prefix + 5 hex characters (max).
1275 static char name[WAI_TYPE_NAME_LEN + 1];
1276
1277 g_sprintf(name, "WAIType%x", interfaceMask);
1278 name[WAI_TYPE_NAME_LEN] = '\0';
1279
1280 return name;
1281}
1282
1283static GType accessibilityTypeFromObject(AccessibilityObject* coreObject)
1284{
1285 static const GTypeInfo typeInfo = {
1286 sizeof(WebKitAccessibleClass),
1287 nullptr, // GBaseInitFunc
1288 nullptr, // GBaseFinalizeFunc
1289 nullptr, // GClassInitFunc
1290 nullptr, // GClassFinalizeFunc
1291 nullptr, // class data
1292 sizeof(WebKitAccessible), // instance size
1293 0, // nb preallocs
1294 nullptr, // GInstanceInitFunc
1295 nullptr // value table
1296 };
1297
1298 guint16 interfaceMask = interfaceMaskFromObject(coreObject);
1299 const char* atkTypeName = uniqueAccessibilityTypeName(interfaceMask);
1300 if (GType type = g_type_from_name(atkTypeName))
1301 return type;
1302
1303 GType type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, static_cast<GTypeFlags>(0));
1304 for (unsigned i = 0; i < G_N_ELEMENTS(atkInterfacesInitFunctions); ++i) {
1305 if (interfaceMask & (1 << i)) {
1306 g_type_add_interface_static(type,
1307 atkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
1308 &atkInterfacesInitFunctions[i]);
1309 }
1310 }
1311
1312 return type;
1313}
1314
1315WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject)
1316{
1317 auto* object = ATK_OBJECT(g_object_new(accessibilityTypeFromObject(coreObject), nullptr));
1318 atk_object_initialize(object, coreObject);
1319 return WEBKIT_ACCESSIBLE(object);
1320}
1321
1322AccessibilityObject& webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible)
1323{
1324 ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1325 return *accessible->priv->object;
1326}
1327
1328void webkitAccessibleDetach(WebKitAccessible* accessible)
1329{
1330 ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1331 ASSERT(accessible->priv->object != fallbackObject());
1332
1333 if (accessible->priv->object->roleValue() == AccessibilityRole::WebArea)
1334 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_DEFUNCT, TRUE);
1335
1336 // We replace the WebCore AccessibilityObject with a fallback object that
1337 // provides default implementations to avoid repetitive null-checking after
1338 // detachment.
1339 accessible->priv->object = fallbackObject();
1340}
1341
1342bool webkitAccessibleIsDetached(WebKitAccessible* accessible)
1343{
1344 ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1345 return accessible->priv->object == fallbackObject();
1346}
1347
1348const char* webkitAccessibleCacheAndReturnAtkProperty(WebKitAccessible* accessible, AtkCachedProperty property, CString&& value)
1349{
1350 ASSERT(WEBKIT_IS_ACCESSIBLE(accessible));
1351
1352 WebKitAccessiblePrivate* priv = accessible->priv;
1353 CString* propertyPtr = nullptr;
1354
1355 switch (property) {
1356 case AtkCachedAccessibleName:
1357 propertyPtr = &priv->accessibleName;
1358 break;
1359 case AtkCachedAccessibleDescription:
1360 propertyPtr = &priv->accessibleDescription;
1361 break;
1362 case AtkCachedActionName:
1363 propertyPtr = &priv->actionName;
1364 break;
1365 case AtkCachedActionKeyBinding:
1366 propertyPtr = &priv->actionKeyBinding;
1367 break;
1368 case AtkCachedDocumentLocale:
1369 propertyPtr = &priv->documentLocale;
1370 break;
1371 case AtkCachedDocumentType:
1372 propertyPtr = &priv->documentType;
1373 break;
1374 case AtkCachedDocumentEncoding:
1375 propertyPtr = &priv->documentEncoding;
1376 break;
1377 case AtkCachedDocumentURI:
1378 propertyPtr = &priv->documentURI;
1379 break;
1380 case AtkCachedImageDescription:
1381 propertyPtr = &priv->imageDescription;
1382 break;
1383 default:
1384 ASSERT_NOT_REACHED();
1385 }
1386
1387 // Don't invalidate old memory if not stricly needed, since other
1388 // callers might be still holding on to it.
1389 if (*propertyPtr != value)
1390 *propertyPtr = WTFMove(value);
1391
1392 return (*propertyPtr).data();
1393}
1394
1395#endif // HAVE(ACCESSIBILITY)
1396