static bool replacedNodeNeedsCharacter(Node* replacedNode)
{
    // we should always be given a rendered node and a replaced node, but be safe
    // replaced nodes are either attachments (widgets) or images
    if (!replacedNode || !replacedNode->renderer() || !replacedNode->renderer()->isReplaced() || replacedNode->isTextNode())
        return false;

    // create an AX object, but skip it if it is not supposed to be seen
    AccessibilityObject* object = replacedNode->renderer()->document()->axObjectCache()->getOrCreate(replacedNode->renderer());
    if (object->accessibilityIsIgnored())
        return false;

    return true;
}
static AtkRole webkit_accessible_get_role(AtkObject* object)
{
    AccessibilityObject* AXObject = core(object);

    if (!AXObject)
        return ATK_ROLE_UNKNOWN;

    // WebCore does not seem to have a role for list items
    if (AXObject->isGroup()) {
        AccessibilityObject* parent = AXObject->parentObjectUnignored();
        if (parent && parent->isList())
            return ATK_ROLE_LIST_ITEM;
    }

    // WebCore does not know about paragraph role, label role, or section role
    if (AXObject->isAccessibilityRenderObject()) {
        Node* node = static_cast<AccessibilityRenderObject*>(AXObject)->renderer()->node();
        if (node) {
            if (node->hasTagName(HTMLNames::pTag))
                return ATK_ROLE_PARAGRAPH;
            if (node->hasTagName(HTMLNames::labelTag))
                return ATK_ROLE_LABEL;
            if (node->hasTagName(HTMLNames::divTag))
                return ATK_ROLE_SECTION;
        }
    }

    // Note: Why doesn't WebCore have a password field for this
    if (AXObject->isPasswordField())
        return ATK_ROLE_PASSWORD_TEXT;

    return atkRole(AXObject->roleValue());
}
static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset)
{
    AccessibilityObject* coreObject = core(text);
    String ret;
    unsigned start = startOffset;
    int length = endOffset - startOffset;

    if (coreObject->isTextControl())
        ret = coreObject->doAXStringForRange(PlainTextRange(start, length));
    else
        ret = coreObject->textUnderElement().substring(start, length);

    return g_strdup(ret.utf8().data());
}
String AccessibilityObject::language() const
{
    AccessibilityObject* parent = parentObject();
    
    // as a last resort, fall back to the content language specified in the meta tag
    if (!parent) {
        Document* doc = document();
        if (doc)
            return doc->contentLanguage();
        return String();
    }
    
    return parent->language();
}
void AccessibilityObject::ariaTreeItemContent(AccessibilityChildrenVector& result)
{
    // The ARIA tree item content are the item that are not other tree items or their containing groups.
    AccessibilityChildrenVector axChildren = children();
    unsigned count = axChildren.size();
    for (unsigned k = 0; k < count; ++k) {
        AccessibilityObject* obj = axChildren[k].get();
        AccessibilityRole role = obj->roleValue();
        if (role == TreeItemRole || role == GroupRole)
            continue;
        
        result.append(obj);
    }
}
static gint webkitAccessibleGetNChildren(AtkObject* object)
{
    g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);

    AccessibilityObject* coreObject = core(object);

    // Tables should be treated in a different way because rows should
    // be bypassed when exposing the accessible hierarchy.
    if (coreObject->isAccessibilityTable())
        return getNChildrenForTable(coreObject);

    return coreObject->children().size();
}
Example #7
0
static void modifyAccessibilityValue(AtkObject* axObject, bool increment)
{
    if (!axObject || !WEBKIT_IS_ACCESSIBLE(axObject))
        return;

    AccessibilityObject* coreObject = webkit_accessible_get_accessibility_object(WEBKIT_ACCESSIBLE(axObject));
    if (!coreObject)
        return;

    if (increment)
        coreObject->increment();
    else
        coreObject->decrement();
}
Example #8
0
void AXObjectCache::nodeTextChangePlatformNotification(AccessibilityObject* object, AXTextChange textChange, unsigned offset, const String& text)
{
    if (!object || text.isEmpty())
        return;

    AccessibilityObject* parentObject = object->parentObjectUnignored();
    if (!parentObject)
        return;

    AtkObject* wrapper = parentObject->wrapper();
    if (!wrapper || !ATK_IS_TEXT(wrapper))
        return;

    Node* node = object->node();
    if (!node)
        return;

    // Ensure document's layout is up-to-date before using TextIterator.
    Document& document = node->document();
    document.updateLayout();

    // Select the right signal to be emitted
    CString detail;
    switch (textChange) {
    case AXObjectCache::AXTextInserted:
        detail = "text-insert";
        break;
    case AXObjectCache::AXTextDeleted:
        detail = "text-remove";
        break;
    }

    String textToEmit = text;
    unsigned offsetToEmit = offset;

    // If the object we're emitting the signal from represents a
    // password field, we will emit the masked text.
    if (parentObject->isPasswordField()) {
        String maskedText = parentObject->passwordFieldValue();
        textToEmit = maskedText.substring(offset, text.length());
    } else {
        // Consider previous text objects that might be present for
        // the current accessibility object to ensure we emit the
        // right offset (e.g. multiline text areas).
        RefPtr<Range> range = Range::create(document, node->parentNode(), 0, node, 0);
        offsetToEmit = offset + TextIterator::rangeLength(range.get());
    }

    g_signal_emit_by_name(wrapper, detail.data(), offsetToEmit, textToEmit.length(), textToEmit.utf8().data());
}
void AccessibilityARIAGrid::addChildren()
{
    ASSERT(!m_haveChildren); 
    
    if (!isAccessibilityTable()) {
        AccessibilityRenderObject::addChildren();
        return;
    }
    
    m_haveChildren = true;
    if (!m_renderer)
        return;
    
    AXObjectCache* axCache = m_renderer->document()->axObjectCache();
    
    // add only rows that are labeled as aria rows
    HashSet<AccessibilityObject*> appendedRows;
    unsigned columnCount = 0;
    for (RefPtr<AccessibilityObject> child = firstChild(); child; child = child->nextSibling()) {

        if (!addTableCellChild(child.get(), appendedRows, columnCount)) {
            
            // in case the render tree doesn't match the expected ARIA hierarchy, look at the children
            if (!child->hasChildren())
                child->addChildren();

            // The children of this non-row will contain all non-ignored elements (recursing to find them). 
            // This allows the table to dive arbitrarily deep to find the rows.
            AccessibilityChildrenVector children = child->children();
            size_t length = children.size();
            for (size_t i = 0; i < length; ++i)
                addTableCellChild(children[i].get(), appendedRows, columnCount);
        }
    }
    
    // make the columns based on the number of columns in the first body
    for (unsigned i = 0; i < columnCount; ++i) {
        AccessibilityTableColumn* column = static_cast<AccessibilityTableColumn*>(axCache->getOrCreate(ColumnRole));
        column->setColumnIndex((int)i);
        column->setParent(this);
        m_columns.append(column);
        if (!column->accessibilityIsIgnored())
            m_children.append(column);
    }
    
    AccessibilityObject* headerContainerObject = headerContainer();
    if (headerContainerObject && !headerContainerObject->accessibilityIsIgnored())
        m_children.append(headerContainerObject);
}
AtkObject* webkitAccessibleTableCellGetTable(AtkTableCell* cell)
{
    g_return_val_if_fail(ATK_TABLE_CELL(cell), nullptr);
    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(cell), nullptr);

    AccessibilityObject* axObject = core(cell);
    if (!axObject || !axObject->isTableCell())
        return nullptr;

    AtkObject* table = atk_object_get_parent(axObject->wrapper());
    if (!table || !ATK_IS_TABLE(table))
        return nullptr;

    return ATK_OBJECT(g_object_ref(table));
}
void AccessibilityMenuListPopup::childrenChanged()
{
    AXObjectCache* cache = axObjectCache();
    for (size_t i = m_children.size(); i > 0 ; --i) {
        AccessibilityObject* child = m_children[i - 1].get();
        if (child->actionElement() && !child->actionElement()->inRenderedDocument()) {
            child->detachFromParent();
            cache->remove(child->axObjectID());
        }
    }
    
    m_children.clear();
    m_haveChildren = false;
    addChildren();
}
void AccessibilityObject::ariaTreeItemDisclosedRows(AccessibilityChildrenVector& result)
{
    AccessibilityChildrenVector axChildren = children();
    unsigned count = axChildren.size();
    for (unsigned k = 0; k < count; ++k) {
        AccessibilityObject* obj = axChildren[k].get();
        
        // Add tree items as the rows.
        if (obj->roleValue() == TreeItemRole)
            result.append(obj);
        // If it's not a tree item, then descend into the group to find more tree items.
        else 
            obj->ariaTreeRows(result);
    }    
}
static gboolean webkitAccessibleHyperlinkActionDoAction(AtkAction* action, gint index)
{
    g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), FALSE);
    g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, FALSE);
    g_return_val_if_fail(!index, FALSE);

    if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl))
        return FALSE;

    AccessibilityObject* coreObject = core(action);
    if (!coreObject)
        return FALSE;

    return coreObject->performDefaultAction();
}
AccessibilityTable* AccessibilityARIAGridRow::parentTable() const
{
    // The parent table might not be the direct ancestor of the row unfortunately. ARIA states that role="grid" should
    // only have "row" elements, but if not, we still should handle it gracefully by finding the right table.
    for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
        // The parent table for an ARIA grid row should be an ARIA table.
        if (is<AccessibilityTable>(*parent)) {
            AccessibilityTable& tableParent = downcast<AccessibilityTable>(*parent);
            if (tableParent.isExposableThroughAccessibility() && tableParent.isAriaTable())
                return &tableParent;
        }
    }
    
    return nullptr;
}
static gboolean webkitAccessibleSelectionIsChildSelected(AtkSelection* selection, gint index)
{
    g_return_val_if_fail(ATK_SELECTION(selection), FALSE);
    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(selection), FALSE);

    AccessibilityObject* coreSelection = core(selection);
    if (!coreSelection)
        return FALSE;

    AccessibilityObject* option = optionFromList(selection, index);
    if (option && (coreSelection->isListBox() || coreSelection->isMenuList()))
        return option->isSelected();

    return FALSE;
}
void AccessibilityObject::ariaTreeRows(AccessibilityChildrenVector& result)
{
    AccessibilityChildrenVector axChildren = children();
    unsigned count = axChildren.size();
    for (unsigned k = 0; k < count; ++k) {
        AccessibilityObject* obj = axChildren[k].get();

        // Add tree items as the rows.
        if (obj->roleValue() == TreeItemRole)
            result.append(obj);

        // Now see if this item also has rows hiding inside of it.
        obj->ariaTreeRows(result);
    }
}
static const gchar* webkitAccessibleHyperlinkActionGetName(AtkAction* action, gint index)
{
    g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(action), 0);
    g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl, 0);
    g_return_val_if_fail(!index, 0);

    if (!ATK_IS_ACTION(WEBKIT_ACCESSIBLE_HYPERLINK(action)->priv->hyperlinkImpl))
        return 0;

    AccessibilityObject* coreObject = core(action);
    if (!coreObject)
        return 0;

    return returnString(coreObject->actionVerb());
}
Example #18
0
static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object)
{
    g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), 0);
    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), 0);

    AtkAttributeSet* attributeSet = 0;
