AccessibilityNodeObject.cpp [plain text]
#include "config.h"
#include "AccessibilityNodeObject.h"
#include "AXObjectCache.h"
#include "AccessibilityImageMapLink.h"
#include "AccessibilityListBox.h"
#include "AccessibilitySpinButton.h"
#include "AccessibilityTable.h"
#include "EventNames.h"
#include "FloatRect.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameSelection.h"
#include "FrameView.h"
#include "HTMLAreaElement.h"
#include "HTMLFieldSetElement.h"
#include "HTMLFormElement.h"
#include "HTMLFrameElementBase.h"
#include "HTMLImageElement.h"
#include "HTMLInputElement.h"
#include "HTMLLabelElement.h"
#include "HTMLLegendElement.h"
#include "HTMLMapElement.h"
#include "HTMLNames.h"
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLOptionsCollection.h"
#include "HTMLPlugInImageElement.h"
#include "HTMLSelectElement.h"
#include "HTMLTextAreaElement.h"
#include "HTMLTextFormControlElement.h"
#include "HitTestRequest.h"
#include "HitTestResult.h"
#include "LabelableElement.h"
#include "LocalizedStrings.h"
#include "MathMLNames.h"
#include "NodeList.h"
#include "NodeTraversal.h"
#include "Page.h"
#include "ProgressTracker.h"
#include "SVGElement.h"
#include "SVGNames.h"
#include "SVGStyledElement.h"
#include "Text.h"
#include "TextControlInnerElements.h"
#include "TextIterator.h"
#include "UserGestureIndicator.h"
#include "VisibleUnits.h"
#include "Widget.h"
#include "htmlediting.h"
#include <wtf/StdLibExtras.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/unicode/CharacterNames.h>
using namespace std;
namespace WebCore {
using namespace HTMLNames;
AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
: AccessibilityObject()
, m_ariaRole(UnknownRole)
, m_childrenDirty(false)
, m_roleForMSAA(UnknownRole)
#ifndef NDEBUG
, m_initialized(false)
#endif
, m_node(node)
{
}
AccessibilityNodeObject::~AccessibilityNodeObject()
{
ASSERT(isDetached());
}
void AccessibilityNodeObject::init()
{
#ifndef NDEBUG
ASSERT(!m_initialized);
m_initialized = true;
#endif
m_role = determineAccessibilityRole();
}
PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
{
return adoptRef(new AccessibilityNodeObject(node));
}
void AccessibilityNodeObject::detach()
{
clearChildren();
AccessibilityObject::detach();
m_node = 0;
}
void AccessibilityNodeObject::childrenChanged()
{
if (!node() && !renderer())
return;
axObjectCache()->postNotification(this, document(), AXObjectCache::AXChildrenChanged, true);
for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
parent->setNeedsToUpdateChildren();
if (parent->supportsARIALiveRegion())
axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged, true);
if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->rendererIsEditable())
axObjectCache()->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged, true);
}
}
void AccessibilityNodeObject::updateAccessibilityRole()
{
bool ignoredStatus = accessibilityIsIgnored();
m_role = determineAccessibilityRole();
if (ignoredStatus != accessibilityIsIgnored())
childrenChanged();
}
AccessibilityObject* AccessibilityNodeObject::firstChild() const
{
if (!node())
return 0;
Node* firstChild = node()->firstChild();
if (!firstChild)
return 0;
return axObjectCache()->getOrCreate(firstChild);
}
AccessibilityObject* AccessibilityNodeObject::lastChild() const
{
if (!node())
return 0;
Node* lastChild = node()->lastChild();
if (!lastChild)
return 0;
return axObjectCache()->getOrCreate(lastChild);
}
AccessibilityObject* AccessibilityNodeObject::previousSibling() const
{
if (!node())
return 0;
Node* previousSibling = node()->previousSibling();
if (!previousSibling)
return 0;
return axObjectCache()->getOrCreate(previousSibling);
}
AccessibilityObject* AccessibilityNodeObject::nextSibling() const
{
if (!node())
return 0;
Node* nextSibling = node()->nextSibling();
if (!nextSibling)
return 0;
return axObjectCache()->getOrCreate(nextSibling);
}
AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
{
return parentObject();
}
AccessibilityObject* AccessibilityNodeObject::parentObject() const
{
if (!node())
return 0;
Node* parentObj = node()->parentNode();
if (parentObj)
return axObjectCache()->getOrCreate(parentObj);
return 0;
}
LayoutRect AccessibilityNodeObject::elementRect() const
{
return boundingBoxRect();
}
LayoutRect AccessibilityNodeObject::boundingBoxRect() const
{
LayoutRect boundingBox;
for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
if (positionProvider->isAccessibilityRenderObject()) {
LayoutRect parentRect = positionProvider->elementRect();
boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
boundingBox.setLocation(parentRect.location());
break;
}
}
return boundingBox;
}
void AccessibilityNodeObject::setNode(Node* node)
{
m_node = node;
}
Document* AccessibilityNodeObject::document() const
{
if (!node())
return 0;
return node()->document();
}
AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
{
if (!node())
return UnknownRole;
m_ariaRole = determineAriaRoleAttribute();
AccessibilityRole ariaRole = ariaRoleAttribute();
if (ariaRole != UnknownRole)
return ariaRole;
if (node()->isLink())
return WebCoreLinkRole;
if (node()->isTextNode())
return StaticTextRole;
if (node()->hasTagName(buttonTag))
return buttonRoleType();
if (node()->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (input->isCheckbox())
return CheckBoxRole;
if (input->isRadioButton())
return RadioButtonRole;
if (input->isTextButton())
return buttonRoleType();
if (input->isRangeControl())
return SliderRole;
#if ENABLE(INPUT_TYPE_COLOR)
const AtomicString& type = input->getAttribute(typeAttr);
if (equalIgnoringCase(type, "color"))
return ColorWellRole;
#endif
return TextFieldRole;
}
if (node()->hasTagName(selectTag)) {
HTMLSelectElement* selectElement = toHTMLSelectElement(node());
return selectElement->multiple() ? ListBoxRole : PopUpButtonRole;
}
if (node()->hasTagName(textareaTag))
return TextAreaRole;
if (headingLevel())
return HeadingRole;
if (node()->hasTagName(divTag))
return DivRole;
if (node()->hasTagName(pTag))
return ParagraphRole;
if (node()->hasTagName(labelTag))
return LabelRole;
if (node()->isElementNode() && toElement(node())->isFocusable())
return GroupRole;
return UnknownRole;
}
void AccessibilityNodeObject::insertChild(AccessibilityObject* 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);
}
}
void AccessibilityNodeObject::addChild(AccessibilityObject* child)
{
insertChild(child, m_children.size());
}
void AccessibilityNodeObject::addChildren()
{
ASSERT(!m_haveChildren);
if (!m_node)
return;
m_haveChildren = true;
if (renderer() && !m_node->hasTagName(canvasTag))
return;
for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
addChild(axObjectCache()->getOrCreate(child));
}
bool AccessibilityNodeObject::canHaveChildren() const
{
if (!node() && !isAccessibilityRenderObject())
return false;
switch (roleValue()) {
case ImageRole:
case ButtonRole:
case PopUpButtonRole:
case CheckBoxRole:
case RadioButtonRole:
case TabRole:
case ToggleButtonRole:
case StaticTextRole:
case ListBoxOptionRole:
case ScrollBarRole:
case ProgressIndicatorRole:
return false;
default:
return true;
}
}
bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
{
#ifndef NDEBUG
ASSERT(m_initialized);
#endif
if (isDescendantOfBarrenParent())
return true;
return m_role == UnknownRole;
}
bool AccessibilityNodeObject::canvasHasFallbackContent() const
{
Node* node = this->node();
if (!node || !node->hasTagName(canvasTag))
return false;
for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
if (child->isElementNode())
return true;
}
return false;
}
bool AccessibilityNodeObject::isImageButton() const
{
return isNativeImage() && isButton();
}
bool AccessibilityNodeObject::isAnchor() const
{
return !isNativeImage() && isLink();
}
bool AccessibilityNodeObject::isNativeTextControl() const
{
Node* node = this->node();
if (!node)
return false;
if (node->hasTagName(textareaTag))
return true;
if (node->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
return input->isText() || input->isNumberField();
}
return false;
}
bool AccessibilityNodeObject::isSearchField() const
{
Node* node = this->node();
if (!node)
return false;
HTMLInputElement* inputElement = node->toInputElement();
if (!inputElement)
return false;
if (inputElement->isSearchField())
return true;
const AtomicString& nameAttribute = getAttribute(nameAttr);
if (nameAttribute.contains("search", false))
return true;
HTMLFormElement* form = inputElement->form();
if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
return true;
return false;
}
bool AccessibilityNodeObject::isNativeImage() const
{
Node* node = this->node();
if (!node)
return false;
if (node->hasTagName(imgTag))
return true;
if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
return true;
if (node->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
return input->isImageButton();
}
return false;
}
bool AccessibilityNodeObject::isImage() const
{
return roleValue() == ImageRole;
}
bool AccessibilityNodeObject::isPasswordField() const
{
Node* node = this->node();
if (!node || !node->isHTMLElement())
return false;
if (ariaRoleAttribute() != UnknownRole)
return false;
HTMLInputElement* inputElement = node->toInputElement();
if (!inputElement)
return false;
return inputElement->isPasswordField();
}
bool AccessibilityNodeObject::isInputImage() const
{
Node* node = this->node();
if (!node)
return false;
if (roleValue() == ButtonRole && node->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
return input->isImageButton();
}
return false;
}
bool AccessibilityNodeObject::isProgressIndicator() const
{
return roleValue() == ProgressIndicatorRole;
}
bool AccessibilityNodeObject::isSlider() const
{
return roleValue() == SliderRole;
}
bool AccessibilityNodeObject::isMenuRelated() const
{
switch (roleValue()) {
case MenuRole:
case MenuBarRole:
case MenuButtonRole:
case MenuItemRole:
return true;
default:
return false;
}
}
bool AccessibilityNodeObject::isMenu() const
{
return roleValue() == MenuRole;
}
bool AccessibilityNodeObject::isMenuBar() const
{
return roleValue() == MenuBarRole;
}
bool AccessibilityNodeObject::isMenuButton() const
{
return roleValue() == MenuButtonRole;
}
bool AccessibilityNodeObject::isMenuItem() const
{
return roleValue() == MenuItemRole;
}
bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
{
Node* node = this->node();
if (!node)
return false;
HTMLInputElement* input = node->toInputElement();
if (input)
return input->isCheckbox() || input->isRadioButton();
return false;
}
bool AccessibilityNodeObject::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 AccessibilityNodeObject::isIndeterminate() const
{
Node* node = this->node();
if (!node)
return false;
HTMLInputElement* inputElement = node->toInputElement();
if (!inputElement)
return false;
return inputElement->shouldAppearIndeterminate();
}
bool AccessibilityNodeObject::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;
}
if (!node->isElementNode())
return false;
return toElement(node)->active();
}
bool AccessibilityNodeObject::isChecked() const
{
Node* node = this->node();
if (!node)
return false;
HTMLInputElement* inputElement = node->toInputElement();
if (inputElement)
return inputElement->shouldAppearChecked();
bool validRole = false;
switch (ariaRoleAttribute()) {
case RadioButtonRole:
case CheckBoxRole:
case MenuItemRole:
validRole = true;
break;
default:
break;
}
if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
return true;
return false;
}
bool AccessibilityNodeObject::isHovered() const
{
Node* node = this->node();
if (!node)
return false;
return node->isElementNode() && toElement(node)->hovered();
}
bool AccessibilityNodeObject::isMultiSelectable() const
{
const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
if (equalIgnoringCase(ariaMultiSelectable, "true"))
return true;
if (equalIgnoringCase(ariaMultiSelectable, "false"))
return false;
return node() && node()->hasTagName(selectTag) && toHTMLSelectElement(node())->multiple();
}
bool AccessibilityNodeObject::isReadOnly() const
{
Node* node = this->node();
if (!node)
return true;
if (node->hasTagName(textareaTag))
return static_cast<HTMLTextAreaElement*>(node)->isReadOnly();
if (node->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
if (input->isTextField())
return input->isReadOnly();
}
return !node->rendererIsEditable();
}
bool AccessibilityNodeObject::isRequired() const
{
if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
return true;
Node* n = this->node();
if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
return static_cast<HTMLFormControlElement*>(n)->isRequired();
return false;
}
bool AccessibilityNodeObject::supportsRequiredAttribute() const
{
switch (roleValue()) {
case CellRole:
case CheckBoxRole:
case ComboBoxRole:
case GridRole:
case IncrementorRole:
case ListBoxRole:
case PopUpButtonRole:
case RadioButtonRole:
case RadioGroupRole:
case RowHeaderRole:
case SliderRole:
case SpinButtonRole:
case TableHeaderContainerRole:
case TextAreaRole:
case TextFieldRole:
case ToggleButtonRole:
return true;
default:
return false;
}
}
int AccessibilityNodeObject::headingLevel() const
{
Node* node = this->node();
if (!node)
return false;
if (isHeading()) {
int ariaLevel = getAttribute(aria_levelAttr).toInt();
if (ariaLevel > 0)
return ariaLevel;
}
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;
}
String AccessibilityNodeObject::valueDescription() const
{
if (!isRangeControl())
return String();
return getAttribute(aria_valuetextAttr).string();
}
float AccessibilityNodeObject::valueForRange() const
{
if (node() && node()->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (input->isRangeControl())
return input->valueAsNumber();
}
if (!isRangeControl())
return 0.0f;
return getAttribute(aria_valuenowAttr).toFloat();
}
float AccessibilityNodeObject::maxValueForRange() const
{
if (node() && node()->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (input->isRangeControl())
return input->maximum();
}
if (!isRangeControl())
return 0.0f;
return getAttribute(aria_valuemaxAttr).toFloat();
}
float AccessibilityNodeObject::minValueForRange() const
{
if (node() && node()->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
if (input->isRangeControl())
return input->minimum();
}
if (!isRangeControl())
return 0.0f;
return getAttribute(aria_valueminAttr).toFloat();
}
float AccessibilityNodeObject::stepValueForRange() const
{
return getAttribute(stepAttr).toFloat();
}
bool AccessibilityNodeObject::isHeading() const
{
return roleValue() == HeadingRole;
}
bool AccessibilityNodeObject::isLink() const
{
return roleValue() == WebCoreLinkRole;
}
bool AccessibilityNodeObject::isControl() const
{
Node* node = this->node();
if (!node)
return false;
return ((node->isElementNode() && toElement(node)->isFormControlElement())
|| AccessibilityObject::isARIAControl(ariaRoleAttribute()));
}
bool AccessibilityNodeObject::isFieldset() const
{
Node* node = this->node();
if (!node)
return false;
return node->hasTagName(fieldsetTag);
}
bool AccessibilityNodeObject::isGroup() const
{
return roleValue() == GroupRole;
}
AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
{
if (!isRadioGroup())
return 0;
AccessibilityObject::AccessibilityChildrenVector children = this->children();
size_t size = children.size();
for (size_t i = 0; i < size; ++i) {
AccessibilityObject* object = children[i].get();
if (object->roleValue() == RadioButtonRole && object->checkboxOrRadioValue() == ButtonStateOn)
return object;
}
return 0;
}
AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
{
if (!isTabList())
return 0;
AccessibilityObject::AccessibilityChildrenVector tabs;
tabChildren(tabs);
AccessibilityObject::AccessibilityChildrenVector children = this->children();
size_t size = tabs.size();
for (size_t i = 0; i < size; ++i) {
AccessibilityObject* object = children[i].get();
if (object->isTabItem() && object->isChecked())
return object;
}
return 0;
}
AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
{
if (isNativeCheckboxOrRadio())
return isChecked() ? ButtonStateOn : ButtonStateOff;
return AccessibilityObject::checkboxOrRadioValue();
}
Element* AccessibilityNodeObject::anchorElement() const
{
Node* node = this->node();
if (!node)
return 0;
AXObjectCache* cache = axObjectCache();
for ( ; node; node = node->parentNode()) {
if (node->hasTagName(aTag) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
return toElement(node);
}
return 0;
}
Element* AccessibilityNodeObject::actionElement() const
{
Node* node = this->node();
if (!node)
return 0;
if (node->hasTagName(inputTag)) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
if (!input->isDisabledFormControl() && (isCheckboxOrRadio() || input->isTextButton()))
return input;
} else if (node->hasTagName(buttonTag))
return toElement(node);
if (isFileUploadButton())
return toElement(node);
if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
return toElement(node);
if (isImageButton())
return toElement(node);
if (node->hasTagName(selectTag))
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* AccessibilityNodeObject::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->hasTagName(bodyTag) && isStaticText())
break;
if (element->hasEventListeners(eventNames().clickEvent) || element->hasEventListeners(eventNames().mousedownEvent) || element->hasEventListeners(eventNames().mouseupEvent))
return element;
}
return 0;
}
bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
{
for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
if (!object->canHaveChildren())
return true;
}
return false;
}
void AccessibilityNodeObject::alterSliderValue(bool increase)
{
if (roleValue() != SliderRole)
return;
if (!getAttribute(stepAttr).isEmpty())
changeValueByStep(increase);
else
changeValueByPercent(increase ? 5 : -5);
}
void AccessibilityNodeObject::increment()
{
UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
alterSliderValue(true);
}
void AccessibilityNodeObject::decrement()
{
UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
alterSliderValue(false);
}
void AccessibilityNodeObject::changeValueByStep(bool increase)
{
float step = stepValueForRange();
float value = valueForRange();
value += increase ? step : -step;
setValue(String::number(value));
axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
}
void AccessibilityNodeObject::changeValueByPercent(float percentChange)
{
float range = maxValueForRange() - minValueForRange();
float value = valueForRange();
value += range * (percentChange / 100);
setValue(String::number(value));
axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged, true);
}
bool AccessibilityNodeObject::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 (node() && node()->hasTagName(bodyTag))
return false;
if (roleValue() == SVGRootRole)
return false;
return true;
}
HTMLLabelElement* AccessibilityNodeObject::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 (parent->hasTagName(labelTag))
return static_cast<HTMLLabelElement*>(parent);
}
return 0;
}
String AccessibilityNodeObject::ariaAccessibilityDescription() const
{
String ariaLabeledBy = ariaLabeledByAttribute();
if (!ariaLabeledBy.isEmpty())
return ariaLabeledBy;
const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
return String();
}
static Element* siblingWithAriaRole(String role, Node* node)
{
Node* parent = node->parentNode();
if (!parent)
return 0;
for (Node* sibling = parent->firstChild(); sibling; sibling = sibling->nextSibling()) {
if (sibling->isElementNode()) {
const AtomicString& siblingAriaRole = toElement(sibling)->getAttribute(roleAttr);
if (equalIgnoringCase(siblingAriaRole, role))
return toElement(sibling);
}
}
return 0;
}
Element* AccessibilityNodeObject::menuElementForMenuButton() const
{
if (ariaRoleAttribute() != MenuButtonRole)
return 0;
return siblingWithAriaRole("menu", node());
}
AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
{
return axObjectCache()->getOrCreate(menuElementForMenuButton());
}
Element* AccessibilityNodeObject::menuItemElementForMenu() const
{
if (ariaRoleAttribute() != MenuRole)
return 0;
return siblingWithAriaRole("menuitem", node());
}
AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
{
Element* menuItem = menuItemElementForMenu();
if (menuItem) {
AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
if (menuItemAX && menuItemAX->isMenuButton())
return menuItemAX;
}
return 0;
}
void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder)
{
Node* node = this->node();
if (!node)
return;
bool isInputTag = node->hasTagName(inputTag);
if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
HTMLLabelElement* label = labelForElement(toElement(node));
if (label) {
AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
textOrder.append(AccessibilityText(label->innerText(), LabelByElementText, labelObject));
return;
}
}
AccessibilityObject* titleUIElement = this->titleUIElement();
if (titleUIElement)
textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
}
void AccessibilityNodeObject::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));
}
Node* node = this->node();
if (!node)
return;
#if ENABLE(SVG)
if (node->isSVGElement() && toSVGElement(node)->isSVGStyledElement())
textOrder.append(AccessibilityText(toSVGStyledElement(node)->title(), AlternativeText));
#endif
#if ENABLE(MATHML)
if (node->isElementNode() && toElement(node)->isMathMLElement())
textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
#endif
}
void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
{
Node* node = this->node();
if (!node)
return;
bool isInputTag = node->hasTagName(inputTag);
if (isInputTag) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
if (input->isTextButton()) {
textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText));
return;
}
}
if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
return;
bool useTextUnderElement = false;
switch (roleValue()) {
case PopUpButtonRole:
if (node->hasTagName(selectTag))
break;
case ButtonRole:
case ToggleButtonRole:
case CheckBoxRole:
case ListBoxOptionRole:
case ListItemRole:
case MenuButtonRole:
case MenuItemRole:
case RadioButtonRole:
case TabRole:
case ProgressIndicatorRole:
useTextUnderElement = true;
break;
default:
break;
}
if (isHeading() || isLink() || isGenericFocusableElement())
useTextUnderElement = true;
if (useTextUnderElement) {
String text = textUnderElement();
if (!text.isEmpty())
textOrder.append(AccessibilityText(text, ChildrenText));
}
}
void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
{
const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
if (!ariaHelp.isEmpty())
textOrder.append(AccessibilityText(ariaHelp, HelpText));
String describedBy = ariaDescribedByAttribute();
if (!describedBy.isEmpty())
textOrder.append(AccessibilityText(describedBy, SummaryText));
for (Node* curr = node(); curr; curr = curr->parentNode()) {
const AtomicString& summary = getAttribute(summaryAttr);
if (!summary.isEmpty())
textOrder.append(AccessibilityText(summary, SummaryText));
const AtomicString& title = getAttribute(titleAttr);
if (!title.isEmpty())
textOrder.append(AccessibilityText(title, TitleTagText));
AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
if (!axObj)
return;
AccessibilityRole role = axObj->roleValue();
if (role != GroupRole && role != UnknownRole)
break;
}
}
void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
{
titleElementText(textOrder);
alternativeText(textOrder);
visibleText(textOrder);
helpText(textOrder);
String placeholder = placeholderValue();
if (!placeholder.isEmpty())
textOrder.append(AccessibilityText(placeholder, PlaceholderText));
}
void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
{
String ariaLabeledBy = ariaLabeledByAttribute();
if (!ariaLabeledBy.isEmpty()) {
Vector<Element*> elements;
ariaLabeledByElements(elements);
Vector<RefPtr<AccessibilityObject> > axElements;
unsigned length = elements.size();
for (unsigned k = 0; k < length; k++) {
RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(elements[k]);
axElements.append(axElement);
}
textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements));
}
}
String AccessibilityNodeObject::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 (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr);
if (!title.isEmpty())
return title;
return static_cast<HTMLFrameElementBase*>(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();
}
String AccessibilityNodeObject::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 ENABLE(SVG)
if (m_node && m_node->isSVGElement() && toSVGElement(m_node)->isSVGStyledElement())
return toSVGStyledElement(m_node)->title();
#endif
#if ENABLE(MATHML)
if (m_node && m_node->isElementNode() && toElement(m_node)->isMathMLElement())
return getAttribute(MathMLNames::alttextAttr);
#endif
if (title().isEmpty())
return getAttribute(titleAttr);
return String();
}
String AccessibilityNodeObject::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;
}
AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
if (axObj) {
AccessibilityRole role = axObj->roleValue();
if (role != GroupRole && role != UnknownRole)
break;
}
}
return String();
}
unsigned AccessibilityNodeObject::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 (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
AccessibilityRole parentRole = parent->roleValue();
if (parentRole == GroupRole)
level++;
else if (parentRole == TreeRole)
break;
}
return level;
}
static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj)
{
if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
return false;
if (obj->isDescendantOfBarrenParent())
return true;
if (obj->canSetFocusAttribute())
return false;
if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas())
return false;
return true;
}
String AccessibilityNodeObject::textUnderElement() const
{
Node* node = this->node();
if (node && node->isTextNode())
return toText(node)->wholeText();
StringBuilder builder;
for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
if (!shouldUseAccessiblityObjectInnerText(child))
continue;
if (child->isAccessibilityNodeObject()) {
Vector<AccessibilityText> textOrder;
toAccessibilityNodeObject(child)->alternativeText(textOrder);
if (textOrder.size() > 0 && textOrder[0].text.length()) {
if (builder.length())
builder.append(' ');
builder.append(textOrder[0].text);
continue;
}
}
String childText = child->textUnderElement();
if (childText.length()) {
if (builder.length())
builder.append(' ');
builder.append(childText);
}
}
return builder.toString().stripWhiteSpace().simplifyWhiteSpace();
}
String AccessibilityNodeObject::title() const
{
Node* node = this->node();
if (!node)
return String();
bool isInputTag = node->hasTagName(inputTag);
if (isInputTag) {
HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
if (input->isTextButton())
return input->valueWithDefault();
}
if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
HTMLLabelElement* label = labelForElement(toElement(node));
if (label && !exposesTitleUIElement())
return label->innerText();
}
if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
return String();
switch (roleValue()) {
case PopUpButtonRole:
if (node->hasTagName(selectTag))
return String();
case ButtonRole:
case ToggleButtonRole:
case CheckBoxRole:
case ListBoxOptionRole:
case ListItemRole:
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 AccessibilityNodeObject::text() const
{
if (ariaRoleAttribute() == StaticTextRole)
return ariaAccessibilityDescription();
if (!isTextControl())
return String();
Node* node = this->node();
if (!node)
return String();
if (isNativeTextControl()) {
if (node->hasTagName(textareaTag))
return static_cast<HTMLTextAreaElement*>(node)->value();
if (node->hasTagName(inputTag))
return node->toInputElement()->value();
}
if (!node->isElementNode())
return String();
return toElement(node)->innerText();
}
String AccessibilityNodeObject::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 (node->hasTagName(selectTag)) {
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();
}
void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
{
r = 0;
g = 0;
b = 0;
if (!isColorWell())
return;
if (!node() || !node()->hasTagName(inputTag))
return;
HTMLInputElement* input = static_cast<HTMLInputElement*>(node());
const AtomicString& type = input->getAttribute(typeAttr);
if (!equalIgnoringCase(type, "color"))
return;
Color color(input->value());
r = color.red();
g = color.green();
b = color.blue();
}
static String accessibleNameForNode(Node* node)
{
if (node->isTextNode())
return toText(node)->data();
if (node->hasTagName(inputTag))
return static_cast<HTMLInputElement*>(node)->value();
if (node->isHTMLElement()) {
const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
if (!alt.isEmpty())
return alt;
}
return String();
}
String AccessibilityNodeObject::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();
}
String AccessibilityNodeObject::ariaDescribedByAttribute() const
{
Vector<Element*> elements;
elementsFromAttribute(elements, aria_describedbyAttr);
return accessibilityDescriptionForElements(elements);
}
void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
{
Node* node = this->node();
if (!node || !node->isElementNode())
return;
TreeScope* scope = node->treeScope();
if (!scope)
return;
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);
}
}
void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
{
elementsFromAttribute(elements, aria_labeledbyAttr);
if (!elements.size())
elementsFromAttribute(elements, aria_labelledbyAttr);
}
String AccessibilityNodeObject::ariaLabeledByAttribute() const
{
Vector<Element*> elements;
ariaLabeledByElements(elements);
return accessibilityDescriptionForElements(elements);
}
bool AccessibilityNodeObject::canSetFocusAttribute() const
{
Node* node = this->node();
if (!node)
return false;
if (isWebArea())
return true;
if (!node)
return false;
if (!node->isElementNode())
return false;
Element* element = toElement(node);
if (element->isDisabledFormControl())
return false;
return element->supportsFocus();
}
AccessibilityRole AccessibilityNodeObject::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;
}
AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
{
return m_ariaRole;
}
AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
{
if (role != ListBoxOptionRole && role != MenuItemRole)
return role;
for (AccessibilityObject* 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;
}
bool AccessibilityNodeObject::hasContentEditableAttributeSet() const
{
if (!hasAttribute(contenteditableAttr))
return false;
const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
}
bool AccessibilityNodeObject::canSetSelectedAttribute() const
{
switch (roleValue()) {
case CellRole:
case RadioButtonRole:
case RowHeaderRole:
case RowRole:
case TabListRole:
case TabRole:
case TreeGridRole:
case TreeItemRole:
case TreeRole:
return isEnabled();
default:
return false;
}
}
}