This source file includes following definitions.
- m_node
- create
- accessibleNameForNode
- accessibilityDescriptionForElements
- alterSliderValue
- ariaAccessibilityDescription
- ariaLabeledByElements
- changeValueByStep
- computeAccessibilityIsIgnored
- determineAccessibilityRole
- determineAriaRoleAttribute
- elementsFromAttribute
- hasContentEditableAttributeSet
- isDescendantOfBarrenParent
- isGenericFocusableElement
- labelForElement
- menuButtonForMenu
- siblingWithAriaRole
- menuItemElementForMenu
- mouseButtonListener
- remapAriaRoleDueToParent
- init
- detach
- isAnchor
- isControl
- isFieldset
- isHeading
- isHovered
- isImage
- isImageButton
- isInputImage
- isLink
- isMenu
- isMenuButton
- isMultiSelectable
- isNativeCheckboxOrRadio
- isNativeImage
- isNativeTextControl
- isNonNativeTextControl
- isPasswordField
- isProgressIndicator
- isSlider
- isChecked
- isClickable
- isEnabled
- isIndeterminate
- isPressed
- isReadOnly
- isRequired
- canSetFocusAttribute
- canSetValueAttribute
- canvasHasFallbackContent
- exposesTitleUIElement
- headingLevel
- hierarchicalLevel
- text
- titleUIElement
- checkboxOrRadioValue
- colorValue
- valueDescription
- valueForRange
- maxValueForRange
- minValueForRange
- stepValueForRange
- stringValue
- ariaDescribedByAttribute
- ariaLabeledByAttribute
- ariaRoleAttribute
- shouldUseAccessiblityObjectInnerText
- textUnderElement
- accessibilityDescription
- title
- helpText
- elementRect
- parentObject
- parentObjectIfExists
- firstChild
- nextSibling
- addChildren
- addChild
- insertChild
- canHaveChildren
- actionElement
- anchorElement
- document
- setNode
- correspondingControlForLabelElement
- labelElementContainer
- setFocused
- increment
- decrement
- childrenChanged
- selectionChanged
- textChanged
- updateAccessibilityRole
- alternativeTextForWebArea
- alternativeText
- ariaLabeledByText
- changeValueByPercent
#include "config.h"
#include "core/accessibility/AXNodeObject.h"
#include "core/accessibility/AXObjectCache.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/Text.h"
#include "core/html/HTMLFieldSetElement.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLLabelElement.h"
#include "core/html/HTMLLegendElement.h"
#include "core/html/HTMLSelectElement.h"
#include "core/html/HTMLTextAreaElement.h"
#include "core/rendering/RenderObject.h"
#include "platform/UserGestureIndicator.h"
#include "wtf/text/StringBuilder.h"
namespace WebCore {
using namespace HTMLNames;
AXNodeObject::AXNodeObject(Node* node)
: AXObject()
, m_ariaRole(UnknownRole)
, m_childrenDirty(false)
#ifndef NDEBUG
, m_initialized(false)
#endif
, m_node(node)
{
}
PassRefPtr<AXNodeObject> AXNodeObject::create(Node* node)
{
return adoptRef(new AXNodeObject(node));
}
AXNodeObject::~AXNodeObject()
{
ASSERT(isDetached());
}
static String accessibleNameForNode(Node* node)
{
if (node->isTextNode())
return toText(node)->data();
if (isHTMLInputElement(*node))
return toHTMLInputElement(*node).value();
if (node->isHTMLElement()) {
const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
if (!alt.isEmpty())
return alt;
}
return String();
}
String AXNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
{
StringBuilder builder;
unsigned size = elements.size();
for (unsigned i = 0; i < size; ++i) {
Element* idElement = elements[i];
builder.append(accessibleNameForNode(idElement));
for (Node* n = idElement->firstChild(); n; n = NodeTraversal::next(*n, idElement))
builder.append(accessibleNameForNode(n));
if (i != size - 1)
builder.append(' ');
}
return builder.toString();
}
void AXNodeObject::alterSliderValue(bool increase)
{
if (roleValue() != SliderRole)
return;
if (!getAttribute(stepAttr).isEmpty())
changeValueByStep(increase);
else
changeValueByPercent(increase ? 5 : -5);
}
String AXNodeObject::ariaAccessibilityDescription() const
{
String ariaLabeledBy = ariaLabeledByAttribute();
if (!ariaLabeledBy.isEmpty())
return ariaLabeledBy;
const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
return String();
}
void AXNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
{
elementsFromAttribute(elements, aria_labeledbyAttr);
if (!elements.size())
elementsFromAttribute(elements, aria_labelledbyAttr);
}
void AXNodeObject::changeValueByStep(bool increase)
{
float step = stepValueForRange();
float value = valueForRange();
value += increase ? step : -step;
setValue(String::number(value));
axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
}
bool AXNodeObject::computeAccessibilityIsIgnored() const
{
#ifndef NDEBUG
ASSERT(m_initialized);
#endif
if (isDescendantOfBarrenParent())
return true;
AXObject* controlObject = correspondingControlForLabelElement();
if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio())
return true;
return m_role == UnknownRole;
}
AccessibilityRole AXNodeObject::determineAccessibilityRole()
{
if (!node())
return UnknownRole;
m_ariaRole = determineAriaRoleAttribute();
AccessibilityRole ariaRole = ariaRoleAttribute();
if (ariaRole != UnknownRole)
return ariaRole;
if (node()->isLink())
return LinkRole;
if (node()->isTextNode())
return StaticTextRole;
if (isHTMLButtonElement(*node()))
return buttonRoleType();
if (isHTMLInputElement(*node())) {
HTMLInputElement& input = toHTMLInputElement(*node());
if (input.isCheckbox())
return CheckBoxRole;
if (input.isRadioButton())
return RadioButtonRole;
if (input.isTextButton())
return buttonRoleType();
if (input.isRangeControl())
return SliderRole;
const AtomicString& type = input.getAttribute(typeAttr);
if (equalIgnoringCase(type, "color"))
return ColorWellRole;
return TextFieldRole;
}
if (isHTMLSelectElement(*node())) {
HTMLSelectElement& selectElement = toHTMLSelectElement(*node());
return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
}
if (isHTMLTextAreaElement(*node()))
return TextAreaRole;
if (headingLevel())
return HeadingRole;
if (isHTMLDivElement(*node()))
return DivRole;
if (isHTMLParagraphElement(*node()))
return ParagraphRole;
if (isHTMLLabelElement(*node()))
return LabelRole;
if (node()->isElementNode() && toElement(node())->isFocusable())
return GroupRole;
if (isHTMLAnchorElement(*node()) && isClickable())
return LinkRole;
if (node()->hasTagName(iframeTag))
return IframeRole;
return UnknownRole;
}
AccessibilityRole AXNodeObject::determineAriaRoleAttribute() const
{
const AtomicString& ariaRole = getAttribute(roleAttr);
if (ariaRole.isNull() || ariaRole.isEmpty())
return UnknownRole;
AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
if (role == PresentationalRole && canSetFocusAttribute())
return UnknownRole;
if (role == ButtonRole)
role = buttonRoleType();
if (role == TextAreaRole && !ariaIsMultiline())
role = TextFieldRole;
role = remapAriaRoleDueToParent(role);
if (role)
return role;
return UnknownRole;
}
void AXNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
{
Node* node = this->node();
if (!node || !node->isElementNode())
return;
TreeScope& scope = node->treeScope();
String idList = getAttribute(attribute).string();
if (idList.isEmpty())
return;
idList.replace('\n', ' ');
Vector<String> idVector;
idList.split(' ', idVector);
unsigned size = idVector.size();
for (unsigned i = 0; i < size; ++i) {
AtomicString idName(idVector[i]);
Element* idElement = scope.getElementById(idName);
if (idElement)
elements.append(idElement);
}
}
bool AXNodeObject::hasContentEditableAttributeSet() const
{
if (!hasAttribute(contenteditableAttr))
return false;
const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
}
bool AXNodeObject::isDescendantOfBarrenParent() const
{
for (AXObject* object = parentObject(); object; object = object->parentObject()) {
if (!object->canHaveChildren())
return true;
}
return false;
}
bool AXNodeObject::isGenericFocusableElement() const
{
if (!canSetFocusAttribute())
return false;
if (isControl())
return false;
if (m_ariaRole != UnknownRole)
return false;
if (hasContentEditableAttributeSet())
return false;
if (roleValue() == WebAreaRole)
return false;
if (isHTMLBodyElement(node()))
return false;
if (roleValue() == SVGRootRole)
return false;
return true;
}
HTMLLabelElement* AXNodeObject::labelForElement(Element* element) const
{
if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
return 0;
const AtomicString& id = element->getIdAttribute();
if (!id.isEmpty()) {
if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
return label;
}
for (Element* parent = element->parentElement(); parent; parent = parent->parentElement()) {
if (isHTMLLabelElement(*parent))
return toHTMLLabelElement(parent);
}
return 0;
}
AXObject* AXNodeObject::menuButtonForMenu() const
{
Element* menuItem = menuItemElementForMenu();
if (menuItem) {
AXObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
if (menuItemAX && menuItemAX->isMenuButton())
return menuItemAX;
}
return 0;
}
static Element* siblingWithAriaRole(String role, Node* node)
{
Node* parent = node->parentNode();
if (!parent)
return 0;
for (Element* sibling = ElementTraversal::firstChild(*parent); sibling; sibling = ElementTraversal::nextSibling(*sibling)) {
const AtomicString& siblingAriaRole = sibling->getAttribute(roleAttr);
if (equalIgnoringCase(siblingAriaRole, role))
return sibling;
}
return 0;
}
Element* AXNodeObject::menuItemElementForMenu() const
{
if (ariaRoleAttribute() != MenuRole)
return 0;
return siblingWithAriaRole("menuitem", node());
}
Element* AXNodeObject::mouseButtonListener() const
{
Node* node = this->node();
if (!node)
return 0;
while (node && !node->isElementNode())
node = node->parentNode();
if (!node)
return 0;
for (Element* element = toElement(node); element; element = element->parentElement()) {
if (element->getAttributeEventListener(EventTypeNames::click) || element->getAttributeEventListener(EventTypeNames::mousedown) || element->getAttributeEventListener(EventTypeNames::mouseup))
return element;
}
return 0;
}
AccessibilityRole AXNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
{
if (role != ListBoxOptionRole && role != MenuItemRole)
return role;
for (AXObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
return MenuItemRole;
if (role == MenuItemRole && parentAriaRole == GroupRole)
return MenuButtonRole;
if (parentAriaRole)
break;
}
return role;
}
void AXNodeObject::init()
{
#ifndef NDEBUG
ASSERT(!m_initialized);
m_initialized = true;
#endif
m_role = determineAccessibilityRole();
}
void AXNodeObject::detach()
{
clearChildren();
AXObject::detach();
m_node = 0;
}
bool AXNodeObject::isAnchor() const
{
return !isNativeImage() && isLink();
}
bool AXNodeObject::isControl() const
{
Node* node = this->node();
if (!node)
return false;
return ((node->isElementNode() && toElement(node)->isFormControlElement())
|| AXObject::isARIAControl(ariaRoleAttribute()));
}
bool AXNodeObject::isFieldset() const
{
return isHTMLFieldSetElement(node());
}
bool AXNodeObject::isHeading() const
{
return roleValue() == HeadingRole;
}
bool AXNodeObject::isHovered() const
{
Node* node = this->node();
if (!node)
return false;
return node->hovered();
}
bool AXNodeObject::isImage() const
{
return roleValue() == ImageRole;
}
bool AXNodeObject::isImageButton() const
{
return isNativeImage() && isButton();
}
bool AXNodeObject::isInputImage() const
{
Node* node = this->node();
if (roleValue() == ButtonRole && isHTMLInputElement(node))
return toHTMLInputElement(*node).isImageButton();
return false;
}
bool AXNodeObject::isLink() const
{
return roleValue() == LinkRole;
}
bool AXNodeObject::isMenu() const
{
return roleValue() == MenuRole;
}
bool AXNodeObject::isMenuButton() const
{
return roleValue() == MenuButtonRole;
}
bool AXNodeObject::isMultiSelectable() const
{
const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
if (equalIgnoringCase(ariaMultiSelectable, "true"))
return true;
if (equalIgnoringCase(ariaMultiSelectable, "false"))
return false;
return isHTMLSelectElement(node()) && toHTMLSelectElement(*node()).multiple();
}
bool AXNodeObject::isNativeCheckboxOrRadio() const
{
Node* node = this->node();
if (!isHTMLInputElement(node))
return false;
HTMLInputElement* input = toHTMLInputElement(node);
return input->isCheckbox() || input->isRadioButton();
}
bool AXNodeObject::isNativeImage() const
{
Node* node = this->node();
if (!node)
return false;
if (isHTMLImageElement(*node))
return true;
if (isHTMLAppletElement(*node) || isHTMLEmbedElement(*node) || isHTMLObjectElement(*node))
return true;
if (isHTMLInputElement(*node))
return toHTMLInputElement(*node).isImageButton();
return false;
}
bool AXNodeObject::isNativeTextControl() const
{
Node* node = this->node();
if (!node)
return false;
if (isHTMLTextAreaElement(*node))
return true;
if (isHTMLInputElement(*node)) {
HTMLInputElement* input = toHTMLInputElement(node);
return input->isText() || input->isNumberField();
}
return false;
}
bool AXNodeObject::isNonNativeTextControl() const
{
if (isNativeTextControl())
return false;
if (hasContentEditableAttributeSet())
return true;
if (isARIATextControl())
return true;
return false;
}
bool AXNodeObject::isPasswordField() const
{
Node* node = this->node();
if (!isHTMLInputElement(node))
return false;
if (ariaRoleAttribute() != UnknownRole)
return false;
return toHTMLInputElement(node)->isPasswordField();
}
bool AXNodeObject::isProgressIndicator() const
{
return roleValue() == ProgressIndicatorRole;
}
bool AXNodeObject::isSlider() const
{
return roleValue() == SliderRole;
}
bool AXNodeObject::isChecked() const
{
Node* node = this->node();
if (!node)
return false;
if (isHTMLInputElement(*node))
return toHTMLInputElement(*node).shouldAppearChecked();
AccessibilityRole ariaRole = ariaRoleAttribute();
if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
return true;
return false;
}
return false;
}
bool AXNodeObject::isClickable() const
{
if (node()) {
if (node()->isElementNode() && toElement(node())->isDisabledFormControl())
return false;
if (node()->hasEventListeners(EventTypeNames::mouseup) || node()->hasEventListeners(EventTypeNames::mousedown) || node()->hasEventListeners(EventTypeNames::click) || node()->hasEventListeners(EventTypeNames::DOMActivate))
return true;
}
return AXObject::isClickable();
}
bool AXNodeObject::isEnabled() const
{
if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true"))
return false;
Node* node = this->node();
if (!node || !node->isElementNode())
return true;
return !toElement(node)->isDisabledFormControl();
}
bool AXNodeObject::isIndeterminate() const
{
Node* node = this->node();
if (!isHTMLInputElement(node))
return false;
return toHTMLInputElement(node)->shouldAppearIndeterminate();
}
bool AXNodeObject::isPressed() const
{
if (!isButton())
return false;
Node* node = this->node();
if (!node)
return false;
if (ariaRoleAttribute() == ButtonRole) {
if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
return true;
return false;
}
return node->active();
}
bool AXNodeObject::isReadOnly() const
{
Node* node = this->node();
if (!node)
return true;
if (isHTMLTextAreaElement(*node))
return toHTMLTextAreaElement(*node).isReadOnly();
if (isHTMLInputElement(*node)) {
HTMLInputElement& input = toHTMLInputElement(*node);
if (input.isTextField())
return input.isReadOnly();
}
return !node->rendererIsEditable();
}
bool AXNodeObject::isRequired() const
{
if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
return true;
Node* n = this->node();
if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
return toHTMLFormControlElement(n)->isRequired();
return false;
}
bool AXNodeObject::canSetFocusAttribute() const
{
Node* node = this->node();
if (!node)
return false;
if (isWebArea())
return true;
if (!node)
return false;
if (isDisabledFormControl(node))
return false;
return node->isElementNode() && toElement(node)->supportsFocus();
}
bool AXNodeObject::canSetValueAttribute() const
{
if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true"))
return false;
if (isProgressIndicator() || isSlider())
return true;
if (isTextControl() && !isNativeTextControl())
return true;
return !isReadOnly();
}
bool AXNodeObject::canvasHasFallbackContent() const
{
Node* node = this->node();
if (!isHTMLCanvasElement(node))
return false;
return ElementTraversal::firstChild(*node);
}
bool AXNodeObject::exposesTitleUIElement() const
{
if (!isControl())
return false;
if (accessibilityIsIgnored())
return true;
bool hasTextAlternative = (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).isEmpty());
if (isCheckboxOrRadio())
return hasTextAlternative;
if (hasTextAlternative)
return false;
return true;
}
int AXNodeObject::headingLevel() const
{
Node* node = this->node();
if (!node)
return false;
if (ariaRoleAttribute() == HeadingRole)
return getAttribute(aria_levelAttr).toInt();
if (node->hasTagName(h1Tag))
return 1;
if (node->hasTagName(h2Tag))
return 2;
if (node->hasTagName(h3Tag))
return 3;
if (node->hasTagName(h4Tag))
return 4;
if (node->hasTagName(h5Tag))
return 5;
if (node->hasTagName(h6Tag))
return 6;
return 0;
}
unsigned AXNodeObject::hierarchicalLevel() const
{
Node* node = this->node();
if (!node || !node->isElementNode())
return 0;
Element* element = toElement(node);
String ariaLevel = element->getAttribute(aria_levelAttr);
if (!ariaLevel.isEmpty())
return ariaLevel.toInt();
if (roleValue() != TreeItemRole)
return 0;
unsigned level = 1;
for (AXObject* parent = parentObject(); parent; parent = parent->parentObject()) {
AccessibilityRole parentRole = parent->roleValue();
if (parentRole == GroupRole)
level++;
else if (parentRole == TreeRole)
break;
}
return level;
}
String AXNodeObject::text() const
{
if (ariaRoleAttribute() == StaticTextRole)
return ariaAccessibilityDescription();
if (!isTextControl())
return String();
Node* node = this->node();
if (!node)
return String();
if (isNativeTextControl() && (isHTMLTextAreaElement(*node) || isHTMLInputElement(*node)))
return toHTMLTextFormControlElement(*node).value();
if (!node->isElementNode())
return String();
return toElement(node)->innerText();
}
AXObject* AXNodeObject::titleUIElement() const
{
if (!node() || !node()->isElementNode())
return 0;
if (isFieldset())
return axObjectCache()->getOrCreate(toHTMLFieldSetElement(node())->legend());
HTMLLabelElement* label = labelForElement(toElement(node()));
if (label)
return axObjectCache()->getOrCreate(label);
return 0;
}
AccessibilityButtonState AXNodeObject::checkboxOrRadioValue() const
{
if (isNativeCheckboxOrRadio())
return isChecked() ? ButtonStateOn : ButtonStateOff;
return AXObject::checkboxOrRadioValue();
}
void AXNodeObject::colorValue(int& r, int& g, int& b) const
{
r = 0;
g = 0;
b = 0;
if (!isColorWell())
return;
if (!isHTMLInputElement(node()))
return;
HTMLInputElement* input = toHTMLInputElement(node());
const AtomicString& type = input->getAttribute(typeAttr);
if (!equalIgnoringCase(type, "color"))
return;
Color color;
bool success = color.setFromString(input->value());
ASSERT_UNUSED(success, success);
r = color.red();
g = color.green();
b = color.blue();
}
String AXNodeObject::valueDescription() const
{
if (!supportsRangeValue())
return String();
return getAttribute(aria_valuetextAttr).string();
}
float AXNodeObject::valueForRange() const
{
if (hasAttribute(aria_valuenowAttr))
return getAttribute(aria_valuenowAttr).toFloat();
if (isHTMLInputElement(node())) {
HTMLInputElement& input = toHTMLInputElement(*node());
if (input.isRangeControl())
return input.valueAsNumber();
}
return 0.0;
}
float AXNodeObject::maxValueForRange() const
{
if (hasAttribute(aria_valuemaxAttr))
return getAttribute(aria_valuemaxAttr).toFloat();
if (isHTMLInputElement(node())) {
HTMLInputElement& input = toHTMLInputElement(*node());
if (input.isRangeControl())
return input.maximum();
}
return 0.0;
}
float AXNodeObject::minValueForRange() const
{
if (hasAttribute(aria_valueminAttr))
return getAttribute(aria_valueminAttr).toFloat();
if (isHTMLInputElement(node())) {
HTMLInputElement& input = toHTMLInputElement(*node());
if (input.isRangeControl())
return input.minimum();
}
return 0.0;
}
float AXNodeObject::stepValueForRange() const
{
return getAttribute(stepAttr).toFloat();
}
String AXNodeObject::stringValue() const
{
Node* node = this->node();
if (!node)
return String();
if (ariaRoleAttribute() == StaticTextRole) {
String staticText = text();
if (!staticText.length())
staticText = textUnderElement();
return staticText;
}
if (node->isTextNode())
return textUnderElement();
if (isHTMLSelectElement(*node)) {
HTMLSelectElement& selectElement = toHTMLSelectElement(*node);
int selectedIndex = selectElement.selectedIndex();
const Vector<HTMLElement*> listItems = selectElement.listItems();
if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
if (!overriddenDescription.isNull())
return overriddenDescription;
}
if (!selectElement.multiple())
return selectElement.value();
return String();
}
if (isTextControl())
return text();
return String();
}
String AXNodeObject::ariaDescribedByAttribute() const
{
Vector<Element*> elements;
elementsFromAttribute(elements, aria_describedbyAttr);
return accessibilityDescriptionForElements(elements);
}
String AXNodeObject::ariaLabeledByAttribute() const
{
Vector<Element*> elements;
ariaLabeledByElements(elements);
return accessibilityDescriptionForElements(elements);
}
AccessibilityRole AXNodeObject::ariaRoleAttribute() const
{
return m_ariaRole;
}
static bool shouldUseAccessiblityObjectInnerText(AXObject* obj)
{
if (obj->isInertOrAriaHidden())
return false;
if (obj->canSetFocusAttribute())
return false;
if (obj->isList() || obj->isAXTable() || obj->isTree() || obj->isCanvas())
return false;
return true;
}
String AXNodeObject::textUnderElement() const
{
Node* node = this->node();
if (node && node->isTextNode())
return toText(node)->wholeText();
StringBuilder builder;
for (AXObject* child = firstChild(); child; child = child->nextSibling()) {
if (!shouldUseAccessiblityObjectInnerText(child))
continue;
if (child->isAXNodeObject()) {
Vector<AccessibilityText> textOrder;
toAXNodeObject(child)->alternativeText(textOrder);
if (textOrder.size() > 0) {
builder.append(textOrder[0].text);
continue;
}
}
builder.append(child->textUnderElement());
}
return builder.toString();
}
String AXNodeObject::accessibilityDescription() const
{
if (roleValue() == StaticTextRole)
return String();
String ariaDescription = ariaAccessibilityDescription();
if (!ariaDescription.isEmpty())
return ariaDescription;
if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
const AtomicString& alt = getAttribute(altAttr);
if (!alt.isNull())
return alt;
}
if (title().isEmpty())
return getAttribute(titleAttr);
return String();
}
String AXNodeObject::title() const
{
Node* node = this->node();
if (!node)
return String();
bool isInputElement = isHTMLInputElement(*node);
if (isInputElement) {
HTMLInputElement& input = toHTMLInputElement(*node);
if (input.isTextButton())
return input.valueWithDefault();
}
if (isInputElement || AXObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
HTMLLabelElement* label = labelForElement(toElement(node));
if (label && !exposesTitleUIElement())
return label->innerText();
}
if (!isAXRenderObject() && isHTMLSelectElement(*node))
return String();
switch (roleValue()) {
case PopUpButtonRole:
if (isHTMLSelectElement(*node))
return String();
case ButtonRole:
case ToggleButtonRole:
case CheckBoxRole:
case ListBoxOptionRole:
case MenuButtonRole:
case MenuItemRole:
case RadioButtonRole:
case TabRole:
return textUnderElement();
case SVGRootRole:
return String();
default:
break;
}
if (isHeading() || isLink())
return textUnderElement();
if (isGenericFocusableElement())
return textUnderElement();
return String();
}
String AXNodeObject::helpText() const
{
Node* node = this->node();
if (!node)
return String();
const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
if (!ariaHelp.isEmpty())
return ariaHelp;
String describedBy = ariaDescribedByAttribute();
if (!describedBy.isEmpty())
return describedBy;
String description = accessibilityDescription();
for (Node* curr = node; curr; curr = curr->parentNode()) {
if (curr->isHTMLElement()) {
const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr);
if (!summary.isEmpty())
return summary;
const AtomicString& title = toElement(curr)->getAttribute(titleAttr);
if (!title.isEmpty() && description != title)
return title;
}
AXObject* axObj = axObjectCache()->getOrCreate(curr);
if (axObj) {
AccessibilityRole role = axObj->roleValue();
if (role != GroupRole && role != UnknownRole)
break;
}
}
return String();
}
LayoutRect AXNodeObject::elementRect() const
{
if (!m_explicitElementRect.isEmpty())
return m_explicitElementRect;
LayoutRect boundingBox;
for (AXObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
if (positionProvider->isAXRenderObject()) {
LayoutRect parentRect = positionProvider->elementRect();
boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
boundingBox.setLocation(parentRect.location());
break;
}
}
return boundingBox;
}
AXObject* AXNodeObject::parentObject() const
{
if (!node())
return 0;
Node* parentObj = node()->parentNode();
if (parentObj)
return axObjectCache()->getOrCreate(parentObj);
return 0;
}
AXObject* AXNodeObject::parentObjectIfExists() const
{
return parentObject();
}
AXObject* AXNodeObject::firstChild() const
{
if (!node())
return 0;
Node* firstChild = node()->firstChild();
if (!firstChild)
return 0;
return axObjectCache()->getOrCreate(firstChild);
}
AXObject* AXNodeObject::nextSibling() const
{
if (!node())
return 0;
Node* nextSibling = node()->nextSibling();
if (!nextSibling)
return 0;
return axObjectCache()->getOrCreate(nextSibling);
}
void AXNodeObject::addChildren()
{
ASSERT(!m_haveChildren);
if (!m_node)
return;
m_haveChildren = true;
if (renderer() && !isHTMLCanvasElement(*m_node))
return;
for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
addChild(axObjectCache()->getOrCreate(child));
}
void AXNodeObject::addChild(AXObject* child)
{
insertChild(child, m_children.size());
}
void AXNodeObject::insertChild(AXObject* child, unsigned index)
{
if (!child)
return;
child->clearChildren();
if (child->accessibilityIsIgnored()) {
AccessibilityChildrenVector children = child->children();
size_t length = children.size();
for (size_t i = 0; i < length; ++i)
m_children.insert(index + i, children[i]);
} else {
ASSERT(child->parentObject() == this);
m_children.insert(index, child);
}
}
bool AXNodeObject::canHaveChildren() const
{
if (!node() && !isAXRenderObject())
return false;
switch (roleValue()) {
case ImageRole:
case ButtonRole:
case PopUpButtonRole:
case CheckBoxRole:
case RadioButtonRole:
case TabRole:
case ToggleButtonRole:
case ListBoxOptionRole:
case ScrollBarRole:
return false;
case StaticTextRole:
if (!axObjectCache()->inlineTextBoxAccessibility())
return false;
default:
return true;
}
}
Element* AXNodeObject::actionElement() const
{
Node* node = this->node();
if (!node)
return 0;
if (isHTMLInputElement(*node)) {
HTMLInputElement& input = toHTMLInputElement(*node);
if (!input.isDisabledFormControl() && (isCheckboxOrRadio() || input.isTextButton()))
return &input;
} else if (isHTMLButtonElement(*node)) {
return toElement(node);
}
if (isFileUploadButton())
return toElement(node);
if (AXObject::isARIAInput(ariaRoleAttribute()))
return toElement(node);
if (isImageButton())
return toElement(node);
if (isHTMLSelectElement(*node))
return toElement(node);
switch (roleValue()) {
case ButtonRole:
case PopUpButtonRole:
case ToggleButtonRole:
case TabRole:
case MenuItemRole:
case ListItemRole:
return toElement(node);
default:
break;
}
Element* elt = anchorElement();
if (!elt)
elt = mouseButtonListener();
return elt;
}
Element* AXNodeObject::anchorElement() const
{
Node* node = this->node();
if (!node)
return 0;
AXObjectCache* cache = axObjectCache();
for ( ; node; node = node->parentNode()) {
if (isHTMLAnchorElement(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
return toElement(node);
}
return 0;
}
Document* AXNodeObject::document() const
{
if (!node())
return 0;
return &node()->document();
}
void AXNodeObject::setNode(Node* node)
{
m_node = node;
}
AXObject* AXNodeObject::correspondingControlForLabelElement() const
{
HTMLLabelElement* labelElement = labelElementContainer();
if (!labelElement)
return 0;
HTMLElement* correspondingControl = labelElement->control();
if (!correspondingControl)
return 0;
if (correspondingControl->renderer() && !correspondingControl->renderer()->parent())
return 0;
return axObjectCache()->getOrCreate(correspondingControl);
}
HTMLLabelElement* AXNodeObject::labelElementContainer() const
{
if (!node())
return 0;
if (isControl())
return 0;
return Traversal<HTMLLabelElement>::firstAncestorOrSelf(*node());
}
void AXNodeObject::setFocused(bool on)
{
if (!canSetFocusAttribute())
return;
Document* document = this->document();
if (!on) {
document->setFocusedElement(nullptr);
} else {
Node* node = this->node();
if (node && node->isElementNode()) {
if (document->focusedElement() == node)
document->setFocusedElement(nullptr);
toElement(node)->focus();
} else {
document->setFocusedElement(nullptr);
}
}
}
void AXNodeObject::increment()
{
UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
alterSliderValue(true);
}
void AXNodeObject::decrement()
{
UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
alterSliderValue(false);
}
void AXNodeObject::childrenChanged()
{
if (!node() && !renderer())
return;
axObjectCache()->postNotification(this, document(), AXObjectCache::AXChildrenChanged, true);
for (AXObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
parent->setNeedsToUpdateChildren();
if (parent->supportsARIALiveRegion())
axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged, true);
if (isNonNativeTextControl())
axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged, true);
}
}
void AXNodeObject::selectionChanged()
{
if (isFocused() || isWebArea())
axObjectCache()->postNotification(this, document(), AXObjectCache::AXSelectedTextChanged, true);
else
AXObject::selectionChanged();
}
void AXNodeObject::textChanged()
{
AXObjectCache* cache = axObjectCache();
for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) {
AXObject* parent = cache->get(parentNode);
if (!parent)
continue;
if (parent->supportsARIALiveRegion())
cache->postNotification(parentNode, AXObjectCache::AXLiveRegionChanged, true);
if (parent->isNonNativeTextControl())
cache->postNotification(parentNode, AXObjectCache::AXValueChanged, true);
}
}
void AXNodeObject::updateAccessibilityRole()
{
bool ignoredStatus = accessibilityIsIgnored();
m_role = determineAccessibilityRole();
if (ignoredStatus != accessibilityIsIgnored())
childrenChanged();
}
String AXNodeObject::alternativeTextForWebArea() const
{
Document* document = this->document();
if (!document)
return String();
if (Element* documentElement = document->documentElement()) {
const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
}
Node* owner = document->ownerElement();
if (owner) {
if (isHTMLFrameElementBase(*owner)) {
const AtomicString& title = toElement(owner)->getAttribute(titleAttr);
if (!title.isEmpty())
return title;
return toElement(owner)->getNameAttribute();
}
if (owner->isHTMLElement())
return toHTMLElement(owner)->getNameAttribute();
}
String documentTitle = document->title();
if (!documentTitle.isEmpty())
return documentTitle;
owner = document->body();
if (owner && owner->isHTMLElement())
return toHTMLElement(owner)->getNameAttribute();
return String();
}
void AXNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
{
if (isWebArea()) {
String webAreaText = alternativeTextForWebArea();
if (!webAreaText.isEmpty())
textOrder.append(AccessibilityText(webAreaText, AlternativeText));
return;
}
ariaLabeledByText(textOrder);
const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
if (!ariaLabel.isEmpty())
textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
const AtomicString& alt = getAttribute(altAttr);
if (!alt.isNull())
textOrder.append(AccessibilityText(alt, AlternativeText));
}
}
void AXNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
{
String ariaLabeledBy = ariaLabeledByAttribute();
if (!ariaLabeledBy.isEmpty()) {
Vector<Element*> elements;
ariaLabeledByElements(elements);
unsigned length = elements.size();
for (unsigned k = 0; k < length; k++) {
RefPtr<AXObject> axElement = axObjectCache()->getOrCreate(elements[k]);
textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElement));
}
}
}
void AXNodeObject::changeValueByPercent(float percentChange)
{
float range = maxValueForRange() - minValueForRange();
float value = valueForRange();
value += range * (percentChange / 100);
setValue(String::number(value));
axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
}
}