#if PLATFORM(GTK)
    attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk");
#elif PLATFORM(EFL)
    attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitEfl");
#endif

    AccessibilityObject* coreObject = core(object);
    if (!coreObject)
        return attributeSet;

    // 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.
    Node* node = coreObject->node();
    if (node && node->isElementNode()) {
        String id = toElement(node)->getIdAttribute().string();
        if (!id.isEmpty())
            attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data());
    }

    int headingLevel = coreObject->headingLevel();
    if (headingLevel) {
        String value = String::number(headingLevel);
        attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data());
    }

    // Set the 'layout-guess' attribute to help Assistive
    // Technologies know when an exposed table is not data table.
    if (coreObject->isAccessibilityTable() && !coreObject->isDataTable())
        attributeSet = addToAtkAttributeSet(attributeSet, "layout-guess", "true");

    String placeholder = coreObject->placeholderValue();
    if (!placeholder.isEmpty())
        attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data());

    if (coreObject->ariaHasPopup())
        attributeSet = addToAtkAttributeSet(attributeSet, "haspopup", "true");

    AccessibilitySortDirection sortDirection = coreObject->sortDirection();
    if (sortDirection != SortDirectionNone) {
        // WAI-ARIA spec says to translate the value as is from the attribute.
        const AtomicString& sortAttribute = coreObject->getAttribute(HTMLNames::aria_sortAttr);
        attributeSet = addToAtkAttributeSet(attributeSet, "sort", sortAttribute.string().utf8().data());
    }

    return attributeSet;
}
static AtkRole webkitAccessibleGetRole(AtkObject* object)
{
    g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE(object), ATK_ROLE_UNKNOWN);
    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(object), ATK_ROLE_UNKNOWN);

    AccessibilityObject* coreObject = core(object);

    if (!coreObject)
        return ATK_ROLE_UNKNOWN;

    // Note: Why doesn't WebCore have a password field for this
    if (coreObject->isPasswordField())
        return ATK_ROLE_PASSWORD_TEXT;

    return atkRole(coreObject);
}
static AtkObject* webkitAccessibleTableGetCaption(AtkTable* table)
{
    g_return_val_if_fail(ATK_TABLE(table), 0);
    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE(table), 0);

    AccessibilityObject* accTable = core(table);
    if (accTable->isAccessibilityRenderObject()) {
        Node* node = accTable->node();
        if (node && isHTMLTableElement(node)) {
            HTMLTableCaptionElement* caption = toHTMLTableElement(node)->caption();
            if (caption)
                return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->element())->wrapper();
        }
    }
    return 0;
}
static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset)
{
    AccessibilityObject* coreObject = core(text);

    // FIXME: We need to reimplement visiblePositionRangeForRange here
    // because the actual function checks the offset is within the
    // boundaries of text().length(), but text() only works for text
    // controls...
    VisiblePosition startPosition = coreObject->visiblePositionForIndex(offset);
    startPosition.setAffinity(DOWNSTREAM);
    VisiblePosition endPosition = coreObject->visiblePositionForIndex(offset);
    VisiblePositionRange range = VisiblePositionRange(startPosition, endPosition);

    coreObject->setSelectedVisiblePositionRange(range);
    return TRUE;
}
void AccessibilityListBox::addChildren()
{
    Node* selectNode = m_renderer->node();
    if (!selectNode)
        return;
    
    m_haveChildren = true;
    
    for (const auto& listItem : toHTMLSelectElement(selectNode)->listItems()) {
        // The cast to HTMLElement below is safe because the only other possible listItem type
        // would be a WMLElement, but WML builds don't use accessibility features at all.
        AccessibilityObject* listOption = listBoxOptionAccessibilityObject(listItem);
        if (listOption && !listOption->accessibilityIsIgnored())
            m_children.append(listOption);
    }
}
static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row)
{
    AccessibilityObject* accTable = core(table);
    if (accTable->isAccessibilityRenderObject()) {
        AccessibilityObject::AccessibilityChildrenVector allRowHeaders;
        static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders);

        unsigned rowCount = allRowHeaders.size();
        for (unsigned k = 0; k < rowCount; ++k) {
            AccessibilityObject* rowObject = allRowHeaders[k]->parentObject();
            if (static_cast<AccessibilityTableRow*>(rowObject)->rowIndex() == row)
                return allRowHeaders[k]->wrapper();
        }
    }
    return 0;
}
static const gchar* webkit_accessible_get_description(AtkObject* object)
{
    AccessibilityObject* coreObject = core(object);

    // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here.
    if (coreObject->roleValue() == TableRole && coreObject->ariaRoleAttribute() == UnknownRole) {
        Node* node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node();
        if (node && node->isHTMLElement()) {
            String summary = static_cast<HTMLTableElement*>(node)->summary();
            if (!summary.isEmpty())
                return returnString(summary);
        }
    }

    return returnString(coreObject->accessibilityDescription());
}
static gint webkit_accessible_get_index_in_parent(AtkObject* object)
{
    AccessibilityObject* coreObject = core(object);
    AccessibilityObject* parent = coreObject->parentObjectUnignored();

    g_return_val_if_fail(parent, 0);

    AccessibilityObject::AccessibilityChildrenVector children = parent->children();
    unsigned count = children.size();
    for (unsigned i = 0; i < count; ++i) {
        if (children[i] == coreObject)
            return i;
    }

    return 0;
}
static gboolean webkitAccessibleSelectionClearSelection(AtkSelection* selection)
{
    AccessibilityObject* coreSelection = core(selection);
    if (!coreSelection)
        return FALSE;

    AccessibilityObject::AccessibilityChildrenVector selectedItems;
    if (coreSelection->isListBox() || coreSelection->isMenuList()) {
        // Set the list of selected items to an empty list; then verify that it worked.
        AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
        listBox->setSelectedChildren(selectedItems);
        listBox->selectedChildren(selectedItems);
        return !selectedItems.size();
    }
    return FALSE;
}
bool AccessibilitySVGElement::inheritsPresentationalRole() const
{
    if (canSetFocusAttribute())
        return false;

    AccessibilityRole role = roleValue();
    if (role != AccessibilityRole::SVGTextPath && role != AccessibilityRole::SVGTSpan)
        return false;

    for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
        if (is<AccessibilityRenderObject>(*parent) && parent->element()->hasTagName(SVGNames::textTag))
            return parent->roleValue() == AccessibilityRole::Presentational;
    }

    return false;
}
static gchar* webkitAccessibleHyperlinkGetURI(AtkHyperlink* link, gint index)
{
    g_return_val_if_fail(WEBKIT_IS_ACCESSIBLE_HYPERLINK(link), 0);
    g_return_val_if_fail(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0);

    // FIXME: Do NOT support more than one instance of an AtkObject
    // implementing AtkHyperlinkImpl in every instance of AtkHyperLink
    g_return_val_if_fail(!index, 0);

    returnValIfWebKitAccessibleIsInvalid(WEBKIT_ACCESSIBLE_HYPERLINK(link)->priv->hyperlinkImpl, 0);

    AccessibilityObject* coreObject = core(link);
    if (!coreObject || coreObject->url().isNull())
        return 0;

    return g_strdup(coreObject->url().string().utf8().data());
}
AccessibilityObjectPlatformInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const
{
    AccessibilityObject* parent = parentObject();
    if (!parent)
        return DefaultBehavior;

    // When a list item is made up entirely of children (e.g. paragraphs)
    // the list item gets ignored. We need it.
    if (isGroup() && parent->isList())
        return IncludeObject;

    // Entries and password fields have extraneous children which we want to ignore.
    if (parent->isPasswordField() || parent->isTextControl())
        return IgnoreObject;

    return DefaultBehavior;
}
static gint webkitAccessibleTextGetNSelections(AtkText* text)
{
    AccessibilityObject* coreObject = core(text);
    VisibleSelection selection = coreObject->selection();

    // Only range selections are needed for the purpose of this method
    if (!selection.isRange())
        return 0;

    // We don't support multiple selections for now, so there's only
    // two possibilities
    // Also, we don't want to do anything if the selection does not
    // belong to the currently selected object. We have to check since
    // there's no way to get the selection for a given object, only
    // the global one (the API is a bit confusing)
    return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
}