AccessibilityNodeObject.cpp [plain text]
#include "config.h"
#include "AccessibilityNodeObject.h"
#include "AXObjectCache.h"
#include "AccessibilityImageMapLink.h"
#include "AccessibilityList.h"
#include "AccessibilityListBox.h"
#include "AccessibilitySpinButton.h"
#include "AccessibilityTable.h"
#include "Editing.h"
#include "ElementIterator.h"
#include "EventNames.h"
#include "FloatRect.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameSelection.h"
#include "FrameView.h"
#include "HTMLCanvasElement.h"
#include "HTMLDetailsElement.h"
#include "HTMLFieldSetElement.h"
#include "HTMLFormElement.h"
#include "HTMLImageElement.h"
#include "HTMLInputElement.h"
#include "HTMLLabelElement.h"
#include "HTMLLegendElement.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "HTMLSelectElement.h"
#include "HTMLTextAreaElement.h"
#include "HTMLTextFormControlElement.h"
#include "LabelableElement.h"
#include "LocalizedStrings.h"
#include "MathMLElement.h"
#include "MathMLNames.h"
#include "NodeList.h"
#include "NodeTraversal.h"
#include "ProgressTracker.h"
#include "RenderImage.h"
#include "RenderView.h"
#include "SVGElement.h"
#include "Text.h"
#include "TextControlInnerElements.h"
#include "UserGestureIndicator.h"
#include "VisibleUnits.h"
#include "Widget.h"
#include <wtf/StdLibExtras.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/unicode/CharacterNames.h>
namespace WebCore {
using namespace HTMLNames;
static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
: AccessibilityObject()
, m_node(node)
{
}
AccessibilityNodeObject::~AccessibilityNodeObject()
{
ASSERT(isDetached());
}
void AccessibilityNodeObject::init()
{
#ifndef NDEBUG
ASSERT(!m_initialized);
m_initialized = true;
#endif
m_role = determineAccessibilityRole();
}
Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
{
return adoptRef(*new AccessibilityNodeObject(node));
}
void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
{
AccessibilityObject::detach(detachmentType, cache);
m_node = nullptr;
}
void AccessibilityNodeObject::childrenChanged()
{
if (!node() && !renderer())
return;
AXObjectCache* cache = axObjectCache();
if (!cache)
return;
cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged);
this->setNeedsToUpdateSubtree();
bool shouldStopUpdatingParent = false;
for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) {
if (!shouldStopUpdatingParent)
parent->setNeedsToUpdateChildren();
if (parent->supportsLiveRegion())
cache->postLiveRegionChangeNotification(parent);
if (parent->isNonNativeTextControl()) {
cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
shouldStopUpdatingParent = true;
}
}
}
void AccessibilityNodeObject::updateAccessibilityRole()
{
bool ignoredStatus = accessibilityIsIgnored();
m_role = determineAccessibilityRole();
if (ignoredStatus != accessibilityIsIgnored())
childrenChanged();
}
AccessibilityObject* AccessibilityNodeObject::firstChild() const
{
if (!node())
return nullptr;
Node* firstChild = node()->firstChild();
if (!firstChild)
return nullptr;
return axObjectCache()->getOrCreate(firstChild);
}
AccessibilityObject* AccessibilityNodeObject::lastChild() const
{
if (!node())
return nullptr;
Node* lastChild = node()->lastChild();
if (!lastChild)
return nullptr;
return axObjectCache()->getOrCreate(lastChild);
}
AccessibilityObject* AccessibilityNodeObject::previousSibling() const
{
if (!node())
return nullptr;
Node* previousSibling = node()->previousSibling();
if (!previousSibling)
return nullptr;
return axObjectCache()->getOrCreate(previousSibling);
}
AccessibilityObject* AccessibilityNodeObject::nextSibling() const
{
if (!node())
return nullptr;
Node* nextSibling = node()->nextSibling();
if (!nextSibling)
return nullptr;
return axObjectCache()->getOrCreate(nextSibling);
}
AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
{
return parentObject();
}
AccessibilityObject* AccessibilityNodeObject::parentObject() const
{
if (!node())
return nullptr;
Node* parentObj = node()->parentNode();
if (!parentObj)
return nullptr;
if (AXObjectCache* cache = axObjectCache())
return cache->getOrCreate(parentObj);
return nullptr;
}
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 nullptr;
return &node()->document();
}
AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
{
if (!node())
return AccessibilityRole::Unknown;
if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown)
return m_ariaRole;
if (node()->isLink())
return AccessibilityRole::WebCoreLink;
if (node()->isTextNode())
return AccessibilityRole::StaticText;
if (node()->hasTagName(buttonTag))
return buttonRoleType();
if (is<HTMLInputElement>(*node())) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node());
if (input.isCheckbox())
return AccessibilityRole::CheckBox;
if (input.isRadioButton())
return AccessibilityRole::RadioButton;
if (input.isTextButton())
return buttonRoleType();
if (input.isRangeControl())
return AccessibilityRole::Slider;
if (input.isInputTypeHidden())
return AccessibilityRole::Ignored;
if (input.isSearchField())
return AccessibilityRole::SearchField;
#if ENABLE(INPUT_TYPE_COLOR)
if (input.isColorControl())
return AccessibilityRole::ColorWell;
#endif
return AccessibilityRole::TextField;
}
if (node()->hasTagName(selectTag)) {
HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
return selectElement.multiple() ? AccessibilityRole::ListBox : AccessibilityRole::PopUpButton;
}
if (is<HTMLTextAreaElement>(*node()))
return AccessibilityRole::TextArea;
if (headingLevel())
return AccessibilityRole::Heading;
if (node()->hasTagName(blockquoteTag))
return AccessibilityRole::Blockquote;
if (node()->hasTagName(divTag))
return AccessibilityRole::Div;
if (node()->hasTagName(pTag))
return AccessibilityRole::Paragraph;
if (is<HTMLLabelElement>(*node()))
return AccessibilityRole::Label;
if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
return AccessibilityRole::Group;
return AccessibilityRole::Unknown;
}
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));
m_subtreeDirty = false;
}
bool AccessibilityNodeObject::canHaveChildren() const
{
if (!node() && !isAccessibilityRenderObject())
return false;
if (node() && !renderer() && node()->hasTagName(noscriptTag))
return false;
switch (roleValue()) {
case AccessibilityRole::Image:
case AccessibilityRole::Button:
case AccessibilityRole::PopUpButton:
case AccessibilityRole::CheckBox:
case AccessibilityRole::RadioButton:
case AccessibilityRole::Tab:
case AccessibilityRole::ToggleButton:
case AccessibilityRole::StaticText:
case AccessibilityRole::ListBoxOption:
case AccessibilityRole::ScrollBar:
case AccessibilityRole::ProgressIndicator:
case AccessibilityRole::Switch:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::Splitter:
return false;
case AccessibilityRole::DocumentMath:
#if ENABLE(MATHML)
return node()->isMathMLElement();
#endif
return false;
default:
return true;
}
}
bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
{
#ifndef NDEBUG
ASSERT(m_initialized);
#endif
if (m_node && m_node->isTextNode() && !renderer()) {
if (m_node->parentNode() && m_node->parentNode()->hasTagName(iframeTag) && m_node->parentNode()->renderer())
return true;
String string = stringValue().stripWhiteSpace().simplifyWhiteSpace();
if (!string.length())
return true;
}
AccessibilityObjectInclusion decision = defaultObjectInclusion();
if (decision == AccessibilityObjectInclusion::IncludeObject)
return false;
if (decision == AccessibilityObjectInclusion::IgnoreObject)
return true;
if (isDescendantOfBarrenParent())
return true;
if (roleValue() == AccessibilityRole::Ignored)
return true;
return m_role == AccessibilityRole::Unknown;
}
bool AccessibilityNodeObject::canvasHasFallbackContent() const
{
Node* node = this->node();
if (!is<HTMLCanvasElement>(node))
return false;
HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node);
return childrenOfType<Element>(canvasElement).first();
}
bool AccessibilityNodeObject::isImageButton() const
{
return isNativeImage() && isButton();
}
bool AccessibilityNodeObject::isNativeTextControl() const
{
Node* node = this->node();
if (!node)
return false;
if (is<HTMLTextAreaElement>(*node))
return true;
if (is<HTMLInputElement>(*node)) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
return input.isText() || input.isNumberField();
}
return false;
}
bool AccessibilityNodeObject::isSearchField() const
{
Node* node = this->node();
if (!node)
return false;
if (roleValue() == AccessibilityRole::SearchField)
return true;
if (!is<HTMLInputElement>(*node))
return false;
auto& inputElement = downcast<HTMLInputElement>(*node);
const AtomicString& nameAttribute = getAttribute(nameAttr);
if (nameAttribute.containsIgnoringASCIICase("search"))
return true;
auto* form = inputElement.form();
if (form && (form->name().containsIgnoringASCIICase("search") || form->action().containsIgnoringASCIICase("search")))
return true;
return false;
}
bool AccessibilityNodeObject::isNativeImage() const
{
Node* node = this->node();
if (!node)
return false;
if (is<HTMLImageElement>(*node))
return true;
if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
return true;
if (is<HTMLInputElement>(*node)) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
return input.isImageButton();
}
return false;
}
bool AccessibilityNodeObject::isImage() const
{
return roleValue() == AccessibilityRole::Image;
}
bool AccessibilityNodeObject::isPasswordField() const
{
auto* node = this->node();
if (!is<HTMLInputElement>(node))
return false;
if (ariaRoleAttribute() != AccessibilityRole::Unknown)
return false;
return downcast<HTMLInputElement>(*node).isPasswordField();
}
AccessibilityObject* AccessibilityNodeObject::passwordFieldOrContainingPasswordField()
{
Node* node = this->node();
if (!node)
return nullptr;
if (is<HTMLInputElement>(*node) && downcast<HTMLInputElement>(*node).isPasswordField())
return this;
auto* element = node->shadowHost();
if (!is<HTMLInputElement>(element))
return nullptr;
if (auto* cache = axObjectCache())
return cache->getOrCreate(element);
return nullptr;
}
bool AccessibilityNodeObject::isInputImage() const
{
Node* node = this->node();
if (is<HTMLInputElement>(node) && roleValue() == AccessibilityRole::Button) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
return input.isImageButton();
}
return false;
}
bool AccessibilityNodeObject::isProgressIndicator() const
{
return roleValue() == AccessibilityRole::ProgressIndicator;
}
bool AccessibilityNodeObject::isSlider() const
{
return roleValue() == AccessibilityRole::Slider;
}
bool AccessibilityNodeObject::isMenuRelated() const
{
switch (roleValue()) {
case AccessibilityRole::Menu:
case AccessibilityRole::MenuBar:
case AccessibilityRole::MenuButton:
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
return true;
default:
return false;
}
}
bool AccessibilityNodeObject::isMenu() const
{
return roleValue() == AccessibilityRole::Menu;
}
bool AccessibilityNodeObject::isMenuBar() const
{
return roleValue() == AccessibilityRole::MenuBar;
}
bool AccessibilityNodeObject::isMenuButton() const
{
return roleValue() == AccessibilityRole::MenuButton;
}
bool AccessibilityNodeObject::isMenuItem() const
{
switch (roleValue()) {
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::MenuItemCheckbox:
return true;
default:
return false;
}
}
bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
{
Node* node = this->node();
if (!is<HTMLInputElement>(node))
return false;
auto& input = downcast<HTMLInputElement>(*node);
return input.isCheckbox() || input.isRadioButton();
}
bool AccessibilityNodeObject::isEnabled() const
{
for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
if (equalLettersIgnoringASCIICase(disabledStatus, "true"))
return false;
if (equalLettersIgnoringASCIICase(disabledStatus, "false"))
break;
}
if (roleValue() == AccessibilityRole::HorizontalRule)
return false;
Node* node = this->node();
if (!is<Element>(node))
return true;
return !downcast<Element>(*node).isDisabledFormControl();
}
bool AccessibilityNodeObject::isIndeterminate() const
{
return equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true");
}
bool AccessibilityNodeObject::isPressed() const
{
if (!isButton())
return false;
Node* node = this->node();
if (!node)
return false;
if (isToggleButton())
return equalLettersIgnoringASCIICase(getAttribute(aria_pressedAttr), "true");
if (!is<Element>(*node))
return false;
return downcast<Element>(*node).active();
}
bool AccessibilityNodeObject::isChecked() const
{
Node* node = this->node();
if (!node)
return false;
if (is<HTMLInputElement>(*node))
return downcast<HTMLInputElement>(*node).shouldAppearChecked();
bool validRole = false;
switch (ariaRoleAttribute()) {
case AccessibilityRole::RadioButton:
case AccessibilityRole::CheckBox:
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::Switch:
validRole = true;
break;
default:
break;
}
if (validRole && equalLettersIgnoringASCIICase(getAttribute(aria_checkedAttr), "true"))
return true;
return false;
}
bool AccessibilityNodeObject::isHovered() const
{
Node* node = this->node();
return is<Element>(node) && downcast<Element>(*node).hovered();
}
bool AccessibilityNodeObject::isMultiSelectable() const
{
const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "true"))
return true;
if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "false"))
return false;
return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
}
bool AccessibilityNodeObject::isRequired() const
{
const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
if (equalLettersIgnoringASCIICase(requiredValue, "true"))
return true;
if (equalLettersIgnoringASCIICase(requiredValue, "false"))
return false;
Node* n = this->node();
if (is<HTMLFormControlElement>(n))
return downcast<HTMLFormControlElement>(*n).isRequired();
return false;
}
bool AccessibilityNodeObject::supportsRequiredAttribute() const
{
switch (roleValue()) {
case AccessibilityRole::Button:
return isFileUploadButton();
case AccessibilityRole::Cell:
case AccessibilityRole::ColumnHeader:
case AccessibilityRole::CheckBox:
case AccessibilityRole::ComboBox:
case AccessibilityRole::Grid:
case AccessibilityRole::GridCell:
case AccessibilityRole::Incrementor:
case AccessibilityRole::ListBox:
case AccessibilityRole::PopUpButton:
case AccessibilityRole::RadioButton:
case AccessibilityRole::RadioGroup:
case AccessibilityRole::RowHeader:
case AccessibilityRole::Slider:
case AccessibilityRole::SpinButton:
case AccessibilityRole::TableHeaderContainer:
case AccessibilityRole::TextArea:
case AccessibilityRole::TextField:
case AccessibilityRole::ToggleButton:
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;
if (ariaRoleAttribute() == AccessibilityRole::Heading)
return 2;
return 0;
}
String AccessibilityNodeObject::valueDescription() const
{
if (!isRangeControl())
return String();
return getAttribute(aria_valuetextAttr).string();
}
float AccessibilityNodeObject::valueForRange() const
{
if (is<HTMLInputElement>(node())) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node());
if (input.isRangeControl())
return input.valueAsNumber();
}
if (!isRangeControl())
return 0.0f;
auto& value = getAttribute(aria_valuenowAttr);
if (!value.isEmpty())
return value.toFloat();
return isSpinButton() ? 0 : (minValueForRange() + maxValueForRange()) / 2;
}
float AccessibilityNodeObject::maxValueForRange() const
{
if (is<HTMLInputElement>(node())) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node());
if (input.isRangeControl())
return input.maximum();
}
if (!isRangeControl())
return 0.0f;
auto& value = getAttribute(aria_valuemaxAttr);
if (!value.isEmpty())
return value.toFloat();
return isSpinButton() ? std::numeric_limits<float>::max() : 100.0f;
}
float AccessibilityNodeObject::minValueForRange() const
{
if (is<HTMLInputElement>(node())) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node());
if (input.isRangeControl())
return input.minimum();
}
if (!isRangeControl())
return 0.0f;
auto& value = getAttribute(aria_valueminAttr);
if (!value.isEmpty())
return value.toFloat();
return isSpinButton() ? -std::numeric_limits<float>::max() : 0.0f;
}
float AccessibilityNodeObject::stepValueForRange() const
{
return getAttribute(stepAttr).toFloat();
}
bool AccessibilityNodeObject::isHeading() const
{
return roleValue() == AccessibilityRole::Heading;
}
bool AccessibilityNodeObject::isLink() const
{
return roleValue() == AccessibilityRole::WebCoreLink;
}
bool AccessibilityNodeObject::isControl() const
{
Node* node = this->node();
if (!node)
return false;
return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute());
}
bool AccessibilityNodeObject::isFieldset() const
{
Node* node = this->node();
if (!node)
return false;
return node->hasTagName(fieldsetTag);
}
bool AccessibilityNodeObject::isGroup() const
{
AccessibilityRole role = roleValue();
return role == AccessibilityRole::Group || role == AccessibilityRole::TextGroup || role == AccessibilityRole::ApplicationGroup || role == AccessibilityRole::ApplicationTextGroup;
}
AccessibilityObject* AccessibilityNodeObject::selectedRadioButton()
{
if (!isRadioGroup())
return nullptr;
for (const auto& child : children()) {
if (child->roleValue() == AccessibilityRole::RadioButton && child->checkboxOrRadioValue() == AccessibilityButtonState::On)
return child.get();
}
return nullptr;
}
AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
{
if (!isTabList())
return nullptr;
AccessibilityObject::AccessibilityChildrenVector tabs;
tabChildren(tabs);
for (const auto& child : children()) {
if (child->isTabItem() && (child->isChecked() || child->isSelected()))
return child.get();
}
return nullptr;
}
AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
{
if (isNativeCheckboxOrRadio())
return isIndeterminate() ? AccessibilityButtonState::Mixed : isChecked() ? AccessibilityButtonState::On : AccessibilityButtonState::Off;
return AccessibilityObject::checkboxOrRadioValue();
}
Element* AccessibilityNodeObject::anchorElement() const
{
Node* node = this->node();
if (!node)
return nullptr;
AXObjectCache* cache = axObjectCache();
for ( ; node; node = node->parentNode()) {
if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
return downcast<Element>(node);
}
return nullptr;
}
static bool isNodeActionElement(Node* node)
{
if (is<HTMLInputElement>(*node)) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
if (!input.isDisabledFormControl() && (input.isRadioButton() || input.isCheckbox() || input.isTextButton() || input.isFileUpload() || input.isImageButton()))
return true;
} else if (node->hasTagName(buttonTag) || node->hasTagName(selectTag))
return true;
return false;
}
static Element* nativeActionElement(Node* start)
{
if (!start)
return nullptr;
for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
if (isNodeActionElement(child))
return downcast<Element>(child);
if (Element* subChild = nativeActionElement(child))
return subChild;
}
return nullptr;
}
Element* AccessibilityNodeObject::actionElement() const
{
Node* node = this->node();
if (!node)
return nullptr;
if (isNodeActionElement(node))
return downcast<Element>(node);
if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
return downcast<Element>(node);
switch (roleValue()) {
case AccessibilityRole::Button:
case AccessibilityRole::PopUpButton:
case AccessibilityRole::ToggleButton:
case AccessibilityRole::Tab:
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::ListItem:
if (Element* nativeElement = nativeActionElement(node))
return nativeElement;
return downcast<Element>(node);
default:
break;
}
Element* elt = anchorElement();
if (!elt)
elt = mouseButtonListener();
return elt;
}
Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const
{
Node* node = this->node();
if (!node)
return nullptr;
for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) {
if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement)
break;
if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
return &element;
}
return nullptr;
}
bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
{
if (!m_isIgnoredFromParentData.isNull())
return m_isIgnoredFromParentData.isDescendantOfBarrenParent;
for (AccessibilityObject* object = parentObject(); object; object = object->parentObject()) {
if (!object->canHaveChildren())
return true;
}
return false;
}
void AccessibilityNodeObject::alterSliderValue(bool increase)
{
if (roleValue() != AccessibilityRole::Slider)
return;
if (!getAttribute(stepAttr).isEmpty())
changeValueByStep(increase);
else
changeValueByPercent(increase ? 5 : -5);
}
void AccessibilityNodeObject::increment()
{
if (dispatchAccessibilityEventWithType(AccessibilityEventType::Increment))
return;
UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
alterSliderValue(true);
}
void AccessibilityNodeObject::decrement()
{
if (dispatchAccessibilityEventWithType(AccessibilityEventType::Decrement))
return;
UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
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);
}
void AccessibilityNodeObject::changeValueByPercent(float percentChange)
{
float range = maxValueForRange() - minValueForRange();
float step = range * (percentChange / 100);
float value = valueForRange();
if (fabs(step) < 1)
step = fabs(percentChange) * (1 / percentChange);
value += step;
setValue(String::number(value));
axObjectCache()->postNotification(node(), AXObjectCache::AXValueChanged);
}
bool AccessibilityNodeObject::isGenericFocusableElement() const
{
if (!canSetFocusAttribute())
return false;
if (isControl())
return false;
AccessibilityRole role = roleValue();
if (role == AccessibilityRole::Video || role == AccessibilityRole::Audio)
return false;
if (m_ariaRole != AccessibilityRole::Unknown)
return false;
if (hasContentEditableAttributeSet())
return false;
if (role == AccessibilityRole::WebArea)
return false;
if (node() && node()->hasTagName(bodyTag))
return false;
if (role == AccessibilityRole::SVGRoot)
return false;
return true;
}
HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
{
if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable())
return nullptr;
const AtomicString& id = element->getIdAttribute();
if (!id.isEmpty()) {
if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
return label;
}
return ancestorsOfType<HTMLLabelElement>(*element).first();
}
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(Node* node, const char* role)
{
ContainerNode* parent = node->parentNode();
if (!parent)
return nullptr;
for (auto& sibling : childrenOfType<Element>(*parent)) {
if (equalIgnoringASCIICase(sibling.attributeWithoutSynchronization(roleAttr), role))
return &sibling;
}
return nullptr;
}
Element* AccessibilityNodeObject::menuElementForMenuButton() const
{
if (ariaRoleAttribute() != AccessibilityRole::MenuButton)
return nullptr;
return siblingWithAriaRole(node(), "menu");
}
AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
{
if (AXObjectCache* cache = axObjectCache())
return cache->getOrCreate(menuElementForMenuButton());
return nullptr;
}
Element* AccessibilityNodeObject::menuItemElementForMenu() const
{
if (ariaRoleAttribute() != AccessibilityRole::Menu)
return nullptr;
return siblingWithAriaRole(node(), "menuitem");
}
AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
{
AXObjectCache* cache = axObjectCache();
if (!cache)
return nullptr;
Element* menuItem = menuItemElementForMenu();
if (menuItem) {
AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem);
if (menuItemAX && menuItemAX->isMenuButton())
return menuItemAX;
}
return nullptr;
}
AccessibilityObject* AccessibilityNodeObject::captionForFigure() const
{
if (!isFigureElement())
return nullptr;
AXObjectCache* cache = axObjectCache();
if (!cache)
return nullptr;
Node* node = this->node();
for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
if (child->hasTagName(figcaptionTag))
return cache->getOrCreate(child);
}
return nullptr;
}
bool AccessibilityNodeObject::usesAltTagForTextComputation() const
{
return isImage() || isInputImage() || isNativeImage() || isCanvas() || (node() && node()->hasTagName(imgTag));
}
bool AccessibilityNodeObject::isLabelable() const
{
Node* node = this->node();
if (!node)
return false;
return is<HTMLInputElement>(*node) || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl() || isProgressIndicator() || isMeter();
}
String AccessibilityNodeObject::textForLabelElement(Element* element) const
{
String result = String();
if (!is<HTMLLabelElement>(*element))
return result;
HTMLLabelElement* label = downcast<HTMLLabelElement>(element);
if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label))
result = labelObject->ariaLabeledByAttribute();
return !result.isEmpty() ? result : accessibleNameForNode(label);
}
void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
{
Node* node = this->node();
if (!node)
return;
if (isLabelable()) {
if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
String innerText = textForLabelElement(label);
if (!innerText.isEmpty() && !ariaAccessibilityDescription())
textOrder.append(AccessibilityText(innerText, isMeter() ? AccessibilityTextSource::Alternative : AccessibilityTextSource::LabelByElement, axObjectCache()->getOrCreate(label)));
return;
}
}
AccessibilityObject* titleUIElement = this->titleUIElement();
if (titleUIElement)
textOrder.append(AccessibilityText(String(), AccessibilityTextSource::LabelByElement, titleUIElement));
}
void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
{
if (isWebArea()) {
String webAreaText = alternativeTextForWebArea();
if (!webAreaText.isEmpty())
textOrder.append(AccessibilityText(webAreaText, AccessibilityTextSource::Alternative));
return;
}
ariaLabeledByText(textOrder);
const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
if (!ariaLabel.isEmpty())
textOrder.append(AccessibilityText(ariaLabel, AccessibilityTextSource::Alternative));
if (usesAltTagForTextComputation()) {
if (is<RenderImage>(renderer())) {
String renderAltText = downcast<RenderImage>(*renderer()).altText();
if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
textOrder.append(AccessibilityText(renderAltText, AccessibilityTextSource::Alternative));
return;
}
}
const AtomicString& alt = getAttribute(altAttr);
if (!alt.isEmpty())
textOrder.append(AccessibilityText(alt, AccessibilityTextSource::Alternative));
}
Node* node = this->node();
if (!node)
return;
if (is<HTMLFieldSetElement>(*node)) {
AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
if (object && !object->isHidden())
textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AccessibilityTextSource::Alternative));
}
if (isFigureElement()) {
AccessibilityObject* captionForFigure = this->captionForFigure();
if (captionForFigure && !captionForFigure->isHidden())
textOrder.append(AccessibilityText(accessibleNameForNode(captionForFigure->node()), AccessibilityTextSource::Alternative));
}
if (isTreeItem() && ariaLabel.isEmpty() && ariaLabeledByAttribute().isEmpty())
textOrder.append(AccessibilityText(accessibleNameForNode(node), AccessibilityTextSource::Alternative));
#if ENABLE(MATHML)
if (node->isMathMLElement())
textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AccessibilityTextSource::Alternative));
#endif
}
void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
{
Node* node = this->node();
if (!node)
return;
bool isInputTag = is<HTMLInputElement>(*node);
if (isInputTag) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
if (input.isTextButton()) {
textOrder.append(AccessibilityText(input.valueWithDefault(), AccessibilityTextSource::Visible));
return;
}
}
if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
return;
bool useTextUnderElement = false;
switch (roleValue()) {
case AccessibilityRole::PopUpButton:
if (node->hasTagName(selectTag))
break;
FALLTHROUGH;
case AccessibilityRole::Button:
case AccessibilityRole::ToggleButton:
case AccessibilityRole::CheckBox:
case AccessibilityRole::ListBoxOption:
#if !PLATFORM(COCOA)
case AccessibilityRole::ListItem:
#endif
case AccessibilityRole::MenuButton:
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::RadioButton:
case AccessibilityRole::Switch:
case AccessibilityRole::Tab:
useTextUnderElement = true;
break;
default:
break;
}
if (isHeading() || isLink())
useTextUnderElement = true;
if (isOutput())
useTextUnderElement = true;
if (useTextUnderElement) {
AccessibilityTextUnderElementMode mode;
if (isHeading())
mode.includeFocusableContent = true;
String text = textUnderElement(mode);
if (!text.isEmpty())
textOrder.append(AccessibilityText(text, AccessibilityTextSource::Children));
}
}
void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
{
const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
if (!ariaHelp.isEmpty())
textOrder.append(AccessibilityText(ariaHelp, AccessibilityTextSource::Help));
String describedBy = ariaDescribedByAttribute();
if (!describedBy.isEmpty())
textOrder.append(AccessibilityText(describedBy, AccessibilityTextSource::Summary));
else if (isControl()) {
auto matchFunc = [] (const AccessibilityObject& object) {
return object.isFieldset() && !object.ariaDescribedByAttribute().isEmpty();
};
if (const auto* parent = AccessibilityObject::matchedParent(*this, false, WTFMove(matchFunc)))
textOrder.append(AccessibilityText(parent->ariaDescribedByAttribute(), AccessibilityTextSource::Summary));
}
const AtomicString& summary = getAttribute(summaryAttr);
if (!summary.isEmpty())
textOrder.append(AccessibilityText(summary, AccessibilityTextSource::Summary));
const AtomicString& title = getAttribute(titleAttr);
if (!title.isEmpty()) {
if (!isMeter() && !roleIgnoresTitle())
textOrder.append(AccessibilityText(title, AccessibilityTextSource::TitleTag));
else
textOrder.append(AccessibilityText(title, AccessibilityTextSource::Help));
}
}
void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
{
titleElementText(textOrder);
alternativeText(textOrder);
visibleText(textOrder);
helpText(textOrder);
String placeholder = placeholderValue();
if (!placeholder.isEmpty())
textOrder.append(AccessibilityText(placeholder, AccessibilityTextSource::Placeholder));
}
void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
{
String ariaLabeledBy = ariaLabeledByAttribute();
if (!ariaLabeledBy.isEmpty()) {
Vector<Element*> elements;
ariaLabeledByElements(elements);
Vector<RefPtr<AccessibilityObject>> axElements;
for (const auto& element : elements)
axElements.append(axObjectCache()->getOrCreate(element));
textOrder.append(AccessibilityText(ariaLabeledBy, AccessibilityTextSource::Alternative, WTFMove(axElements)));
}
}
String AccessibilityNodeObject::alternativeTextForWebArea() const
{
Document* document = this->document();
if (!document)
return String();
if (Element* documentElement = document->documentElement()) {
const AtomicString& ariaLabel = documentElement->attributeWithoutSynchronization(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
}
if (auto* owner = document->ownerElement()) {
if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
const AtomicString& title = owner->attributeWithoutSynchronization(titleAttr);
if (!title.isEmpty())
return title;
}
return owner->getNameAttribute();
}
String documentTitle = document->title();
if (!documentTitle.isEmpty())
return documentTitle;
if (auto* body = document->bodyOrFrameset())
return body->getNameAttribute();
return String();
}
String AccessibilityNodeObject::accessibilityDescription() const
{
if (roleValue() == AccessibilityRole::StaticText)
return String();
String ariaDescription = ariaAccessibilityDescription();
if (!ariaDescription.isEmpty())
return ariaDescription;
if (usesAltTagForTextComputation()) {
const AtomicString& alt = getAttribute(altAttr);
if (!alt.isNull())
return alt;
}
#if ENABLE(MATHML)
if (is<MathMLElement>(m_node))
return getAttribute(MathMLNames::alttextAttr);
#endif
if (title().isEmpty() && !roleIgnoresTitle())
return getAttribute(titleAttr);
return String();
}
bool AccessibilityNodeObject::roleIgnoresTitle() const
{
if (ariaRoleAttribute() != AccessibilityRole::Unknown)
return false;
switch (roleValue()) {
case AccessibilityRole::Div:
case AccessibilityRole::Unknown:
return true;
default:
return false;
}
}
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* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
if (is<HTMLElement>(*ancestor)) {
HTMLElement& element = downcast<HTMLElement>(*ancestor);
const AtomicString& summary = element.getAttribute(summaryAttr);
if (!summary.isEmpty())
return summary;
const AtomicString& title = element.getAttribute(titleAttr);
if (!title.isEmpty() && description != title)
return title;
}
if (AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor)) {
if (!axObj->isGroup() && axObj->roleValue() != AccessibilityRole::Unknown)
break;
}
}
return String();
}
unsigned AccessibilityNodeObject::hierarchicalLevel() const
{
Node* node = this->node();
if (!is<Element>(node))
return 0;
Element& element = downcast<Element>(*node);
const AtomicString& ariaLevel = element.attributeWithoutSynchronization(aria_levelAttr);
if (!ariaLevel.isEmpty())
return ariaLevel.toInt();
if (roleValue() != AccessibilityRole::TreeItem)
return 0;
unsigned level = 1;
for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
AccessibilityRole parentRole = parent->ariaRoleAttribute();
if (parentRole == AccessibilityRole::ApplicationGroup)
level++;
else if (parentRole == AccessibilityRole::Tree)
break;
}
return level;
}
void AccessibilityNodeObject::setIsExpanded(bool expand)
{
if (is<HTMLDetailsElement>(node())) {
auto& details = downcast<HTMLDetailsElement>(*node());
if (expand != details.isOpen())
details.toggleOpen();
}
}
static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
{
if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
return true;
if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren
&& !obj->accessibleNameDerivesFromContent())
return false;
if (equalLettersIgnoringASCIICase(obj->getAttribute(aria_hiddenAttr), "true"))
return false;
if (obj->isDescendantOfBarrenParent())
return true;
if (obj->canSetFocusAttribute() && !mode.includeFocusableContent)
return false;
if (is<AccessibilityList>(*obj))
return false;
if (is<AccessibilityTable>(*obj) && downcast<AccessibilityTable>(*obj).isExposableThroughAccessibility())
return false;
if (obj->isTree() || obj->isCanvas())
return false;
return true;
}
static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText)
{
if (!builder.length() || !childText.length())
return false;
return !(isHTMLLineBreak(childText[0]) || isHTMLLineBreak(builder[builder.length() - 1]));
}
static void appendNameToStringBuilder(StringBuilder& builder, const String& text)
{
if (shouldAddSpaceBeforeAppendingNextElement(builder, text))
builder.append(' ');
builder.append(text);
}
String AccessibilityNodeObject::textUnderElement(AccessibilityTextUnderElementMode mode) const
{
Node* node = this->node();
if (is<Text>(node))
return downcast<Text>(*node).wholeText();
if (isHidden() && !is<HTMLLabelElement>(node) && (node && !ancestorsOfType<HTMLCanvasElement>(*node).first())) {
AccessibilityObject::AccessibilityChildrenVector labelFor;
AccessibilityObject::AccessibilityChildrenVector descriptionFor;
ariaLabelledByReferencingElements(labelFor);
ariaDescribedByReferencingElements(descriptionFor);
if (!labelFor.size() && !descriptionFor.size())
return String();
}
StringBuilder builder;
for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
if (mode.ignoredChildNode && child->node() == mode.ignoredChildNode)
continue;
bool shouldDeriveNameFromAuthor = (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren && !child->accessibleNameDerivesFromContent());
if (shouldDeriveNameFromAuthor) {
appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
continue;
}
if (!shouldUseAccessibilityObjectInnerText(child, mode))
continue;
if (is<AccessibilityNodeObject>(*child)) {
Vector<Element*> labeledByElements;
downcast<AccessibilityNodeObject>(*child).ariaLabeledByElements(labeledByElements);
if (labeledByElements.contains(node))
continue;
Vector<AccessibilityText> textOrder;
downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
if (textOrder.size() > 0 && textOrder[0].text.length()) {
appendNameToStringBuilder(builder, textOrder[0].text);
continue;
}
}
String childText = child->textUnderElement(mode);
if (childText.length())
appendNameToStringBuilder(builder, childText);
}
return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
}
String AccessibilityNodeObject::title() const
{
Node* node = this->node();
if (!node)
return String();
bool isInputTag = is<HTMLInputElement>(*node);
if (isInputTag) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
if (input.isTextButton())
return input.valueWithDefault();
}
if (isLabelable()) {
HTMLLabelElement* label = labelForElement(downcast<Element>(node));
if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
return textForLabelElement(label);
}
if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
return String();
switch (roleValue()) {
case AccessibilityRole::PopUpButton:
if (node->hasTagName(selectTag))
return String();
FALLTHROUGH;
case AccessibilityRole::Button:
case AccessibilityRole::ToggleButton:
case AccessibilityRole::CheckBox:
case AccessibilityRole::ListBoxOption:
case AccessibilityRole::ListItem:
case AccessibilityRole::MenuButton:
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::RadioButton:
case AccessibilityRole::Switch:
case AccessibilityRole::Tab:
return textUnderElement();
case AccessibilityRole::SVGRoot:
return String();
default:
break;
}
if (isLink())
return textUnderElement();
if (isHeading())
return textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeSkipIgnoredChildren, true));
return String();
}
String AccessibilityNodeObject::text() const
{
if (isARIAStaticText()) {
Vector<AccessibilityText> textOrder;
alternativeText(textOrder);
if (textOrder.size() > 0 && textOrder[0].text.length())
return textOrder[0].text;
}
if (!isTextControl())
return String();
Node* node = this->node();
if (!node)
return String();
if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node))
return downcast<HTMLTextFormControlElement>(*node).value();
if (!node->isElementNode())
return String();
return downcast<Element>(node)->innerText();
}
String AccessibilityNodeObject::stringValue() const
{
Node* node = this->node();
if (!node)
return String();
if (isARIAStaticText()) {
String staticText = text();
if (!staticText.length())
staticText = textUnderElement();
return staticText;
}
if (node->isTextNode())
return textUnderElement();
if (node->hasTagName(selectTag)) {
HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*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]->attributeWithoutSynchronization(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 ENABLE(INPUT_TYPE_COLOR)
if (!isColorWell())
return;
if (!is<HTMLInputElement>(node()))
return;
auto color = downcast<HTMLInputElement>(*node()).valueAsColor();
r = color.red();
g = color.green();
b = color.blue();
#endif
}
static String accessibleNameForNode(Node* node, Node* labelledbyNode)
{
ASSERT(node);
if (!is<Element>(node))
return String();
Element& element = downcast<Element>(*node);
const AtomicString& ariaLabel = element.attributeWithoutSynchronization(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
const AtomicString& alt = element.attributeWithoutSynchronization(altAttr);
if (!alt.isEmpty())
return alt;
AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
if (axObject) {
String valueDescription = axObject->valueDescription();
if (!valueDescription.isEmpty())
return valueDescription;
AccessibilityObject::AccessibilityChildrenVector children;
if (axObject->isListBox())
axObject->selectedChildren(children);
else if (axObject->isComboBox()) {
for (const auto& child : axObject->children()) {
if (child->isListBox()) {
child->selectedChildren(children);
break;
}
}
}
StringBuilder builder;
String childText;
for (const auto& child : children)
appendNameToStringBuilder(builder, accessibleNameForNode(child->node()));
childText = builder.toString();
if (!childText.isEmpty())
return childText;
}
if (is<HTMLInputElement>(*node))
return downcast<HTMLInputElement>(*node).value();
String text;
if (axObject) {
if (axObject->accessibleNameDerivesFromContent())
text = axObject->textUnderElement(AccessibilityTextUnderElementMode(AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren, true, labelledbyNode));
} else
text = element.innerText().simplifyWhiteSpace();
if (!text.isEmpty())
return text;
const AtomicString& title = element.attributeWithoutSynchronization(titleAttr);
if (!title.isEmpty())
return title;
return String();
}
String AccessibilityNodeObject::accessibilityDescriptionForChildren() const
{
Node* node = this->node();
if (!node)
return String();
AXObjectCache* cache = axObjectCache();
if (!cache)
return String();
StringBuilder builder;
for (Node* child = node->firstChild(); child; child = child->nextSibling()) {
if (!is<Element>(child))
continue;
if (AccessibilityObject* axObject = cache->getOrCreate(child)) {
String description = axObject->ariaLabeledByAttribute();
if (description.isEmpty())
description = accessibleNameForNode(child);
appendNameToStringBuilder(builder, description);
}
}
return builder.toString();
}
String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Element*> &elements) const
{
StringBuilder builder;
unsigned size = elements.size();
for (unsigned i = 0; i < size; ++i)
appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node()));
return builder.toString();
}
String AccessibilityNodeObject::ariaDescribedByAttribute() const
{
Vector<Element*> elements;
elementsFromAttribute(elements, aria_describedbyAttr);
return accessibilityDescriptionForElements(elements);
}
void AccessibilityNodeObject::ariaLabeledByElements(Vector<Element*>& elements) const
{
elementsFromAttribute(elements, aria_labelledbyAttr);
if (!elements.size())
elementsFromAttribute(elements, aria_labeledbyAttr);
}
String AccessibilityNodeObject::ariaLabeledByAttribute() const
{
Vector<Element*> elements;
ariaLabeledByElements(elements);
return accessibilityDescriptionForElements(elements);
}
bool AccessibilityNodeObject::hasAttributesRequiredForInclusion() const
{
if (AccessibilityObject::hasAttributesRequiredForInclusion())
return true;
if (getAttribute(aria_labelledbyAttr).length() || getAttribute(aria_labeledbyAttr).length() || getAttribute(aria_labelAttr).length())
return true;
return false;
}
bool AccessibilityNodeObject::canSetFocusAttribute() const
{
Node* node = this->node();
if (!node)
return false;
if (isWebArea())
return true;
if (!is<Element>(node))
return false;
Element& element = downcast<Element>(*node);
if (element.isDisabledFormControl())
return false;
return element.supportsFocus();
}
bool AccessibilityNodeObject::canSetValueAttribute() const
{
Node* node = this->node();
if (!node)
return false;
if (is<HTMLTextAreaElement>(*node))
return !downcast<HTMLTextAreaElement>(*node).isReadOnly();
if (is<HTMLInputElement>(*node)) {
HTMLInputElement& input = downcast<HTMLInputElement>(*node);
if (input.isTextField())
return !input.isReadOnly();
}
String readOnly = readOnlyValue();
if (!readOnly.isEmpty())
return readOnly == "true" ? false : true;
if (isNonNativeTextControl())
return true;
if (isMeter())
return false;
if (isProgressIndicator() || isSlider() || isScrollbar())
return true;
#if PLATFORM(GTK)
if (supportsReadOnly())
return true;
if (isRadioButton()) {
auto radioGroup = radioGroupAncestor();
return radioGroup ? radioGroup->readOnlyValue() != "true" : true;
}
#endif
if (isWebArea()) {
Document* document = this->document();
if (!document)
return false;
if (HTMLElement* body = document->bodyOrFrameset()) {
if (body->hasEditableStyle())
return true;
}
return document->hasEditableStyle();
}
return node->hasEditableStyle();
}
AccessibilityRole AccessibilityNodeObject::determineAriaRoleAttribute() const
{
const AtomicString& ariaRole = getAttribute(roleAttr);
if (ariaRole.isNull() || ariaRole.isEmpty())
return AccessibilityRole::Unknown;
AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
if (role == AccessibilityRole::Presentational && canSetFocusAttribute())
return AccessibilityRole::Unknown;
if (role == AccessibilityRole::Button)
role = buttonRoleType();
if (role == AccessibilityRole::TextArea && !ariaIsMultiline())
role = AccessibilityRole::TextField;
role = remapAriaRoleDueToParent(role);
if (role == AccessibilityRole::Presentational && supportsARIAAttributes())
role = AccessibilityRole::Unknown;
if (role == AccessibilityRole::LandmarkRegion && !hasAttribute(aria_labelAttr) && !hasAttribute(aria_labelledbyAttr))
role = AccessibilityRole::Unknown;
if (static_cast<int>(role))
return role;
return AccessibilityRole::Unknown;
}
AccessibilityRole AccessibilityNodeObject::ariaRoleAttribute() const
{
return m_ariaRole;
}
AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
{
if (role != AccessibilityRole::ListBoxOption && role != AccessibilityRole::MenuItem)
return role;
for (AccessibilityObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
if (role == AccessibilityRole::ListBoxOption && parentAriaRole == AccessibilityRole::Menu)
return AccessibilityRole::MenuItem;
if (role == AccessibilityRole::MenuItem && parentAriaRole == AccessibilityRole::ApplicationGroup)
return AccessibilityRole::MenuButton;
if (parentAriaRole != AccessibilityRole::Unknown)
break;
}
return role;
}
bool AccessibilityNodeObject::canSetSelectedAttribute() const
{
switch (roleValue()) {
case AccessibilityRole::Cell:
case AccessibilityRole::GridCell:
case AccessibilityRole::RadioButton:
case AccessibilityRole::RowHeader:
case AccessibilityRole::Row:
case AccessibilityRole::TabList:
case AccessibilityRole::Tab:
case AccessibilityRole::TreeGrid:
case AccessibilityRole::TreeItem:
case AccessibilityRole::Tree:
case AccessibilityRole::MenuItemCheckbox:
case AccessibilityRole::MenuItemRadio:
case AccessibilityRole::MenuItem:
return isEnabled();
default:
return false;
}
}
}