| /* |
| * Copyright (C) 2008 Nuanti Ltd. |
| * Copyright (C) 2009 Jan Alonzo |
| * Copyright (C) 2009, 2010, 2011, 2012, 2019 Igalia S.L. |
| * Copyright (C) 2013 Samsung Electronics |
| * |
| * Portions from Mozilla a11y, copyright as follows: |
| * |
| * The Original Code is mozilla.org code. |
| * |
| * The Initial Developer of the Original Code is |
| * Sun Microsystems, Inc. |
| * Portions created by the Initial Developer are Copyright (C) 2002 |
| * the Initial Developer. All Rights Reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "WebKitAccessible.h" |
| |
| #if ENABLE(ACCESSIBILITY) |
| |
| #include "AXObjectCache.h" |
| #include "AccessibilityList.h" |
| #include "AccessibilityListBoxOption.h" |
| #include "AccessibilityTable.h" |
| #include "AccessibilityTableCell.h" |
| #include "AccessibilityTableRow.h" |
| #include "Document.h" |
| #include "Editing.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "HTMLNames.h" |
| #include "HTMLTableElement.h" |
| #include "HostWindow.h" |
| #include "RenderAncestorIterator.h" |
| #include "RenderBlock.h" |
| #include "RenderObject.h" |
| #include "SVGElement.h" |
| #include "Settings.h" |
| #include "TextIterator.h" |
| #include "VisibleUnits.h" |
| #include "WebKitAccessibleHyperlink.h" |
| #include "WebKitAccessibleInterfaceAction.h" |
| #include "WebKitAccessibleInterfaceComponent.h" |
| #include "WebKitAccessibleInterfaceDocument.h" |
| #include "WebKitAccessibleInterfaceEditableText.h" |
| #include "WebKitAccessibleInterfaceHyperlinkImpl.h" |
| #include "WebKitAccessibleInterfaceHypertext.h" |
| #include "WebKitAccessibleInterfaceImage.h" |
| #include "WebKitAccessibleInterfaceSelection.h" |
| #include "WebKitAccessibleInterfaceTable.h" |
| #include "WebKitAccessibleInterfaceTableCell.h" |
| #include "WebKitAccessibleInterfaceText.h" |
| #include "WebKitAccessibleInterfaceValue.h" |
| #include "WebKitAccessibleUtil.h" |
| #include <glib/gprintf.h> |
| #include <wtf/glib/WTFGType.h> |
| #include <wtf/text/CString.h> |
| |
| using namespace WebCore; |
| |
| struct _WebKitAccessiblePrivate { |
| AccessibilityObject* object; |
| |
| // Cached data for AtkObject. |
| CString accessibleName; |
| CString accessibleDescription; |
| |
| // Cached data for AtkAction. |
| CString actionName; |
| CString actionKeyBinding; |
| |
| // Cached data for AtkDocument. |
| CString documentLocale; |
| CString documentType; |
| CString documentEncoding; |
| CString documentURI; |
| |
| // Cached data for AtkImage. |
| CString imageDescription; |
| }; |
| |
| WEBKIT_DEFINE_TYPE(WebKitAccessible, webkit_accessible, ATK_TYPE_OBJECT) |
| |
| static AccessibilityObject* fallbackObject() |
| { |
| static AccessibilityObject* object = &AccessibilityListBoxOption::create().leakRef(); |
| return object; |
| } |
| |
| static const gchar* webkitAccessibleGetName(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| Vector<AccessibilityText> textOrder; |
| accessible->priv->object->accessibilityText(textOrder); |
| |
| for (const auto& text : textOrder) { |
| // FIXME: This check is here because AccessibilityNodeObject::titleElementText() |
| // appends an empty String for the LabelByElementText source when there is a |
| // titleUIElement(). Removing this check makes some fieldsets lose their name. |
| if (text.text.isEmpty()) |
| continue; |
| |
| // WebCore Accessibility should provide us with the text alternative computation |
| // in the order defined by that spec. So take the first thing that our platform |
| // does not expose via the AtkObject description. |
| if (text.textSource != AccessibilityTextSource::Help && text.textSource != AccessibilityTextSource::Summary) |
| return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleName, text.text.utf8()); |
| } |
| |
| return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleName, ""); |
| } |
| |
| static const gchar* webkitAccessibleGetDescription(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| Vector<AccessibilityText> textOrder; |
| accessible->priv->object->accessibilityText(textOrder); |
| |
| bool nameTextAvailable = false; |
| for (const auto& text : textOrder) { |
| // WebCore Accessibility should provide us with the text alternative computation |
| // in the order defined by that spec. So take the first thing that our platform |
| // does not expose via the AtkObject name. |
| if (text.textSource == AccessibilityTextSource::Help || text.textSource == AccessibilityTextSource::Summary) |
| return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleDescription, text.text.utf8()); |
| |
| // If there is no other text alternative, the title tag contents will have been |
| // used for the AtkObject name. We don't want to duplicate it here. |
| if (text.textSource == AccessibilityTextSource::TitleTag && nameTextAvailable) |
| return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleDescription, text.text.utf8()); |
| |
| nameTextAvailable = true; |
| } |
| |
| return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedAccessibleDescription, ""); |
| } |
| |
| static void removeAtkRelationByType(AtkRelationSet* relationSet, AtkRelationType relationType) |
| { |
| int count = atk_relation_set_get_n_relations(relationSet); |
| for (int i = 0; i < count; i++) { |
| AtkRelation* relation = atk_relation_set_get_relation(relationSet, i); |
| if (atk_relation_get_relation_type(relation) == relationType) { |
| atk_relation_set_remove(relationSet, relation); |
| break; |
| } |
| } |
| } |
| |
| static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) |
| { |
| // Elements with aria-labelledby should have the labelled-by relation as per the ARIA AAM spec. |
| // Controls with a label element and fieldsets with a legend element should also use this relation |
| // as per the HTML AAM spec. The reciprocal label-for relation should also be used. |
| removeAtkRelationByType(relationSet, ATK_RELATION_LABELLED_BY); |
| removeAtkRelationByType(relationSet, ATK_RELATION_LABEL_FOR); |
| if (coreObject->isControl()) { |
| if (AccessibilityObject* label = coreObject->correspondingLabelForControlElement()) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, ATK_OBJECT(label->wrapper())); |
| } else if (coreObject->isFieldset()) { |
| if (AccessibilityObject* label = coreObject->titleUIElement()) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, ATK_OBJECT(label->wrapper())); |
| } else if (coreObject->roleValue() == AccessibilityRole::Legend) { |
| if (RenderBlock* renderFieldset = ancestorsOfType<RenderBlock>(*coreObject->renderer()).first()) { |
| if (renderFieldset->isFieldset()) { |
| AccessibilityObject* fieldset = coreObject->axObjectCache()->getOrCreate(renderFieldset); |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, ATK_OBJECT(fieldset->wrapper())); |
| } |
| } |
| } else if (AccessibilityObject* control = coreObject->correspondingControlForLabelElement()) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, ATK_OBJECT(control->wrapper())); |
| else { |
| AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements; |
| coreObject->ariaLabelledByElements(ariaLabelledByElements); |
| for (const auto& accessibilityObject : ariaLabelledByElements) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, ATK_OBJECT(accessibilityObject->wrapper())); |
| } |
| |
| // Elements referenced by aria-labelledby should have the label-for relation as per the ARIA AAM spec. |
| AccessibilityObject::AccessibilityChildrenVector labels; |
| coreObject->ariaLabelledByReferencingElements(labels); |
| for (const auto& accessibilityObject : labels) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements with aria-flowto should have the flows-to relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_TO); |
| AccessibilityObject::AccessibilityChildrenVector ariaFlowToElements; |
| coreObject->ariaFlowToElements(ariaFlowToElements); |
| for (const auto& accessibilityObject : ariaFlowToElements) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_TO, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements referenced by aria-flowto should have the flows-from relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_FLOWS_FROM); |
| AccessibilityObject::AccessibilityChildrenVector flowFrom; |
| coreObject->ariaFlowToReferencingElements(flowFrom); |
| for (const auto& accessibilityObject : flowFrom) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_FLOWS_FROM, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements with aria-describedby should have the described-by relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIBED_BY); |
| AccessibilityObject::AccessibilityChildrenVector ariaDescribedByElements; |
| coreObject->ariaDescribedByElements(ariaDescribedByElements); |
| for (const auto& accessibilityObject : ariaDescribedByElements) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements referenced by aria-describedby should have the description-for relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_DESCRIPTION_FOR); |
| AccessibilityObject::AccessibilityChildrenVector describers; |
| coreObject->ariaDescribedByReferencingElements(describers); |
| for (const auto& accessibilityObject : describers) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DESCRIPTION_FOR, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements with aria-controls should have the controller-for relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLER_FOR); |
| AccessibilityObject::AccessibilityChildrenVector ariaControls; |
| coreObject->ariaControlsElements(ariaControls); |
| for (const auto& accessibilityObject : ariaControls) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLER_FOR, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements referenced by aria-controls should have the controlled-by relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_CONTROLLED_BY); |
| AccessibilityObject::AccessibilityChildrenVector controllers; |
| coreObject->ariaControlsReferencingElements(controllers); |
| for (const auto& accessibilityObject : controllers) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_CONTROLLED_BY, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements with aria-owns should have the node-parent-of relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_NODE_PARENT_OF); |
| AccessibilityObject::AccessibilityChildrenVector ariaOwns; |
| coreObject->ariaOwnsElements(ariaOwns); |
| for (const auto& accessibilityObject : ariaOwns) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_NODE_PARENT_OF, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements referenced by aria-owns should have the node-child-of relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_NODE_CHILD_OF); |
| AccessibilityObject::AccessibilityChildrenVector owners; |
| coreObject->ariaOwnsReferencingElements(owners); |
| for (const auto& accessibilityObject : owners) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_NODE_CHILD_OF, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| #if ATK_CHECK_VERSION(2, 25, 2) |
| // Elements with aria-details should have the details relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_DETAILS); |
| AccessibilityObject::AccessibilityChildrenVector ariaDetails; |
| coreObject->ariaDetailsElements(ariaDetails); |
| for (const auto& accessibilityObject : ariaDetails) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DETAILS, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements referenced by aria-details should have the details-for relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_DETAILS_FOR); |
| AccessibilityObject::AccessibilityChildrenVector details; |
| coreObject->ariaDetailsReferencingElements(details); |
| for (const auto& accessibilityObject : details) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_DETAILS_FOR, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements with aria-errormessage should have the error-message relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_ERROR_MESSAGE); |
| AccessibilityObject::AccessibilityChildrenVector ariaErrorMessage; |
| coreObject->ariaErrorMessageElements(ariaErrorMessage); |
| for (const auto& accessibilityObject : ariaErrorMessage) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_ERROR_MESSAGE, ATK_OBJECT(accessibilityObject->wrapper())); |
| |
| // Elements referenced by aria-errormessage should have the error-for relation as per the ARIA AAM spec. |
| removeAtkRelationByType(relationSet, ATK_RELATION_ERROR_FOR); |
| AccessibilityObject::AccessibilityChildrenVector errors; |
| coreObject->ariaErrorMessageReferencingElements(errors); |
| for (const auto& accessibilityObject : errors) |
| atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_ERROR_FOR, ATK_OBJECT(accessibilityObject->wrapper())); |
| #endif |
| } |
| |
| static bool isRootObject(AccessibilityObject* coreObject) |
| { |
| // The root accessible object in WebCore is always an object with |
| // the ScrolledArea role with one child with the WebArea role. |
| if (!coreObject || !coreObject->isScrollView()) |
| return false; |
| |
| AccessibilityObject* firstChild = coreObject->firstChild(); |
| return firstChild && firstChild->isWebArea(); |
| } |
| |
| static AtkObject* webkitAccessibleGetParent(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| // Check first if the parent has been already set. |
| AtkObject* accessibleParent = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->get_parent(object); |
| if (accessibleParent) |
| return accessibleParent; |
| |
| // Parent not set yet, so try to find it in the hierarchy. |
| auto* coreObject = accessible->priv->object; |
| auto* coreParent = coreObject->parentObjectUnignored(); |
| if (!coreParent && isRootObject(coreObject)) { |
| // The top level object claims to not have a parent. This makes it |
| // impossible for assistive technologies to ascend the accessible |
| // hierarchy all the way to the application. (Bug 30489) |
| if (!coreObject->document()) |
| return nullptr; |
| } |
| |
| return coreParent ? ATK_OBJECT(coreParent->wrapper()) : nullptr; |
| } |
| |
| static gint webkitAccessibleGetNChildren(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, 0); |
| |
| return accessible->priv->object->children().size(); |
| } |
| |
| static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| if (index < 0) |
| return nullptr; |
| |
| const auto& children = accessible->priv->object->children(); |
| if (static_cast<size_t>(index) >= children.size()) |
| return nullptr; |
| |
| auto& coreChild = children[index]; |
| if (!coreChild) |
| return nullptr; |
| |
| auto* child = coreChild->wrapper(); |
| if (!child) |
| return nullptr; |
| |
| atk_object_set_parent(ATK_OBJECT(child), object); |
| return ATK_OBJECT(g_object_ref(child)); |
| } |
| |
| static gint webkitAccessibleGetIndexInParent(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, -1); |
| |
| auto* coreObject = accessible->priv->object; |
| auto* parent = coreObject->parentObjectUnignored(); |
| if (!parent && isRootObject(coreObject)) { |
| if (!coreObject->document()) |
| return -1; |
| |
| auto* atkParent = parent ? ATK_OBJECT(parent->wrapper()) : nullptr; |
| if (!atkParent) |
| return -1; |
| |
| unsigned count = atk_object_get_n_accessible_children(atkParent); |
| for (unsigned i = 0; i < count; ++i) { |
| GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(atkParent, i)); |
| if (child.get() == object) |
| return i; |
| } |
| } |
| |
| if (!parent) |
| return -1; |
| |
| size_t index = parent->children().find(coreObject); |
| return (index == WTF::notFound) ? -1 : index; |
| } |
| |
| static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| AtkAttributeSet* attributeSet = nullptr; |
| #if PLATFORM(GTK) |
| attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk"); |
| #elif PLATFORM(WPE) |
| attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WPEWebKit"); |
| #endif |
| |
| auto* coreObject = accessible->priv->object; |
| |
| // Hack needed for WebKit2 tests because obtaining an element by its ID |
| // cannot be done from the UIProcess. Assistive technologies have no need |
| // for this information. |
| Element* element = coreObject->element() ? coreObject->element() : coreObject->actionElement(); |
| if (element) { |
| String tagName = element->tagName(); |
| if (!tagName.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "tag", tagName.convertToASCIILowercase().utf8().data()); |
| String id = element->getIdAttribute().string(); |
| if (!id.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data()); |
| } |
| |
| int level = coreObject->isHeading() ? coreObject->headingLevel() : coreObject->hierarchicalLevel(); |
| if (level) { |
| String value = String::number(level); |
| attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data()); |
| } |
| |
| if (coreObject->roleValue() == AccessibilityRole::MathElement) { |
| if (coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript)) |
| attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "pre"); |
| else if (coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript)) |
| attributeSet = addToAtkAttributeSet(attributeSet, "multiscript-type", "post"); |
| } |
| |
| if (is<AccessibilityTable>(*coreObject) && downcast<AccessibilityTable>(*coreObject).isExposable()) { |
| auto& table = downcast<AccessibilityTable>(*coreObject); |
| int rowCount = table.axRowCount(); |
| if (rowCount) |
| attributeSet = addToAtkAttributeSet(attributeSet, "rowcount", String::number(rowCount).utf8().data()); |
| |
| int columnCount = table.axColumnCount(); |
| if (columnCount) |
| attributeSet = addToAtkAttributeSet(attributeSet, "colcount", String::number(columnCount).utf8().data()); |
| } else if (is<AccessibilityTableRow>(*coreObject)) { |
| auto& row = downcast<AccessibilityTableRow>(*coreObject); |
| int rowIndex = row.axRowIndex(); |
| if (rowIndex != -1) |
| attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data()); |
| } else if (is<AccessibilityTableCell>(*coreObject)) { |
| auto& cell = downcast<AccessibilityTableCell>(*coreObject); |
| int rowIndex = cell.axRowIndex(); |
| if (rowIndex != -1) |
| attributeSet = addToAtkAttributeSet(attributeSet, "rowindex", String::number(rowIndex).utf8().data()); |
| |
| int columnIndex = cell.axColumnIndex(); |
| if (columnIndex != -1) |
| attributeSet = addToAtkAttributeSet(attributeSet, "colindex", String::number(columnIndex).utf8().data()); |
| |
| int rowSpan = cell.axRowSpan(); |
| if (rowSpan != -1) |
| attributeSet = addToAtkAttributeSet(attributeSet, "rowspan", String::number(rowSpan).utf8().data()); |
| |
| int columnSpan = cell.axColumnSpan(); |
| if (columnSpan != -1) |
| attributeSet = addToAtkAttributeSet(attributeSet, "colspan", String::number(columnSpan).utf8().data()); |
| } |
| |
| String placeholder = coreObject->placeholderValue(); |
| if (!placeholder.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data()); |
| |
| if (coreObject->supportsAutoComplete()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "autocomplete", coreObject->autoCompleteValue().utf8().data()); |
| |
| if (coreObject->supportsHasPopup()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "haspopup", coreObject->popupValue().utf8().data()); |
| |
| if (coreObject->supportsCurrent()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "current", coreObject->currentValue().utf8().data()); |
| |
| // The Core AAM states that an explicitly-set value should be exposed, including "none". |
| if (coreObject->hasAttribute(HTMLNames::aria_sortAttr)) { |
| switch (coreObject->sortDirection()) { |
| case AccessibilitySortDirection::Invalid: |
| break; |
| case AccessibilitySortDirection::Ascending: |
| attributeSet = addToAtkAttributeSet(attributeSet, "sort", "ascending"); |
| break; |
| case AccessibilitySortDirection::Descending: |
| attributeSet = addToAtkAttributeSet(attributeSet, "sort", "descending"); |
| break; |
| case AccessibilitySortDirection::Other: |
| attributeSet = addToAtkAttributeSet(attributeSet, "sort", "other"); |
| break; |
| case AccessibilitySortDirection::None: |
| attributeSet = addToAtkAttributeSet(attributeSet, "sort", "none"); |
| } |
| } |
| |
| if (coreObject->supportsPosInSet()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "posinset", String::number(coreObject->posInSet()).utf8().data()); |
| |
| if (coreObject->supportsSetSize()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "setsize", String::number(coreObject->setSize()).utf8().data()); |
| |
| String isReadOnly = coreObject->readOnlyValue(); |
| if (!isReadOnly.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "readonly", isReadOnly.utf8().data()); |
| |
| String valueDescription = coreObject->valueDescription(); |
| if (!valueDescription.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "valuetext", valueDescription.utf8().data()); |
| |
| // According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules: |
| // "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so." |
| // In the case of ATK, the mechanism to do so is an object attribute pair (xml-roles:"string"). |
| // We cannot use the computedRoleString for this purpose because it is not limited to elements |
| // with ARIA roles, and it might not contain the actual ARIA role value (e.g. DPub ARIA). |
| String roleString = coreObject->getAttribute(HTMLNames::roleAttr); |
| if (!roleString.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", roleString.utf8().data()); |
| |
| String computedRoleString = coreObject->computedRoleString(); |
| if (!computedRoleString.isEmpty()) { |
| attributeSet = addToAtkAttributeSet(attributeSet, "computed-role", computedRoleString.utf8().data()); |
| |
| // The HTML AAM maps several elements to ARIA landmark roles. In order for the type of landmark |
| // to be obtainable in the same fashion as an ARIA landmark, fall back on the computedRoleString. |
| // We also want to do this for the style-format-group element types so that the type of format |
| // group it is doesn't get lost to a generic platform role. |
| if (coreObject->ariaRoleAttribute() == AccessibilityRole::Unknown |
| && (coreObject->isLandmark() || coreObject->isStyleFormatGroup())) |
| attributeSet = addToAtkAttributeSet(attributeSet, "xml-roles", computedRoleString.utf8().data()); |
| } |
| |
| String roleDescription = coreObject->roleDescription(); |
| if (!roleDescription.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "roledescription", roleDescription.utf8().data()); |
| |
| // We need to expose the live region attributes even if the live region is currently disabled/off. |
| if (auto liveContainer = coreObject->liveRegionAncestor(false)) { |
| String liveStatus = liveContainer->liveRegionStatus(); |
| String relevant = liveContainer->liveRegionRelevant(); |
| bool isAtom = liveContainer->liveRegionAtomic(); |
| String liveRole = roleString.isEmpty() ? computedRoleString : roleString; |
| |
| // According to the Core AAM, we need to expose the above properties with "container-" prefixed |
| // object attributes regardless of whether the container is this object, or an ancestor of it. |
| attributeSet = addToAtkAttributeSet(attributeSet, "container-live", liveStatus.utf8().data()); |
| attributeSet = addToAtkAttributeSet(attributeSet, "container-relevant", relevant.utf8().data()); |
| if (isAtom) |
| attributeSet = addToAtkAttributeSet(attributeSet, "container-atomic", "true"); |
| if (!liveRole.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "container-live-role", liveRole.utf8().data()); |
| |
| // According to the Core AAM, if this object is the live region (rather than its descendant), |
| // we must expose the above properties on the object without a "container-" prefix. |
| if (liveContainer == coreObject) { |
| attributeSet = addToAtkAttributeSet(attributeSet, "live", liveStatus.utf8().data()); |
| attributeSet = addToAtkAttributeSet(attributeSet, "relevant", relevant.utf8().data()); |
| if (isAtom) |
| attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true"); |
| } else if (!isAtom && coreObject->liveRegionAtomic()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "atomic", "true"); |
| } |
| |
| // The Core AAM states the author-provided value should be exposed as-is. |
| String dropEffect = coreObject->getAttribute(HTMLNames::aria_dropeffectAttr); |
| if (!dropEffect.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "dropeffect", dropEffect.utf8().data()); |
| |
| if (coreObject->isARIAGrabbed()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "true"); |
| else if (coreObject->supportsARIADragging()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "grabbed", "false"); |
| |
| // The Core AAM states the author-provided value should be exposed as-is. |
| const AtomString& keyShortcuts = coreObject->keyShortcutsValue(); |
| if (!keyShortcuts.isEmpty()) |
| attributeSet = addToAtkAttributeSet(attributeSet, "keyshortcuts", keyShortcuts.string().utf8().data()); |
| |
| return attributeSet; |
| } |
| |
| static AtkRole atkRole(AccessibilityObject* coreObject) |
| { |
| switch (coreObject->roleValue()) { |
| case AccessibilityRole::ApplicationAlert: |
| return ATK_ROLE_ALERT; |
| case AccessibilityRole::ApplicationAlertDialog: |
| case AccessibilityRole::ApplicationDialog: |
| return ATK_ROLE_DIALOG; |
| case AccessibilityRole::ApplicationStatus: |
| return ATK_ROLE_STATUSBAR; |
| case AccessibilityRole::Unknown: |
| return ATK_ROLE_UNKNOWN; |
| case AccessibilityRole::Audio: |
| return ATK_ROLE_AUDIO; |
| case AccessibilityRole::Video: |
| return ATK_ROLE_VIDEO; |
| case AccessibilityRole::Button: |
| return ATK_ROLE_PUSH_BUTTON; |
| case AccessibilityRole::Switch: |
| case AccessibilityRole::ToggleButton: |
| return ATK_ROLE_TOGGLE_BUTTON; |
| case AccessibilityRole::RadioButton: |
| return ATK_ROLE_RADIO_BUTTON; |
| case AccessibilityRole::CheckBox: |
| return ATK_ROLE_CHECK_BOX; |
| case AccessibilityRole::Slider: |
| return ATK_ROLE_SLIDER; |
| case AccessibilityRole::TabGroup: |
| case AccessibilityRole::TabList: |
| return ATK_ROLE_PAGE_TAB_LIST; |
| case AccessibilityRole::TextField: |
| case AccessibilityRole::TextArea: |
| case AccessibilityRole::SearchField: |
| return ATK_ROLE_ENTRY; |
| case AccessibilityRole::StaticText: |
| return ATK_ROLE_STATIC; |
| case AccessibilityRole::Outline: |
| case AccessibilityRole::Tree: |
| return ATK_ROLE_TREE; |
| case AccessibilityRole::TreeItem: |
| return ATK_ROLE_TREE_ITEM; |
| case AccessibilityRole::MenuBar: |
| return ATK_ROLE_MENU_BAR; |
| case AccessibilityRole::MenuListPopup: |
| case AccessibilityRole::Menu: |
| return ATK_ROLE_MENU; |
| case AccessibilityRole::MenuListOption: |
| case AccessibilityRole::MenuItem: |
| case AccessibilityRole::MenuButton: |
| return ATK_ROLE_MENU_ITEM; |
| case AccessibilityRole::MenuItemCheckbox: |
| return ATK_ROLE_CHECK_MENU_ITEM; |
| case AccessibilityRole::MenuItemRadio: |
| return ATK_ROLE_RADIO_MENU_ITEM; |
| case AccessibilityRole::Column: |
| // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? |
| return ATK_ROLE_UNKNOWN; // Matches Mozilla |
| case AccessibilityRole::Row: |
| return ATK_ROLE_TABLE_ROW; |
| case AccessibilityRole::Toolbar: |
| return ATK_ROLE_TOOL_BAR; |
| case AccessibilityRole::Meter: |
| return ATK_ROLE_LEVEL_BAR; |
| case AccessibilityRole::BusyIndicator: |
| case AccessibilityRole::ProgressIndicator: |
| return ATK_ROLE_PROGRESS_BAR; |
| case AccessibilityRole::Window: |
| return ATK_ROLE_WINDOW; |
| case AccessibilityRole::PopUpButton: |
| return coreObject->hasPopup() ? ATK_ROLE_PUSH_BUTTON : ATK_ROLE_COMBO_BOX; |
| case AccessibilityRole::ComboBox: |
| return ATK_ROLE_COMBO_BOX; |
| case AccessibilityRole::SplitGroup: |
| return ATK_ROLE_SPLIT_PANE; |
| case AccessibilityRole::Splitter: |
| return ATK_ROLE_SEPARATOR; |
| #if PLATFORM(GTK) |
| case AccessibilityRole::ColorWell: |
| // ATK_ROLE_COLOR_CHOOSER is defined as a dialog (i.e. it's what appears when you push the button). |
| return ATK_ROLE_PUSH_BUTTON; |
| #endif |
| case AccessibilityRole::List: |
| return ATK_ROLE_LIST; |
| case AccessibilityRole::ScrollBar: |
| return ATK_ROLE_SCROLL_BAR; |
| case AccessibilityRole::ScrollArea: |
| case AccessibilityRole::TabPanel: |
| return ATK_ROLE_SCROLL_PANE; |
| case AccessibilityRole::Grid: |
| case AccessibilityRole::Table: |
| return ATK_ROLE_TABLE; |
| case AccessibilityRole::TreeGrid: |
| return ATK_ROLE_TREE_TABLE; |
| case AccessibilityRole::Application: |
| return ATK_ROLE_APPLICATION; |
| case AccessibilityRole::ApplicationGroup: |
| case AccessibilityRole::Feed: |
| case AccessibilityRole::Figure: |
| case AccessibilityRole::GraphicsObject: |
| case AccessibilityRole::Group: |
| case AccessibilityRole::RadioGroup: |
| case AccessibilityRole::SVGRoot: |
| return ATK_ROLE_PANEL; |
| case AccessibilityRole::RowHeader: |
| return ATK_ROLE_ROW_HEADER; |
| case AccessibilityRole::ColumnHeader: |
| return ATK_ROLE_COLUMN_HEADER; |
| case AccessibilityRole::Caption: |
| return ATK_ROLE_CAPTION; |
| case AccessibilityRole::Cell: |
| case AccessibilityRole::GridCell: |
| return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_TABLE_CELL; |
| case AccessibilityRole::Link: |
| case AccessibilityRole::WebCoreLink: |
| case AccessibilityRole::ImageMapLink: |
| return ATK_ROLE_LINK; |
| case AccessibilityRole::ImageMap: |
| return ATK_ROLE_IMAGE_MAP; |
| case AccessibilityRole::GraphicsSymbol: |
| case AccessibilityRole::Image: |
| return ATK_ROLE_IMAGE; |
| case AccessibilityRole::ListMarker: |
| return ATK_ROLE_TEXT; |
| case AccessibilityRole::DocumentArticle: |
| return ATK_ROLE_ARTICLE; |
| case AccessibilityRole::Document: |
| case AccessibilityRole::GraphicsDocument: |
| return ATK_ROLE_DOCUMENT_FRAME; |
| case AccessibilityRole::DocumentNote: |
| return ATK_ROLE_COMMENT; |
| case AccessibilityRole::Heading: |
| return ATK_ROLE_HEADING; |
| case AccessibilityRole::ListBox: |
| // https://rawgit.com/w3c/aria/master/core-aam/core-aam.html#role-map-listbox |
| return coreObject->isDescendantOfRole(AccessibilityRole::ComboBox) ? ATK_ROLE_MENU : ATK_ROLE_LIST_BOX; |
| case AccessibilityRole::ListItem: |
| return coreObject->inheritsPresentationalRole() ? ATK_ROLE_SECTION : ATK_ROLE_LIST_ITEM; |
| case AccessibilityRole::ListBoxOption: |
| return coreObject->isDescendantOfRole(AccessibilityRole::ComboBox) ? ATK_ROLE_MENU_ITEM : ATK_ROLE_LIST_ITEM; |
| case AccessibilityRole::Paragraph: |
| return ATK_ROLE_PARAGRAPH; |
| case AccessibilityRole::Label: |
| case AccessibilityRole::Legend: |
| return ATK_ROLE_LABEL; |
| case AccessibilityRole::Blockquote: |
| return ATK_ROLE_BLOCK_QUOTE; |
| #if ATK_CHECK_VERSION(2, 25, 2) |
| case AccessibilityRole::Footnote: |
| return ATK_ROLE_FOOTNOTE; |
| #endif |
| case AccessibilityRole::ApplicationTextGroup: |
| case AccessibilityRole::Div: |
| case AccessibilityRole::Pre: |
| case AccessibilityRole::SVGText: |
| case AccessibilityRole::TextGroup: |
| return ATK_ROLE_SECTION; |
| case AccessibilityRole::Footer: |
| return ATK_ROLE_FOOTER; |
| case AccessibilityRole::Form: |
| if (coreObject->ariaRoleAttribute() != AccessibilityRole::Unknown) |
| return ATK_ROLE_LANDMARK; |
| return ATK_ROLE_FORM; |
| case AccessibilityRole::Canvas: |
| return ATK_ROLE_CANVAS; |
| case AccessibilityRole::HorizontalRule: |
| return ATK_ROLE_SEPARATOR; |
| case AccessibilityRole::SpinButton: |
| return ATK_ROLE_SPIN_BUTTON; |
| case AccessibilityRole::Tab: |
| return ATK_ROLE_PAGE_TAB; |
| case AccessibilityRole::UserInterfaceTooltip: |
| return ATK_ROLE_TOOL_TIP; |
| case AccessibilityRole::WebArea: |
| return ATK_ROLE_DOCUMENT_WEB; |
| case AccessibilityRole::WebApplication: |
| return ATK_ROLE_EMBEDDED; |
| case AccessibilityRole::ApplicationLog: |
| return ATK_ROLE_LOG; |
| case AccessibilityRole::ApplicationMarquee: |
| return ATK_ROLE_MARQUEE; |
| case AccessibilityRole::ApplicationTimer: |
| return ATK_ROLE_TIMER; |
| case AccessibilityRole::Definition: |
| return ATK_ROLE_DEFINITION; |
| case AccessibilityRole::DocumentMath: |
| return ATK_ROLE_MATH; |
| case AccessibilityRole::MathElement: |
| if (coreObject->isMathRow()) |
| return ATK_ROLE_PANEL; |
| if (coreObject->isMathTable()) |
| return ATK_ROLE_TABLE; |
| if (coreObject->isMathTableRow()) |
| return ATK_ROLE_TABLE_ROW; |
| if (coreObject->isMathTableCell()) |
| return ATK_ROLE_TABLE_CELL; |
| if (coreObject->isMathSubscriptSuperscript() || coreObject->isMathMultiscript()) |
| return ATK_ROLE_SECTION; |
| if (coreObject->isMathFraction()) |
| return ATK_ROLE_MATH_FRACTION; |
| if (coreObject->isMathSquareRoot() || coreObject->isMathRoot()) |
| return ATK_ROLE_MATH_ROOT; |
| if (coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Subscript) |
| || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript)) |
| return ATK_ROLE_SUBSCRIPT; |
| if (coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Superscript) |
| || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript)) |
| return ATK_ROLE_SUPERSCRIPT; |
| if (coreObject->isMathToken()) |
| return ATK_ROLE_STATIC; |
| return ATK_ROLE_UNKNOWN; |
| case AccessibilityRole::LandmarkBanner: |
| case AccessibilityRole::LandmarkComplementary: |
| case AccessibilityRole::LandmarkContentInfo: |
| case AccessibilityRole::LandmarkDocRegion: |
| case AccessibilityRole::LandmarkMain: |
| case AccessibilityRole::LandmarkNavigation: |
| case AccessibilityRole::LandmarkRegion: |
| case AccessibilityRole::LandmarkSearch: |
| return ATK_ROLE_LANDMARK; |
| case AccessibilityRole::DescriptionList: |
| return ATK_ROLE_DESCRIPTION_LIST; |
| case AccessibilityRole::Term: |
| case AccessibilityRole::DescriptionListTerm: |
| return ATK_ROLE_DESCRIPTION_TERM; |
| case AccessibilityRole::DescriptionListDetail: |
| return ATK_ROLE_DESCRIPTION_VALUE; |
| case AccessibilityRole::Deletion: |
| #if ATK_CHECK_VERSION(2, 33, 3) |
| return ATK_ROLE_CONTENT_DELETION; |
| #else |
| return ATK_ROLE_STATIC; |
| #endif |
| case AccessibilityRole::Insertion: |
| #if ATK_CHECK_VERSION(2, 33, 3) |
| return ATK_ROLE_CONTENT_INSERTION; |
| #else |
| return ATK_ROLE_STATIC; |
| #endif |
| case AccessibilityRole::Subscript: |
| return ATK_ROLE_SUBSCRIPT; |
| case AccessibilityRole::Superscript: |
| return ATK_ROLE_SUPERSCRIPT; |
| case AccessibilityRole::Inline: |
| case AccessibilityRole::SVGTextPath: |
| case AccessibilityRole::SVGTSpan: |
| case AccessibilityRole::Time: |
| return ATK_ROLE_STATIC; |
| default: |
| return ATK_ROLE_UNKNOWN; |
| } |
| } |
| |
| static AtkRole webkitAccessibleGetRole(AtkObject* object) |
| { |
| // ATK_ROLE_UNKNOWN should only be applied in cases where there is a valid |
| // WebCore accessible object for which the platform role mapping is unknown. |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, ATK_ROLE_INVALID); |
| |
| // Note: Why doesn't WebCore have a password field for this |
| if (accessible->priv->object->isPasswordField()) |
| return ATK_ROLE_PASSWORD_TEXT; |
| |
| return atkRole(accessible->priv->object); |
| } |
| |
| static bool isTextWithCaret(AccessibilityObject* coreObject) |
| { |
| if (!coreObject || !coreObject->isAccessibilityRenderObject()) |
| return false; |
| |
| Document* document = coreObject->document(); |
| if (!document) |
| return false; |
| |
| Frame* frame = document->frame(); |
| if (!frame) |
| return false; |
| |
| if (!frame->settings().caretBrowsingEnabled()) |
| return false; |
| |
| // Check text objects and paragraphs only. |
| auto* axObject = coreObject->wrapper(); |
| AtkRole role = axObject ? atk_object_get_role(ATK_OBJECT(axObject)) : ATK_ROLE_INVALID; |
| if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH) |
| return false; |
| |
| // Finally, check whether the caret is set in the current object. |
| VisibleSelection selection = coreObject->selection(); |
| if (!selection.isCaret()) |
| return false; |
| |
| return selectionBelongsToObject(coreObject, selection); |
| } |
| |
| static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet) |
| { |
| AccessibilityObject* parent = coreObject->parentObject(); |
| bool isListBoxOption = parent && parent->isListBox(); |
| |
| // Please keep the state list in alphabetical order |
| if ((isListBoxOption && coreObject->isSelectedOptionActive()) |
| || coreObject->currentState() != AccessibilityCurrentState::False) |
| atk_state_set_add_state(stateSet, ATK_STATE_ACTIVE); |
| |
| if (coreObject->isBusy()) |
| atk_state_set_add_state(stateSet, ATK_STATE_BUSY); |
| |
| if (coreObject->supportsChecked() && coreObject->canSetValueAttribute()) |
| atk_state_set_add_state(stateSet, ATK_STATE_CHECKABLE); |
| |
| if (coreObject->isChecked()) |
| atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); |
| |
| if ((coreObject->isTextControl() || coreObject->isNonNativeTextControl()) && coreObject->canSetValueAttribute()) |
| atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); |
| |
| // FIXME: Put both ENABLED and SENSITIVE together here for now |
| if (coreObject->isEnabled()) { |
| atk_state_set_add_state(stateSet, ATK_STATE_ENABLED); |
| atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE); |
| } |
| |
| if (coreObject->canSetExpandedAttribute()) |
| atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE); |
| |
| if (coreObject->isExpanded()) |
| atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED); |
| |
| if (coreObject->canSetFocusAttribute()) |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); |
| |
| // According to the Core AAM, if the element which is focused has a valid aria-activedescendant, |
| // we should not expose the focused state on the element which is actually focused, but instead |
| // on its active descendant. |
| if ((coreObject->isFocused() && !coreObject->activeDescendant()) || isTextWithCaret(coreObject)) |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); |
| else if (coreObject->isActiveDescendantOfFocusedContainer()) { |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); |
| } |
| |
| if (coreObject->orientation() == AccessibilityOrientation::Horizontal) |
| atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL); |
| else if (coreObject->orientation() == AccessibilityOrientation::Vertical) |
| atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL); |
| |
| if (coreObject->hasPopup()) |
| atk_state_set_add_state(stateSet, ATK_STATE_HAS_POPUP); |
| |
| if (coreObject->isIndeterminate()) |
| atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); |
| else if (coreObject->isCheckboxOrRadio() || coreObject->isMenuItem() || coreObject->isToggleButton()) { |
| if (coreObject->checkboxOrRadioValue() == AccessibilityButtonState::Mixed) |
| atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); |
| } |
| |
| if (coreObject->isModalNode()) |
| atk_state_set_add_state(stateSet, ATK_STATE_MODAL); |
| |
| if (coreObject->invalidStatus() != "false") |
| atk_state_set_add_state(stateSet, ATK_STATE_INVALID_ENTRY); |
| |
| if (coreObject->isMultiSelectable()) |
| atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE); |
| |
| // TODO: ATK_STATE_OPAQUE |
| |
| if (coreObject->isPressed()) |
| atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); |
| |
| if (!coreObject->canSetValueAttribute() && (coreObject->supportsReadOnly())) |
| atk_state_set_add_state(stateSet, ATK_STATE_READ_ONLY); |
| |
| if (coreObject->isRequired()) |
| atk_state_set_add_state(stateSet, ATK_STATE_REQUIRED); |
| |
| // TODO: ATK_STATE_SELECTABLE_TEXT |
| |
| if (coreObject->canSetSelectedAttribute()) { |
| atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE); |
| // Items in focusable lists have both STATE_SELECT{ABLE,ED} |
| // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on |
| // the former. |
| if (isListBoxOption) |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); |
| } |
| |
| if (coreObject->isSelected()) { |
| atk_state_set_add_state(stateSet, ATK_STATE_SELECTED); |
| // Items in focusable lists have both STATE_SELECT{ABLE,ED} |
| // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the |
| // former. |
| if (isListBoxOption) |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); |
| } |
| |
| // FIXME: Group both SHOWING and VISIBLE here for now |
| // Not sure how to handle this in WebKit, see bug |
| // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other |
| // issues with SHOWING vs VISIBLE. |
| if (!coreObject->isOffScreen()) { |
| atk_state_set_add_state(stateSet, ATK_STATE_SHOWING); |
| atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE); |
| } |
| |
| // Mutually exclusive, so we group these two |
| if (coreObject->roleValue() == AccessibilityRole::TextArea || coreObject->ariaIsMultiline()) |
| atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE); |
| else if (coreObject->roleValue() == AccessibilityRole::TextField || coreObject->roleValue() == AccessibilityRole::SearchField) |
| atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); |
| |
| // TODO: ATK_STATE_SENSITIVE |
| |
| if (coreObject->supportsAutoComplete() && coreObject->autoCompleteValue() != "none") |
| atk_state_set_add_state(stateSet, ATK_STATE_SUPPORTS_AUTOCOMPLETION); |
| |
| if (coreObject->isVisited()) |
| atk_state_set_add_state(stateSet, ATK_STATE_VISITED); |
| } |
| |
| static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object); |
| |
| // Make sure the layout is updated to really know whether the object |
| // is defunct or not, so we can return the proper state. |
| accessible->priv->object->updateBackingStore(); |
| |
| if (accessible->priv->object == fallbackObject()) { |
| atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT); |
| return stateSet; |
| } |
| |
| // Text objects must be focusable. |
| AtkRole role = atk_object_get_role(object); |
| if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH) |
| atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); |
| |
| setAtkStateSetFromCoreObject(accessible->priv->object, stateSet); |
| return stateSet; |
| } |
| |
| static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object); |
| setAtkRelationSetFromCoreObject(accessible->priv->object, relationSet); |
| return relationSet; |
| } |
| |
| static void webkitAccessibleInit(AtkObject* object, gpointer data) |
| { |
| if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize) |
| ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data); |
| |
| WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object); |
| accessible->priv->object = reinterpret_cast<AccessibilityObject*>(data); |
| } |
| |
| static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object) |
| { |
| auto* accessible = WEBKIT_ACCESSIBLE(object); |
| returnValIfWebKitAccessibleIsInvalid(accessible, nullptr); |
| |
| if (ATK_IS_DOCUMENT(object)) { |
| // TODO: Should we fall back on lang xml:lang when the following comes up empty? |
| String language = accessible->priv->object->language(); |
| if (!language.isEmpty()) |
| return webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedDocumentLocale, language.utf8()); |
| |
| } else if (ATK_IS_TEXT(object)) { |
| const gchar* locale = nullptr; |
| |
| AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object)); |
| for (auto* attributes = textAttributes; attributes; attributes = attributes->next) { |
| auto* atkAttribute = static_cast<AtkAttribute*>(attributes->data); |
| if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) { |
| locale = webkitAccessibleCacheAndReturnAtkProperty(accessible, AtkCachedDocumentLocale, atkAttribute->value); |
| break; |
| } |
| } |
| atk_attribute_set_free(textAttributes); |
| |
| return locale; |
| } |
| |
| return nullptr; |
| } |
| |
| static void webkit_accessible_class_init(WebKitAccessibleClass* klass) |
| { |
| auto* atkObjectClass = ATK_OBJECT_CLASS(klass); |
| atkObjectClass->initialize = webkitAccessibleInit; |
| atkObjectClass->get_name = webkitAccessibleGetName; |
| atkObjectClass->get_description = webkitAccessibleGetDescription; |
| atkObjectClass->get_parent = webkitAccessibleGetParent; |
| atkObjectClass->get_n_children = webkitAccessibleGetNChildren; |
| atkObjectClass->ref_child = webkitAccessibleRefChild; |
| atkObjectClass->get_role = webkitAccessibleGetRole; |
| atkObjectClass->ref_state_set = webkitAccessibleRefStateSet; |
| atkObjectClass->get_index_in_parent = webkitAccessibleGetIndexInParent; |
| atkObjectClass->get_attributes = webkitAccessibleGetAttributes; |
| atkObjectClass->ref_relation_set = webkitAccessibleRefRelationSet; |
| atkObjectClass->get_object_locale = webkitAccessibleGetObjectLocale; |
| } |
| |
| static const GInterfaceInfo atkInterfacesInitFunctions[] = { |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleActionInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleSelectionInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleEditableTextInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTextInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleComponentInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleImageInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTableInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleTableCellInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleHypertextInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleHyperlinkImplInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleDocumentInterfaceInit)), nullptr, nullptr}, |
| {reinterpret_cast<GInterfaceInitFunc>(reinterpret_cast<GCallback>(webkitAccessibleValueInterfaceInit)), nullptr, nullptr} |
| }; |
| |
| enum WAIType { |
| WAIAction, |
| WAISelection, |
| WAIEditableText, |
| WAIText, |
| WAIComponent, |
| WAIImage, |
| WAITable, |
| WAITableCell, |
| WAIHypertext, |
| WAIHyperlink, |
| WAIDocument, |
| WAIValue, |
| }; |
| |
| static GType atkInterfaceTypeFromWAIType(WAIType type) |
| { |
| switch (type) { |
| case WAIAction: |
| return ATK_TYPE_ACTION; |
| case WAISelection: |
| return ATK_TYPE_SELECTION; |
| case WAIEditableText: |
| return ATK_TYPE_EDITABLE_TEXT; |
| case WAIText: |
| return ATK_TYPE_TEXT; |
| case WAIComponent: |
| return ATK_TYPE_COMPONENT; |
| case WAIImage: |
| return ATK_TYPE_IMAGE; |
| case WAITable: |
| return ATK_TYPE_TABLE; |
| case WAITableCell: |
| return ATK_TYPE_TABLE_CELL; |
| case WAIHypertext: |
| return ATK_TYPE_HYPERTEXT; |
| case WAIHyperlink: |
| return ATK_TYPE_HYPERLINK_IMPL; |
| case WAIDocument: |
| return ATK_TYPE_DOCUMENT; |
| case WAIValue: |
| return ATK_TYPE_VALUE; |
| } |
| |
| return G_TYPE_INVALID; |
| } |
| |
| static bool roleIsTextType(AccessibilityRole role) |
| { |
| return role == AccessibilityRole::Paragraph |
| || role == AccessibilityRole::Heading |
| || role == AccessibilityRole::Div |
| || role == AccessibilityRole::Cell |
| || role == AccessibilityRole::Link |
| || role == AccessibilityRole::WebCoreLink |
| || role == AccessibilityRole::ListItem |
| || role == AccessibilityRole::Pre |
| || role == AccessibilityRole::GridCell |
| || role == AccessibilityRole::TextGroup |
| || role == AccessibilityRole::ApplicationTextGroup |
| || role == AccessibilityRole::ApplicationGroup; |
| } |
| |
| static guint16 interfaceMaskFromObject(AXCoreObject* coreObject) |
| { |
| guint16 interfaceMask = 0; |
| |
| // Component interface is always supported |
| interfaceMask |= 1 << WAIComponent; |
| |
| AccessibilityRole role = coreObject->roleValue(); |
| |
| // Action |
| // As the implementation of the AtkAction interface is a very |
| // basic one (just relays in executing the default action for each |
| // object, and only supports having one action per object), it is |
| // better just to implement this interface for every instance of |
| // the WebKitAccessible class and let WebCore decide what to do. |
| interfaceMask |= 1 << WAIAction; |
| |
| // Selection |
| if (coreObject->canHaveSelectedChildren() || coreObject->isMenuList()) |
| interfaceMask |= 1 << WAISelection; |
| |
| // Get renderer if available. |
| RenderObject* renderer = nullptr; |
| if (coreObject->isAccessibilityRenderObject()) |
| renderer = coreObject->renderer(); |
| |
| // Hyperlink (links and embedded objects). |
| if (coreObject->isLink() || (renderer && renderer->isReplaced())) |
| interfaceMask |= 1 << WAIHyperlink; |
| |
| // Text, Editable Text & Hypertext |
| if (role == AccessibilityRole::StaticText || coreObject->isMenuListOption()) |
| interfaceMask |= 1 << WAIText; |
| else if (coreObject->isTextControl() || coreObject->isNonNativeTextControl()) { |
| interfaceMask |= 1 << WAIText; |
| if (coreObject->canSetValueAttribute()) |
| interfaceMask |= 1 << WAIEditableText; |
| } else if (!coreObject->isWebArea()) { |
| if (role != AccessibilityRole::Table) { |
| interfaceMask |= 1 << WAIHypertext; |
| if ((renderer && renderer->childrenInline()) || roleIsTextType(role) || coreObject->isMathToken()) |
| interfaceMask |= 1 << WAIText; |
| } |
| |
| // Add the TEXT interface for list items whose |
| // first accessible child has a text renderer |
| if (role == AccessibilityRole::ListItem) { |
| const auto& children = coreObject->children(); |
| if (!children.isEmpty()) |
| interfaceMask |= interfaceMaskFromObject(children[0].get()); |
| } |
| } |
| |
| // Image |
| if (coreObject->isImage()) |
| interfaceMask |= 1 << WAIImage; |
| |
| // Table |
| if (coreObject->isTable()) |
| interfaceMask |= 1 << WAITable; |
| |
| if (role == AccessibilityRole::Cell || role == AccessibilityRole::GridCell || role == AccessibilityRole::ColumnHeader || role == AccessibilityRole::RowHeader) |
| interfaceMask |= 1 << WAITableCell; |
| |
| // Document |
| if (role == AccessibilityRole::WebArea) |
| interfaceMask |= 1 << WAIDocument; |
| |
| // Value |
| if (coreObject->supportsRangeValue()) |
| interfaceMask |= 1 << WAIValue; |
| |
| #if ENABLE(INPUT_TYPE_COLOR) |
| // Color type. |
| if (role == AccessibilityRole::ColorWell) |
| interfaceMask |= 1 << WAIText; |
| #endif |
| |
| return interfaceMask; |
| } |
| |
| static const char* uniqueAccessibilityTypeName(guint16 interfaceMask) |
| { |
| #define WAI_TYPE_NAME_LEN (30) // Enough for prefix + 5 hex characters (max). |
| static char name[WAI_TYPE_NAME_LEN + 1]; |
| |
| g_sprintf(name, "WAIType%x", interfaceMask); |
| name[WAI_TYPE_NAME_LEN] = '\0'; |
| |
| return name; |
| } |
| |
| static GType accessibilityTypeFromObject(AccessibilityObject* coreObject) |
| { |
| static const GTypeInfo typeInfo = { |
| sizeof(WebKitAccessibleClass), |
| nullptr, // GBaseInitFunc |
| nullptr, // GBaseFinalizeFunc |
| nullptr, // GClassInitFunc |
| nullptr, // GClassFinalizeFunc |
| nullptr, // class data |
| sizeof(WebKitAccessible), // instance size |
| 0, // nb preallocs |
| nullptr, // GInstanceInitFunc |
| nullptr // value table |
| }; |
| |
| guint16 interfaceMask = interfaceMaskFromObject(coreObject); |
| const char* atkTypeName = uniqueAccessibilityTypeName(interfaceMask); |
| if (GType type = g_type_from_name(atkTypeName)) |
| return type; |
| |
| GType type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, static_cast<GTypeFlags>(0)); |
| for (unsigned i = 0; i < G_N_ELEMENTS(atkInterfacesInitFunctions); ++i) { |
| if (interfaceMask & (1 << i)) { |
| g_type_add_interface_static(type, |
| atkInterfaceTypeFromWAIType(static_cast<WAIType>(i)), |
| &atkInterfacesInitFunctions[i]); |
| } |
| } |
| |
| return type; |
| } |
| |
| WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject) |
| { |
| auto* object = ATK_OBJECT(g_object_new(accessibilityTypeFromObject(coreObject), nullptr)); |
| atk_object_initialize(object, coreObject); |
| return WEBKIT_ACCESSIBLE(object); |
| } |
| |
| AccessibilityObject& webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible) |
| { |
| ASSERT(WEBKIT_IS_ACCESSIBLE(accessible)); |
| return *accessible->priv->object; |
| } |
| |
| void webkitAccessibleDetach(WebKitAccessible* accessible) |
| { |
| ASSERT(WEBKIT_IS_ACCESSIBLE(accessible)); |
| ASSERT(accessible->priv->object != fallbackObject()); |
| |
| if (accessible->priv->object->roleValue() == AccessibilityRole::WebArea) |
| atk_object_notify_state_change(ATK_OBJECT(accessible), ATK_STATE_DEFUNCT, TRUE); |
| |
| // We replace the WebCore AccessibilityObject with a fallback object that |
| // provides default implementations to avoid repetitive null-checking after |
| // detachment. |
| accessible->priv->object = fallbackObject(); |
| } |
| |
| bool webkitAccessibleIsDetached(WebKitAccessible* accessible) |
| { |
| ASSERT(WEBKIT_IS_ACCESSIBLE(accessible)); |
| return accessible->priv->object == fallbackObject(); |
| } |
| |
| const char* webkitAccessibleCacheAndReturnAtkProperty(WebKitAccessible* accessible, AtkCachedProperty property, CString&& value) |
| { |
| ASSERT(WEBKIT_IS_ACCESSIBLE(accessible)); |
| |
| WebKitAccessiblePrivate* priv = accessible->priv; |
| CString* propertyPtr = nullptr; |
| |
| switch (property) { |
| case AtkCachedAccessibleName: |
| propertyPtr = &priv->accessibleName; |
| break; |
| case AtkCachedAccessibleDescription: |
| propertyPtr = &priv->accessibleDescription; |
| break; |
| case AtkCachedActionName: |
| propertyPtr = &priv->actionName; |
| break; |
| case AtkCachedActionKeyBinding: |
| propertyPtr = &priv->actionKeyBinding; |
| break; |
| case AtkCachedDocumentLocale: |
| propertyPtr = &priv->documentLocale; |
| break; |
| case AtkCachedDocumentType: |
| propertyPtr = &priv->documentType; |
| break; |
| case AtkCachedDocumentEncoding: |
| propertyPtr = &priv->documentEncoding; |
| break; |
| case AtkCachedDocumentURI: |
| propertyPtr = &priv->documentURI; |
| break; |
| case AtkCachedImageDescription: |
| propertyPtr = &priv->imageDescription; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| // Don't invalidate old memory if not stricly needed, since other |
| // callers might be still holding on to it. |
| if (*propertyPtr != value) |
| *propertyPtr = WTFMove(value); |
| |
| return (*propertyPtr).data(); |
| } |
| |
| #endif // ENABLE(ACCESSIBILITY) |