2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Jan Alonzo
4 * Copyright (C) 2009, 2010, 2011, 2012 Igalia S.L.
5 * Copyright (C) 2013 Samsung Electronics
7 * Portions from Mozilla a11y, copyright as follows:
9 * The Original Code is mozilla.org code.
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.
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.
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.
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.
33 #include "WebKitAccessibleWrapperAtk.h"
35 #if HAVE(ACCESSIBILITY)
37 #include "AXObjectCache.h"
38 #include "AccessibilityList.h"
39 #include "AccessibilityListBoxOption.h"
40 #include "AccessibilityTable.h"
41 #include "AccessibilityTableCell.h"
42 #include "AccessibilityTableRow.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"
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/text/CString.h>
74 using namespace WebCore;
76 struct _WebKitAccessiblePrivate {
77 // Cached data for AtkObject.
78 CString accessibleName;
79 CString accessibleDescription;
81 // Cached data for AtkAction.
83 CString actionKeyBinding;
85 // Cached data for AtkDocument.
86 CString documentLocale;
88 CString documentEncoding;
91 // Cached data for AtkImage.
92 CString imageDescription;
95 #define WEBKIT_ACCESSIBLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessiblePrivate))
97 static AccessibilityObject* fallbackObject()
99 static AccessibilityObject* object = &AccessibilityListBoxOption::create().leakRef();
103 static AccessibilityObject* core(AtkObject* object)
105 if (!WEBKIT_IS_ACCESSIBLE(object))
108 return webkitAccessibleGetAccessibilityObject(WEBKIT_ACCESSIBLE(object));
111 static const gchar* webkitAccessibleGetName(AtkObject* object)
113 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
114 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
116 Vector<AccessibilityText> textOrder;
117 core(object)->accessibilityText(textOrder);
119 for (const auto& text : textOrder) {
120 // FIXME: This check is here because AccessibilityNodeObject::titleElementText()
121 // appends an empty String for the LabelByElementText source when there is a
122 // titleUIElement(). Removing this check makes some fieldsets lose their name.
123 if (text.text.isEmpty())
126 // WebCore Accessibility should provide us with the text alternative computation
127 // in the order defined by that spec. So take the first thing that our platform
128 // does not expose via the AtkObject description.
129 if (text.textSource != HelpText && text.textSource != SummaryText)
130 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, text.text);
133 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, "");
136 static const gchar* webkitAccessibleGetDescription(AtkObject* object)
138 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
139 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
141 Vector<AccessibilityText> textOrder;
142 core(object)->accessibilityText(textOrder);
144 bool nameTextAvailable = false;
145 for (const auto& text : textOrder) {
146 // WebCore Accessibility should provide us with the text alternative computation
147 // in the order defined by that spec. So take the first thing that our platform
148 // does not expose via the AtkObject name.
149 if (text.textSource == HelpText || text.textSource == SummaryText)
150 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, text.text);
152 // If there is no other text alternative, the title tag contents will have been
153 // used for the AtkObject name. We don't want to duplicate it here.
154 if (text.textSource == TitleTagText && nameTextAvailable)
155 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, text.text);
157 nameTextAvailable = true;
160 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, "");
163 static void removeAtkRelationByType(AtkRelationSet* relationSet, AtkRelationType relationType)
165 int count = atk_relation_set_get_n_relations(relationSet);
166 for (int i = 0; i < count; i++) {
167 AtkRelation* relation = atk_relation_set_get_relation(relationSet, i);
168 if (atk_relation_get_relation_type(relation) == relationType) {
169 atk_relation_set_remove(relationSet, relation);
175 static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
177 // Elements with aria-labelledby should have the labelled-by relation as per the ARIA AAM spec.
178 // Controls with a label element and fieldsets with a legend element should also use this relation
179 // as per the HTML AAM spec. The reciprocal label-for relation should also be used.
180 removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY);
181 removeAtkRelationByType(relationSet, ATK_RELATION_LABEL_FOR);
182 if (coreObject->isControl()) {
183 if (AccessibilityObject* label = coreObject->correspondingLabelForControlElement())
184 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
185 } else if (coreObject->isFieldset()) {
186 if (AccessibilityObject* label = coreObject->titleUIElement())
187 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
188 } else if (coreObject->roleValue() == LegendRole) {
189 if (RenderBlock* renderFieldset = ancestorsOfType<RenderBlock>(*coreObject->renderer()).first()) {
190 if (renderFieldset->isFieldset()) {
191 AccessibilityObject* fieldset = coreObject->axObjectCache()->getOrCreate(renderFieldset);
192 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, fieldset->wrapper());
195 } else if (AccessibilityObject* control = coreObject->correspondingControlForLabelElement()) {
196 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper());
198 AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements;
199 coreObject->ariaLabelledByElements(ariaLabelledByElements);
200 for (const auto& accessibilityObject : ariaLabelledByElements)
201 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, accessibilityObject->wrapper());
204 // Elements referenced by aria-labelledby should have the label-for relation as per the ARIA AAM spec.
205 AccessibilityObject::AccessibilityChildrenVector labels;
206 coreObject->ariaLabelledByReferencingElements(labels);
207 for (const auto& accessibilityObject : labels)
208 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, accessibilityObject->wrapper());
210 // Elements with aria-flowto should have the flows-to relation as per the ARIA AAM spec.
211 removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_TO);
212 AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements;
213 coreObject->ariaFlowToElements(ariaFlowToElements);
214 for (const auto& accessibilityObject : ariaFlowToElements)
215 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_TO, accessibilityObject->wrapper());
217 // Elements referenced by aria-flowto should have the flows-from relation as per the ARIA AAM spec.
218 removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_FROM);
219 AccessibilityObject::AccessibilityChildrenVector flowFrom;
220 coreObject->ariaFlowToReferencingElements(flowFrom);
221 for (const auto& accessibilityObject : flowFrom)
222 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_FROM, accessibilityObject->wrapper());
224 // Elements with aria-describedby should have the described-by relation as per the ARIA AAM spec.
225 removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIBED_BY);
226 AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements;
227 coreObject->ariaDescribedByElements(ariaDescribedByElements);
228 for (const auto& accessibilityObject : ariaDescribedByElements)
229 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY, accessibilityObject->wrapper());
231 // Elements referenced by aria-describedby should have the description-for relation as per the ARIA AAM spec.
232 removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIPTION_FOR);
233 AccessibilityObject::AccessibilityChildrenVector describers;
234 coreObject->ariaDescribedByReferencingElements(describers);
235 for (const auto& accessibilityObject : describers)
236 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIPTION_FOR, accessibilityObject->wrapper());
238 // Elements with aria-controls should have the controller-for relation as per the ARIA AAM spec.
239 removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLER_FOR);
240 AccessibilityObject::AccessibilityChildrenVector ariaControls;
241 coreObject->ariaControlsElements(ariaControls);
242 for (const auto& accessibilityObject : ariaControls)
243 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLER_FOR, accessibilityObject->wrapper());
245 // Elements referenced by aria-controls should have the controlled-by relation as per the ARIA AAM spec.
246 removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLED_BY);
247 AccessibilityObject::AccessibilityChildrenVector controllers;
248 coreObject->ariaControlsReferencingElements(controllers);
249 for (const auto& accessibilityObject : controllers)
250 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLED_BY, accessibilityObject->wrapper());
252 // Elements with aria-owns should have the node-parent-of relation as per the ARIA AAM spec.
253 removeAtkRelationByType(relationSet, ATK_RELATION_NODE_PARENT_OF);
254 AccessibilityObject::AccessibilityChildrenVector ariaOwns;
255 coreObject->ariaOwnsElements(ariaOwns);
256 for (const auto& accessibilityObject : ariaOwns)
257 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_NODE_PARENT_OF, accessibilityObject->wrapper());
259 // Elements referenced by aria-owns should have the node-child-of relation as per the ARIA AAM spec.
260 removeAtkRelationByType(relationSet, ATK_RELATION_NODE_CHILD_OF);
261 AccessibilityObject::AccessibilityChildrenVector owners;
262 coreObject->ariaOwnsReferencingElements(owners);
263 for (const auto& accessibilityObject : owners)
264 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_NODE_CHILD_OF, accessibilityObject->wrapper());
266 #if ATK_CHECK_VERSION(2, 25, 2)
267 // Elements with aria-details should have the details relation as per the ARIA AAM spec.
268 removeAtkRelationByType(relationSet, ATK_RELATION_DETAILS);
269 AccessibilityObject::AccessibilityChildrenVector ariaDetails;
270 coreObject->ariaDetailsElements(ariaDetails);
271 for (const auto& accessibilityObject : ariaDetails)
272 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DETAILS, accessibilityObject->wrapper());
274 // Elements referenced by aria-details should have the details-for relation as per the ARIA AAM spec.
275 removeAtkRelationByType(relationSet, ATK_RELATION_DETAILS_FOR);
276 AccessibilityObject::AccessibilityChildrenVector details;
277 coreObject->ariaDetailsReferencingElements(details);
278 for (const auto& accessibilityObject : details)
279 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DETAILS_FOR, accessibilityObject->wrapper());
281 // Elements with aria-errormessage should have the error-message relation as per the ARIA AAM spec.
282 removeAtkRelationByType(relationSet, ATK_RELATION_ERROR_MESSAGE);
283 AccessibilityObject::AccessibilityChildrenVector ariaErrorMessage;
284 coreObject->ariaErrorMessageElements(ariaErrorMessage);
285 for (const auto& accessibilityObject : ariaErrorMessage)
286 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_ERROR_MESSAGE, accessibilityObject->wrapper());
288 // Elements referenced by aria-errormessage should have the error-for relation as per the ARIA AAM spec.
289 removeAtkRelationByType(relationSet, ATK_RELATION_ERROR_FOR);
290 AccessibilityObject::AccessibilityChildrenVector errors;
291 coreObject->ariaErrorMessageReferencingElements(errors);
292 for (const auto& accessibilityObject : errors)
293 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_ERROR_FOR, accessibilityObject->wrapper());
297 static gpointer webkitAccessibleParentClass = nullptr;
299 static bool isRootObject(AccessibilityObject* coreObject)
301 // The root accessible object in WebCore is always an object with
302 // the ScrolledArea role with one child with the WebArea role.
303 if (!coreObject || !coreObject->isScrollView())
306 AccessibilityObject* firstChild = coreObject->firstChild();
307 if (!firstChild || !firstChild->isWebArea())
313 static AtkObject* atkParentOfRootObject(AtkObject* object)
315 AccessibilityObject* coreObject = core(object);
316 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
318 // The top level object claims to not have a parent. This makes it
319 // impossible for assistive technologies to ascend the accessible
320 // hierarchy all the way to the application. (Bug 30489)
321 if (!coreParent && isRootObject(coreObject)) {
322 Document* document = coreObject->document();
330 return coreParent->wrapper();
333 static AtkObject* webkitAccessibleGetParent(AtkObject* object)
335 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
336 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
338 // Check first if the parent has been already set.
339 AtkObject* accessibleParent = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->get_parent(object);
340 if (accessibleParent)
341 return accessibleParent;
343 // Parent not set yet, so try to find it in the hierarchy.
344 AccessibilityObject* coreObject = core(object);
348 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
350 if (!coreParent && isRootObject(coreObject))
351 return atkParentOfRootObject(object);
356 return coreParent->wrapper();
359 static gint webkitAccessibleGetNChildren(AtkObject* object)
361 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
362 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
364 AccessibilityObject* coreObject = core(object);
366 return coreObject->children().size();
369 static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index)
371 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
372 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
377 AccessibilityObject* coreObject = core(object);
378 AccessibilityObject* coreChild = nullptr;
380 const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children();
381 if (static_cast<size_t>(index) >= children.size())
383 coreChild = children.at(index).get();
388 AtkObject* child = coreChild->wrapper();
389 atk_object_set_parent(child, object);
395 static gint webkitAccessibleGetIndexInParent(AtkObject* object)
397 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), -1);
398 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), -1);
400 AccessibilityObject* coreObject = core(object);
401 AccessibilityObject* parent = coreObject->parentObjectUnignored();
403 if (!parent && isRootObject(coreObject)) {
404 AtkObject* atkParent = atkParentOfRootObject(object);
408 unsigned count = atk_object_get_n_accessible_children(atkParent);
409 for (unsigned i = 0; i < count; ++i) {
410 AtkObject* child = atk_object_ref_accessible_child(atkParent, i);
411 bool childIsObject = child == object;
412 g_object_unref(child);
421 size_t index = parent->children().find(coreObject);
422 return (index == WTF::notFound) ? -1 : index;
425 static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object)
427 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
428 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
430 AtkAttributeSet* attributeSet = nullptr;
432 attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk");
435 AccessibilityObject* coreObject = core(object);
439 // Hack needed for WebKit2 tests because obtaining an element by its ID
440 // cannot be done from the UIProcess. Assistive technologies have no need
441 // for this information.
442 Element* element = coreObject->element() ? coreObject->element() : coreObject->actionElement();
444 String tagName = element->tagName();
445 if (!tagName.isEmpty())
446 attributeSet = addToAtkAttributeSet(attributeSet, "tag", tagName.convertToASCIILowercase().utf8().data());
447 String id = element->getIdAttribute().string();
449 attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data());
452 int level = coreObject->isHeading() ? coreObject->headingLevel() : coreObject->hierarchicalLevel();
454 String value = String::number(level);
455 attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data());
458 if (coreObject->roleValue() == MathElementRole) {
459 if (coreObject->isMathMultiscriptObject(PreSuperscript) || coreObject->isMathMultiscriptObject(PreSubscript))
460 attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "pre");
461 else if (coreObject->isMathMultiscriptObject(PostSuperscript) || coreObject->isMathMultiscriptObject(PostSubscript))
462 attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "post");
465 if (is<AccessibilityTable>(*coreObject) && downcast<AccessibilityTable>(*coreObject).isExposableThroughAccessibility()) {
466 auto& table = downcast<AccessibilityTable>(*coreObject);
467 int rowCount = table.ariaRowCount();
469 attributeSet = addToAtkAttributeSet(attributeSet, "rowcount", String::number(rowCount).utf8().data());
471 int columnCount = table.ariaColumnCount();
473 attributeSet = addToAtkAttributeSet(attributeSet, "colcount", String::number(columnCount).utf8().data());
474 } else if (is<AccessibilityTableRow>(*coreObject)) {
475 auto& row = downcast<AccessibilityTableRow>(*coreObject);
476 int rowIndex = row.ariaRowIndex();
478 attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data());
479 } else if (is<AccessibilityTableCell>(*coreObject)) {
480 auto& cell = downcast<AccessibilityTableCell>(*coreObject);
481 int rowIndex = cell.ariaRowIndex();
483 attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data());
485 int columnIndex = cell.ariaColumnIndex();
486 if (columnIndex != -1)
487 attributeSet = addToAtkAttributeSet(attributeSet, "colindex", String::number(columnIndex).utf8().data());
489 int rowSpan = cell.ariaRowSpan();
491 attributeSet = addToAtkAttributeSet(attributeSet, "rowspan", String::number(rowSpan).utf8().data());
493 int columnSpan = cell.ariaColumnSpan();
494 if (columnSpan != -1)
495 attributeSet = addToAtkAttributeSet(attributeSet, "colspan", String::number(columnSpan).utf8().data());
498 String placeholder = coreObject->placeholderValue();
499 if (!placeholder.isEmpty())
500 attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data());
502 if (coreObject->supportsARIAAutoComplete())
503 attributeSet = addToAtkAttributeSet(attributeSet, "autocomplete", coreObject->ariaAutoCompleteValue().utf8().data());
505 if (coreObject->supportsARIAHasPopup())
506 attributeSet = addToAtkAttributeSet(attributeSet, "haspopup", coreObject->ariaPopupValue().utf8().data());
508 if (coreObject->supportsARIACurrent())
509 attributeSet = addToAtkAttributeSet(attributeSet, "current", coreObject->ariaCurrentValue().utf8().data());
511 // The Core AAM states that an explicitly-set value should be exposed, including "none".
512 if (coreObject->hasAttribute(HTMLNames::aria_sortAttr)) {
513 switch (coreObject->sortDirection()) {
514 case SortDirectionInvalid:
516 case SortDirectionAscending:
517 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "ascending");
519 case SortDirectionDescending:
520 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "descending");
522 case SortDirectionOther:
523 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "other");
526 attributeSet = addToAtkAttributeSet(attributeSet, "sort", "none");
530 if (coreObject->supportsARIAPosInSet())
531 attributeSet = addToAtkAttributeSet(attributeSet, "posinset", String::number(coreObject->ariaPosInSet()).utf8().data());
533 if (coreObject->supportsARIASetSize())
534 attributeSet = addToAtkAttributeSet(attributeSet, "setsize", String::number(coreObject->ariaSetSize()).utf8().data());
536 String isReadOnly = coreObject->ariaReadOnlyValue();
537 if (!isReadOnly.isEmpty())
538 attributeSet = addToAtkAttributeSet(attributeSet, "readonly", isReadOnly.utf8().data());
540 String valueDescription = coreObject->valueDescription();
541 if (!valueDescription.isEmpty())
542 attributeSet = addToAtkAttributeSet(attributeSet, "valuetext", valueDescription.utf8().data());
544 // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules:
545 // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so."
546 // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string").
547 // We cannot use the computedRoleString for this purpose because it is not limited to elements
548 // with ARIA roles, and it might not contain the actual ARIA role value (e.g. DPub ARIA).
549 String roleString = coreObject->getAttribute(HTMLNames::roleAttr);
550 if (!roleString.isEmpty())
551 attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", roleString.utf8().data());
553 String computedRoleString = coreObject->computedRoleString();
554 if (!computedRoleString.isEmpty()) {
555 attributeSet = addToAtkAttributeSet(attributeSet, "computed-role", computedRoleString.utf8().data());
557 // The HTML AAM maps several elements to ARIA landmark roles. In order for the type of landmark
558 // to be obtainable in the same fashion as an ARIA landmark, fall back on the computedRoleString.
559 if (coreObject->ariaRoleAttribute() == UnknownRole && coreObject->isLandmark())
560 attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", computedRoleString.utf8().data());
563 String roleDescription = coreObject->roleDescription();
564 if (!roleDescription.isEmpty())
565 attributeSet = addToAtkAttributeSet(attributeSet, "roledescription", roleDescription.utf8().data());
567 // We need to expose the live region attributes even if the live region is currently disabled/off.
568 if (auto liveContainer = coreObject->ariaLiveRegionAncestor(false)) {
569 String liveStatus = liveContainer->ariaLiveRegionStatus();
570 String relevant = liveContainer->ariaLiveRegionRelevant();
571 bool isAtomic = liveContainer->ariaLiveRegionAtomic();
572 String liveRole = roleString.isEmpty() ? computedRoleString : roleString;
574 // According to the Core AAM, we need to expose the above properties with "container-" prefixed
575 // object attributes regardless of whether the container is this object, or an ancestor of it.
576 attributeSet = addToAtkAttributeSet(attributeSet, "container-live", liveStatus.utf8().data());
577 attributeSet = addToAtkAttributeSet(attributeSet, "container-relevant", relevant.utf8().data());
579 attributeSet = addToAtkAttributeSet(attributeSet, "container-atomic", "true");
580 if (!liveRole.isEmpty())
581 attributeSet = addToAtkAttributeSet(attributeSet, "container-live-role", liveRole.utf8().data());
583 // According to the Core AAM, if this object is the live region (rather than its descendant),
584 // we must expose the above properties on the object without a "container-" prefix.
585 if (liveContainer == coreObject) {
586 attributeSet = addToAtkAttributeSet(attributeSet, "live", liveStatus.utf8().data());
587 attributeSet = addToAtkAttributeSet(attributeSet, "relevant", relevant.utf8().data());
589 attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true");
590 } else if (!isAtomic && coreObject->ariaLiveRegionAtomic())
591 attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true");
594 // The Core AAM states the author-provided value should be exposed as-is.
595 String dropEffect = coreObject->getAttribute(HTMLNames::aria_dropeffectAttr);
596 if (!dropEffect.isEmpty())
597 attributeSet = addToAtkAttributeSet(attributeSet, "dropeffect", dropEffect.utf8().data());
599 if (coreObject->isARIAGrabbed())
600 attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "true");
601 else if (coreObject->supportsARIADragging())
602 attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "false");
607 static AtkRole atkRole(AccessibilityObject* coreObject)
609 AccessibilityRole role = coreObject->roleValue();
611 case ApplicationAlertRole:
612 return ATK_ROLE_ALERT;
613 case ApplicationAlertDialogRole:
614 case ApplicationDialogRole:
615 return ATK_ROLE_DIALOG;
616 case ApplicationStatusRole:
617 return ATK_ROLE_STATUSBAR;
619 return ATK_ROLE_UNKNOWN;
621 #if ATK_CHECK_VERSION(2, 11, 3)
622 return ATK_ROLE_AUDIO;
625 #if ATK_CHECK_VERSION(2, 11, 3)
626 return ATK_ROLE_VIDEO;
628 return ATK_ROLE_EMBEDDED;
630 return ATK_ROLE_PUSH_BUTTON;
632 case ToggleButtonRole:
633 return ATK_ROLE_TOGGLE_BUTTON;
634 case RadioButtonRole:
635 return ATK_ROLE_RADIO_BUTTON;
637 return ATK_ROLE_CHECK_BOX;
639 return ATK_ROLE_SLIDER;
642 return ATK_ROLE_PAGE_TAB_LIST;
645 case SearchFieldRole:
646 return ATK_ROLE_ENTRY;
648 #if ATK_CHECK_VERSION(2, 15, 2)
649 return ATK_ROLE_STATIC;
651 return ATK_ROLE_TEXT;
655 return ATK_ROLE_TREE;
657 return ATK_ROLE_TREE_ITEM;
659 return ATK_ROLE_MENU_BAR;
660 case MenuListPopupRole:
662 return ATK_ROLE_MENU;
663 case MenuListOptionRole:
666 return ATK_ROLE_MENU_ITEM;
667 case MenuItemCheckboxRole:
668 return ATK_ROLE_CHECK_MENU_ITEM;
669 case MenuItemRadioRole:
670 return ATK_ROLE_RADIO_MENU_ITEM;
672 // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
673 return ATK_ROLE_UNKNOWN; // Matches Mozilla
675 return ATK_ROLE_TABLE_ROW;
677 return ATK_ROLE_TOOL_BAR;
678 case BusyIndicatorRole:
679 return ATK_ROLE_PROGRESS_BAR; // Is this right?
680 case ProgressIndicatorRole:
681 return coreObject->isMeter() ? ATK_ROLE_LEVEL_BAR : ATK_ROLE_PROGRESS_BAR;
683 return ATK_ROLE_WINDOW;
684 case PopUpButtonRole:
685 return coreObject->ariaHasPopup() ? ATK_ROLE_PUSH_BUTTON : ATK_ROLE_COMBO_BOX;
687 return ATK_ROLE_COMBO_BOX;
689 return ATK_ROLE_SPLIT_PANE;
691 return ATK_ROLE_SEPARATOR;
694 // ATK_ROLE_COLOR_CHOOSER is defined as a dialog (i.e. it's what appears when you push the button).
695 return ATK_ROLE_PUSH_BUTTON;
698 return ATK_ROLE_LIST;
700 return ATK_ROLE_SCROLL_BAR;
703 return ATK_ROLE_SCROLL_PANE;
706 return ATK_ROLE_TABLE;
708 return ATK_ROLE_TREE_TABLE;
709 case ApplicationRole:
710 return ATK_ROLE_APPLICATION;
711 case ApplicationGroupRole:
717 return ATK_ROLE_PANEL;
719 return ATK_ROLE_ROW_HEADER;
720 case ColumnHeaderRole:
721 return ATK_ROLE_COLUMN_HEADER;
723 return ATK_ROLE_CAPTION;
726 return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_TABLE_CELL;
728 case WebCoreLinkRole:
729 case ImageMapLinkRole:
730 return ATK_ROLE_LINK;
732 return ATK_ROLE_IMAGE_MAP;
734 return ATK_ROLE_IMAGE;
736 return ATK_ROLE_TEXT;
737 case DocumentArticleRole:
738 #if ATK_CHECK_VERSION(2, 11, 3)
739 return ATK_ROLE_ARTICLE;
742 return ATK_ROLE_DOCUMENT_FRAME;
743 case DocumentNoteRole:
744 return ATK_ROLE_COMMENT;
746 return ATK_ROLE_HEADING;
748 // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-listbox
749 return coreObject->isDescendantOfRole(ComboBoxRole) ? ATK_ROLE_MENU : ATK_ROLE_LIST_BOX;
751 return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_LIST_ITEM;
752 case ListBoxOptionRole:
753 return coreObject->isDescendantOfRole(ComboBoxRole) ? ATK_ROLE_MENU_ITEM : ATK_ROLE_LIST_ITEM;
755 return ATK_ROLE_PARAGRAPH;
758 return ATK_ROLE_LABEL;
760 #if ATK_CHECK_VERSION(2, 11, 3)
761 return ATK_ROLE_BLOCK_QUOTE;
764 #if ATK_CHECK_VERSION(2, 25, 2)
765 return ATK_ROLE_FOOTNOTE;
767 case ApplicationTextGroupRole:
772 return ATK_ROLE_SECTION;
774 return ATK_ROLE_FOOTER;
776 return ATK_ROLE_FORM;
778 return ATK_ROLE_CANVAS;
779 case HorizontalRuleRole:
780 return ATK_ROLE_SEPARATOR;
782 return ATK_ROLE_SPIN_BUTTON;
784 return ATK_ROLE_PAGE_TAB;
785 case UserInterfaceTooltipRole:
786 return ATK_ROLE_TOOL_TIP;
788 return ATK_ROLE_DOCUMENT_WEB;
789 case WebApplicationRole:
790 return ATK_ROLE_EMBEDDED;
791 #if ATK_CHECK_VERSION(2, 11, 3)
792 case ApplicationLogRole:
794 case ApplicationMarqueeRole:
795 return ATK_ROLE_MARQUEE;
796 case ApplicationTimerRole:
797 return ATK_ROLE_TIMER;
799 return ATK_ROLE_DEFINITION;
800 case DocumentMathRole:
801 return ATK_ROLE_MATH;
802 case MathElementRole:
803 if (coreObject->isMathRow())
804 return ATK_ROLE_PANEL;
805 if (coreObject->isMathTable())
806 return ATK_ROLE_TABLE;
807 if (coreObject->isMathTableRow())
808 return ATK_ROLE_TABLE_ROW;
809 if (coreObject->isMathTableCell())
810 return ATK_ROLE_TABLE_CELL;
811 if (coreObject->isMathSubscriptSuperscript() || coreObject->isMathMultiscript())
812 return ATK_ROLE_SECTION;
813 #if ATK_CHECK_VERSION(2, 15, 4)
814 if (coreObject->isMathFraction())
815 return ATK_ROLE_MATH_FRACTION;
816 if (coreObject->isMathSquareRoot() || coreObject->isMathRoot())
817 return ATK_ROLE_MATH_ROOT;
818 if (coreObject->isMathScriptObject(Subscript)
819 || coreObject->isMathMultiscriptObject(PreSubscript) || coreObject->isMathMultiscriptObject(PostSubscript))
820 return ATK_ROLE_SUBSCRIPT;
821 if (coreObject->isMathScriptObject(Superscript)
822 || coreObject->isMathMultiscriptObject(PreSuperscript) || coreObject->isMathMultiscriptObject(PostSuperscript))
823 return ATK_ROLE_SUPERSCRIPT;
825 #if ATK_CHECK_VERSION(2, 15, 2)
826 if (coreObject->isMathToken())
827 return ATK_ROLE_STATIC;
829 return ATK_ROLE_UNKNOWN;
830 case LandmarkBannerRole:
831 case LandmarkComplementaryRole:
832 case LandmarkContentInfoRole:
833 case LandmarkDocRegionRole:
834 case LandmarkMainRole:
835 case LandmarkNavigationRole:
836 case LandmarkRegionRole:
837 case LandmarkSearchRole:
838 return ATK_ROLE_LANDMARK;
840 #if ATK_CHECK_VERSION(2, 11, 4)
841 case DescriptionListRole:
842 return ATK_ROLE_DESCRIPTION_LIST;
844 case DescriptionListTermRole:
845 return ATK_ROLE_DESCRIPTION_TERM;
846 case DescriptionListDetailRole:
847 return ATK_ROLE_DESCRIPTION_VALUE;
850 #if ATK_CHECK_VERSION(2, 15, 4)
851 if (coreObject->isSubscriptStyleGroup())
852 return ATK_ROLE_SUBSCRIPT;
853 if (coreObject->isSuperscriptStyleGroup())
854 return ATK_ROLE_SUPERSCRIPT;
856 #if ATK_CHECK_VERSION(2, 15, 2)
857 return ATK_ROLE_STATIC;
858 case SVGTextPathRole:
861 return ATK_ROLE_STATIC;
864 return ATK_ROLE_UNKNOWN;
868 static AtkRole webkitAccessibleGetRole(AtkObject* object)
870 // ATK_ROLE_UNKNOWN should only be applied in cases where there is a valid
871 // WebCore accessible object for which the platform role mapping is unknown.
872 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), ATK_ROLE_INVALID);
873 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), ATK_ROLE_INVALID);
875 AccessibilityObject* coreObject = core(object);
878 return ATK_ROLE_INVALID;
880 // Note: Why doesn't WebCore have a password field for this
881 if (coreObject->isPasswordField())
882 return ATK_ROLE_PASSWORD_TEXT;
884 return atkRole(coreObject);
887 static bool isTextWithCaret(AccessibilityObject* coreObject)
889 if (!coreObject || !coreObject->isAccessibilityRenderObject())
892 Document* document = coreObject->document();
896 Frame* frame = document->frame();
900 if (!frame->settings().caretBrowsingEnabled())
903 // Check text objects and paragraphs only.
904 AtkObject* axObject = coreObject->wrapper();
905 AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID;
906 if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
909 // Finally, check whether the caret is set in the current object.
910 VisibleSelection selection = coreObject->selection();
911 if (!selection.isCaret())
914 return selectionBelongsToObject(coreObject, selection);
917 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
919 AccessibilityObject* parent = coreObject->parentObject();
920 bool isListBoxOption = parent && parent->isListBox();
922 // Please keep the state list in alphabetical order
923 if ((isListBoxOption && coreObject->isSelectedOptionActive())
924 || coreObject->ariaCurrentState() != ARIACurrentFalse)
925 atk_state_set_add_state(stateSet, ATK_STATE_ACTIVE);
927 if (coreObject->isBusy())
928 atk_state_set_add_state(stateSet, ATK_STATE_BUSY);
930 #if ATK_CHECK_VERSION(2,11,2)
931 if (coreObject->supportsChecked() && coreObject->canSetValueAttribute())
932 atk_state_set_add_state(stateSet, ATK_STATE_CHECKABLE);
935 if (coreObject->isChecked())
936 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
938 if ((coreObject->isTextControl() || coreObject->isNonNativeTextControl()) && coreObject->canSetValueAttribute())
939 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
941 // FIXME: Put both ENABLED and SENSITIVE together here for now
942 if (coreObject->isEnabled()) {
943 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
944 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
947 if (coreObject->canSetExpandedAttribute())
948 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
950 if (coreObject->isExpanded())
951 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
953 if (coreObject->canSetFocusAttribute())
954 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
956 if (coreObject->isFocused() || isTextWithCaret(coreObject))
957 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
959 if (coreObject->orientation() == AccessibilityOrientationHorizontal)
960 atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL);
961 else if (coreObject->orientation() == AccessibilityOrientationVertical)
962 atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL);
964 if (coreObject->ariaHasPopup())
965 atk_state_set_add_state(stateSet, ATK_STATE_HAS_POPUP);
967 if (coreObject->isIndeterminate())
968 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
969 else if (coreObject->isCheckboxOrRadio() || coreObject->isMenuItem() || coreObject->isToggleButton()) {
970 if (coreObject->checkboxOrRadioValue() == ButtonStateMixed)
971 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
974 if (coreObject->isAriaModalNode())
975 atk_state_set_add_state(stateSet, ATK_STATE_MODAL);
977 if (coreObject->invalidStatus() != "false")
978 atk_state_set_add_state(stateSet, ATK_STATE_INVALID_ENTRY);
980 if (coreObject->isMultiSelectable())
981 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
983 // TODO: ATK_STATE_OPAQUE
985 if (coreObject->isPressed())
986 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
988 #if ATK_CHECK_VERSION(2,15,3)
989 if (!coreObject->canSetValueAttribute() && (coreObject->supportsARIAReadOnly()))
990 atk_state_set_add_state(stateSet, ATK_STATE_READ_ONLY);
993 if (coreObject->isRequired())
994 atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED);
996 // TODO: ATK_STATE_SELECTABLE_TEXT
998 if (coreObject->canSetSelectedAttribute()) {
999 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
1000 // Items in focusable lists have both STATE_SELECT{ABLE,ED}
1001 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on
1003 if (isListBoxOption)
1004 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
1007 if (coreObject->isSelected()) {
1008 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
1009 // Items in focusable lists have both STATE_SELECT{ABLE,ED}
1010 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
1012 if (isListBoxOption)
1013 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
1016 // FIXME: Group both SHOWING and VISIBLE here for now
1017 // Not sure how to handle this in WebKit, see bug
1018 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
1019 // issues with SHOWING vs VISIBLE.
1020 if (!coreObject->isOffScreen()) {
1021 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
1022 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
1025 // Mutually exclusive, so we group these two
1026 if (coreObject->roleValue() == TextAreaRole || coreObject->ariaIsMultiline())
1027 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
1028 else if (coreObject->roleValue() == TextFieldRole || coreObject->roleValue() == SearchFieldRole)
1029 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
1031 // TODO: ATK_STATE_SENSITIVE
1033 if (coreObject->supportsARIAAutoComplete() && coreObject->ariaAutoCompleteValue() != "none")
1034 atk_state_set_add_state(stateSet, ATK_STATE_SUPPORTS_AUTOCOMPLETION);
1036 if (coreObject->isVisited())
1037 atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
1040 static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object)
1042 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
1044 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_state_set(object);
1045 AccessibilityObject* coreObject = core(object);
1047 // Make sure the layout is updated to really know whether the object
1048 // is defunct or not, so we can return the proper state.
1049 coreObject->updateBackingStore();
1051 if (coreObject == fallbackObject()) {
1052 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
1056 // Text objects must be focusable.
1057 AtkRole role = atk_object_get_role(object);
1058 if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
1059 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
1061 setAtkStateSetFromCoreObject(coreObject, stateSet);
1065 static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object)
1067 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
1068 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
1070 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_relation_set(object);
1071 AccessibilityObject* coreObject = core(object);
1073 setAtkRelationSetFromCoreObject(coreObject, relationSet);
1078 static void webkitAccessibleInit(AtkObject* object, gpointer data)
1080 if (ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize)
1081 ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize(object, data);
1083 WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object);
1084 accessible->m_object = reinterpret_cast<AccessibilityObject*>(data);
1085 accessible->priv = WEBKIT_ACCESSIBLE_GET_PRIVATE(accessible);
1088 static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object)
1090 g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
1091 returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);
1093 AccessibilityObject* coreObject = core(object);
1097 if (ATK_IS_DOCUMENT(object)) {
1098 // TODO: Should we fall back on lang xml:lang when the following comes up empty?
1099 String language = coreObject->language();
1100 if (!language.isEmpty())
1101 return cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, language);
1103 } else if (ATK_IS_TEXT(object)) {
1104 const gchar* locale = nullptr;
1106 AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object));
1107 for (AtkAttributeSet* attributes = textAttributes; attributes; attributes = attributes->next) {
1108 AtkAttribute* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
1109 if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) {
1110 locale = cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, String::fromUTF8(atkAttribute->value));
1114 atk_attribute_set_free(textAttributes);
1122 static void webkitAccessibleFinalize(GObject* object)
1124 G_OBJECT_CLASS(webkitAccessibleParentClass)->finalize(object);
1127 static void webkitAccessibleClassInit(AtkObjectClass* klass)
1129 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
1131 webkitAccessibleParentClass = g_type_class_peek_parent(klass);
1133 gobjectClass->finalize = webkitAccessibleFinalize;
1135 klass->initialize = webkitAccessibleInit;
1136 klass->get_name = webkitAccessibleGetName;
1137 klass->get_description = webkitAccessibleGetDescription;
1138 klass->get_parent = webkitAccessibleGetParent;
1139 klass->get_n_children = webkitAccessibleGetNChildren;
1140 klass->ref_child = webkitAccessibleRefChild;
1141 klass->get_role = webkitAccessibleGetRole;
1142 klass->ref_state_set = webkitAccessibleRefStateSet;
1143 klass->get_index_in_parent = webkitAccessibleGetIndexInParent;
1144 klass->get_attributes = webkitAccessibleGetAttributes;
1145 klass->ref_relation_set = webkitAccessibleRefRelationSet;
1146 klass->get_object_locale = webkitAccessibleGetObjectLocale;
1148 g_type_class_add_private(klass, sizeof(WebKitAccessiblePrivate));
1152 webkitAccessibleGetType(void)
1154 static volatile gsize typeVolatile = 0;
1156 if (g_once_init_enter(&typeVolatile)) {
1157 static const GTypeInfo tinfo = {
1158 sizeof(WebKitAccessibleClass),
1160 (GBaseFinalizeFunc) 0,
1161 (GClassInitFunc) webkitAccessibleClassInit,
1162 (GClassFinalizeFunc) 0,
1164 sizeof(WebKitAccessible), /* instance size */
1165 0, /* nb preallocs */
1166 (GInstanceInitFunc) 0,
1170 GType type = g_type_register_static(ATK_TYPE_OBJECT, "WebKitAccessible", &tinfo, GTypeFlags(0));
1171 g_once_init_leave(&typeVolatile, type);
1174 return typeVolatile;
1177 static const GInterfaceInfo AtkInterfacesInitFunctions[] = {
1178 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleActionInterfaceInit), 0, 0},
1179 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleSelectionInterfaceInit), 0, 0},
1180 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleEditableTextInterfaceInit), 0, 0},
1181 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTextInterfaceInit), 0, 0},
1182 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleComponentInterfaceInit), 0, 0},
1183 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleImageInterfaceInit), 0, 0},
1184 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableInterfaceInit), 0, 0},
1185 #if ATK_CHECK_VERSION(2,11,90)
1186 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableCellInterfaceInit), 0, 0},
1188 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHypertextInterfaceInit), 0, 0},
1189 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHyperlinkImplInterfaceInit), 0, 0},
1190 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleDocumentInterfaceInit), 0, 0},
1191 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleValueInterfaceInit), 0, 0}
1202 #if ATK_CHECK_VERSION(2,11,90)
1211 static GType GetAtkInterfaceTypeFromWAIType(WAIType type)
1215 return ATK_TYPE_ACTION;
1217 return ATK_TYPE_SELECTION;
1218 case WAIEditableText:
1219 return ATK_TYPE_EDITABLE_TEXT;
1221 return ATK_TYPE_TEXT;
1223 return ATK_TYPE_COMPONENT;
1225 return ATK_TYPE_IMAGE;
1227 return ATK_TYPE_TABLE;
1228 #if ATK_CHECK_VERSION(2,11,90)
1230 return ATK_TYPE_TABLE_CELL;
1233 return ATK_TYPE_HYPERTEXT;
1235 return ATK_TYPE_HYPERLINK_IMPL;
1237 return ATK_TYPE_DOCUMENT;
1239 return ATK_TYPE_VALUE;
1242 return G_TYPE_INVALID;
1245 static bool roleIsTextType(AccessibilityRole role)
1247 return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole
1248 || role == LinkRole || role == WebCoreLinkRole || role == ListItemRole || role == PreRole
1249 || role == GridCellRole || role == TextGroupRole || role == ApplicationTextGroupRole;
1252 static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
1254 guint16 interfaceMask = 0;
1256 // Component interface is always supported
1257 interfaceMask |= 1 << WAIComponent;
1259 AccessibilityRole role = coreObject->roleValue();
1262 // As the implementation of the AtkAction interface is a very
1263 // basic one (just relays in executing the default action for each
1264 // object, and only supports having one action per object), it is
1265 // better just to implement this interface for every instance of
1266 // the WebKitAccessible class and let WebCore decide what to do.
1267 interfaceMask |= 1 << WAIAction;
1270 if (coreObject->canHaveSelectedChildren() || coreObject->isMenuList())
1271 interfaceMask |= 1 << WAISelection;
1273 // Get renderer if available.
1274 RenderObject* renderer = nullptr;
1275 if (coreObject->isAccessibilityRenderObject())
1276 renderer = coreObject->renderer();
1278 // Hyperlink (links and embedded objects).
1279 if (coreObject->isLink() || (renderer && renderer->isReplaced()))
1280 interfaceMask |= 1 << WAIHyperlink;
1282 // Text, Editable Text & Hypertext
1283 if (role == StaticTextRole || coreObject->isMenuListOption())
1284 interfaceMask |= 1 << WAIText;
1285 else if (coreObject->isTextControl() || coreObject->isNonNativeTextControl()) {
1286 interfaceMask |= 1 << WAIText;
1287 if (coreObject->canSetValueAttribute())
1288 interfaceMask |= 1 << WAIEditableText;
1289 } else if (!coreObject->isWebArea()) {
1290 if (role != TableRole) {
1291 interfaceMask |= 1 << WAIHypertext;
1292 if ((renderer && renderer->childrenInline()) || roleIsTextType(role) || coreObject->isMathToken())
1293 interfaceMask |= 1 << WAIText;
1296 // Add the TEXT interface for list items whose
1297 // first accessible child has a text renderer
1298 if (role == ListItemRole) {
1299 const AccessibilityObject::AccessibilityChildrenVector& children = coreObject->children();
1300 if (children.size()) {
1301 AccessibilityObject* axRenderChild = children.at(0).get();
1302 interfaceMask |= getInterfaceMaskFromObject(axRenderChild);
1308 if (coreObject->isImage())
1309 interfaceMask |= 1 << WAIImage;
1312 if (coreObject->isTable())
1313 interfaceMask |= 1 << WAITable;
1315 #if ATK_CHECK_VERSION(2,11,90)
1316 if (role == CellRole || role == GridCellRole || role == ColumnHeaderRole || role == RowHeaderRole)
1317 interfaceMask |= 1 << WAITableCell;
1321 if (role == WebAreaRole)
1322 interfaceMask |= 1 << WAIDocument;
1325 if (coreObject->supportsRangeValue())
1326 interfaceMask |= 1 << WAIValue;
1328 #if ENABLE(INPUT_TYPE_COLOR)
1330 if (role == ColorWellRole)
1331 interfaceMask |= 1 << WAIText;
1334 return interfaceMask;
1337 static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask)
1339 #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */
1340 static char name[WAI_TYPE_NAME_LEN + 1];
1342 g_sprintf(name, "WAIType%x", interfaceMask);
1343 name[WAI_TYPE_NAME_LEN] = '\0';
1348 static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject)
1350 static const GTypeInfo typeInfo = {
1351 sizeof(WebKitAccessibleClass),
1353 (GBaseFinalizeFunc) 0,
1355 (GClassFinalizeFunc) 0,
1357 sizeof(WebKitAccessible), /* instance size */
1358 0, /* nb preallocs */
1359 (GInstanceInitFunc) 0,
1363 guint16 interfaceMask = getInterfaceMaskFromObject(coreObject);
1364 const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask);
1365 GType type = g_type_from_name(atkTypeName);
1369 type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, GTypeFlags(0));
1370 for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) {
1371 if (interfaceMask & (1 << i))
1372 g_type_add_interface_static(type,
1373 GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
1374 &AtkInterfacesInitFunctions[i]);
1380 WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject)
1382 GType type = getAccessibilityTypeFromObject(coreObject);
1383 AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0));
1385 atk_object_initialize(object, coreObject);
1387 return WEBKIT_ACCESSIBLE(object);
1390 AccessibilityObject* webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible)
1392 return accessible->m_object;
1395 void webkitAccessibleDetach(WebKitAccessible* accessible)
1397 ASSERT(accessible->m_object);
1399 if (accessible->m_object->roleValue() == WebAreaRole)
1400 atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_DEFUNCT, true);
1402 // We replace the WebCore AccessibilityObject with a fallback object that
1403 // provides default implementations to avoid repetitive null-checking after
1405 accessible->m_object = fallbackObject();
1408 bool webkitAccessibleIsDetached(WebKitAccessible* accessible)
1410 ASSERT(accessible->m_object);
1411 return accessible->m_object == fallbackObject();
1414 AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset)
1416 // Indication that something bogus has transpired.
1419 Document* document = referenceObject->document();
1423 Node* focusedNode = referenceObject->selection().end().containerNode();
1427 RenderObject* focusedRenderer = focusedNode->renderer();
1428 if (!focusedRenderer)
1431 AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
1435 // Look for the actual (not ignoring accessibility) selected object.
1436 AccessibilityObject* firstUnignoredParent = focusedObject;
1437 if (firstUnignoredParent->accessibilityIsIgnored())
1438 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
1439 if (!firstUnignoredParent)
1442 // Don't ignore links if the offset is being requested for a link
1443 // or if the link is a block.
1444 if (!referenceObject->isLink() && firstUnignoredParent->isLink()
1445 && !(firstUnignoredParent->renderer() && !firstUnignoredParent->renderer()->isInline()))
1446 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored();
1447 if (!firstUnignoredParent)
1450 // The reference object must either coincide with the focused
1451 // object being considered, or be a descendant of it.
1452 if (referenceObject->isDescendantOfObject(firstUnignoredParent))
1453 referenceObject = firstUnignoredParent;
1455 Node* startNode = nullptr;
1456 if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) {
1457 // We need to use the first child's node of the reference
1458 // object as the start point to calculate the caret offset
1459 // because we want it to be relative to the object of
1460 // reference, not just to the focused object (which could have
1461 // previous siblings which should be taken into account too).
1462 AccessibilityObject* axFirstChild = referenceObject->firstChild();
1464 startNode = axFirstChild->node();
1466 // Getting the Position of a PseudoElement now triggers an assertion.
1467 // This can occur when clicking on empty space in a render block.
1468 if (!startNode || startNode->isPseudoElement())
1469 startNode = firstUnignoredParent->node();
1471 // Check if the node for the first parent object not ignoring
1472 // accessibility is null again before using it. This might happen
1473 // with certain kind of accessibility objects, such as the root
1474 // one (the scroller containing the webArea object).
1478 VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM);
1479 VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd();
1481 if (startPosition == endPosition)
1483 else if (!isStartOfLine(endPosition)) {
1484 RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
1485 offset = TextIterator::rangeLength(range.get(), true) + 1;
1487 RefPtr<Range> range = makeRange(startPosition, endPosition);
1488 offset = TextIterator::rangeLength(range.get(), true);
1491 return firstUnignoredParent;
1494 const char* cacheAndReturnAtkProperty(AtkObject* object, AtkCachedProperty property, String value)
1496 WebKitAccessiblePrivate* priv = WEBKIT_ACCESSIBLE(object)->priv;
1497 CString* propertyPtr = nullptr;
1500 case AtkCachedAccessibleName:
1501 propertyPtr = &priv->accessibleName;
1504 case AtkCachedAccessibleDescription:
1505 propertyPtr = &priv->accessibleDescription;
1508 case AtkCachedActionName:
1509 propertyPtr = &priv->actionName;
1512 case AtkCachedActionKeyBinding:
1513 propertyPtr = &priv->actionKeyBinding;
1516 case AtkCachedDocumentLocale:
1517 propertyPtr = &priv->documentLocale;
1520 case AtkCachedDocumentType:
1521 propertyPtr = &priv->documentType;
1524 case AtkCachedDocumentEncoding:
1525 propertyPtr = &priv->documentEncoding;
1528 case AtkCachedDocumentURI:
1529 propertyPtr = &priv->documentURI;
1532 case AtkCachedImageDescription:
1533 propertyPtr = &priv->imageDescription;
1537 ASSERT_NOT_REACHED();
1540 // Don't invalidate old memory if not stricly needed, since other
1541 // callers might be still holding on to it.
1542 if (*propertyPtr != value.utf8())
1543 *propertyPtr = value.utf8();
1545 return (*propertyPtr).data();
1548 #endif // HAVE(ACCESSIBILITY)