summaryrefslogtreecommitdiff
path: root/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/accessibility/AccessibilityNodeObject.cpp')
-rw-r--r--Source/WebCore/accessibility/AccessibilityNodeObject.cpp856
1 files changed, 501 insertions, 355 deletions
diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
index 8969dba6f..4bce0b09c 100644
--- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
+++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
@@ -10,7 +10,7 @@
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
-* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
@@ -31,6 +31,7 @@
#include "AXObjectCache.h"
#include "AccessibilityImageMapLink.h"
+#include "AccessibilityList.h"
#include "AccessibilityListBox.h"
#include "AccessibilitySpinButton.h"
#include "AccessibilityTable.h"
@@ -41,40 +42,31 @@
#include "FrameLoader.h"
#include "FrameSelection.h"
#include "FrameView.h"
-#include "HTMLAreaElement.h"
+#include "HTMLCanvasElement.h"
+#include "HTMLDetailsElement.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 "HTMLParserIdioms.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 "MathMLElement.h"
#include "MathMLNames.h"
#include "NodeList.h"
#include "NodeTraversal.h"
-#include "Page.h"
#include "ProgressTracker.h"
#include "RenderImage.h"
#include "RenderView.h"
#include "SVGElement.h"
-#include "SVGNames.h"
#include "Text.h"
#include "TextControlInnerElements.h"
-#include "TextIterator.h"
#include "UserGestureIndicator.h"
#include "VisibleUnits.h"
#include "Widget.h"
@@ -87,7 +79,7 @@ namespace WebCore {
using namespace HTMLNames;
-static String accessibleNameForNode(Node*);
+static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr);
AccessibilityNodeObject::AccessibilityNodeObject(Node* node)
: AccessibilityObject()
@@ -115,16 +107,16 @@ void AccessibilityNodeObject::init()
m_role = determineAccessibilityRole();
}
-PassRefPtr<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
+Ref<AccessibilityNodeObject> AccessibilityNodeObject::create(Node* node)
{
- return adoptRef(new AccessibilityNodeObject(node));
+ return adoptRef(*new AccessibilityNodeObject(node));
}
void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache)
{
// AccessibilityObject calls clearChildren.
AccessibilityObject::detach(detachmentType, cache);
- m_node = 0;
+ m_node = nullptr;
}
void AccessibilityNodeObject::childrenChanged()
@@ -149,11 +141,14 @@ void AccessibilityNodeObject::childrenChanged()
// In other words, they need to be sent even when the screen reader has not accessed this live region since the last update.
// If this element supports ARIA live regions, then notify the AT of changes.
+ // Sometimes this function can be called many times within a short period of time, leading to posting too many AXLiveRegionChanged
+ // notifications. To fix this, we used a timer to make sure we only post one notification for the children changes within a pre-defined
+ // time interval.
if (parent->supportsARIALiveRegion())
- cache->postNotification(parent, parent->document(), AXObjectCache::AXLiveRegionChanged);
+ cache->postLiveRegionChangeNotification(parent);
// If this element is an ARIA text control, notify the AT of changes.
- if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->hasEditableStyle())
+ if (parent->isNonNativeTextControl())
cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged);
}
}
@@ -171,12 +166,12 @@ void AccessibilityNodeObject::updateAccessibilityRole()
AccessibilityObject* AccessibilityNodeObject::firstChild() const
{
if (!node())
- return 0;
+ return nullptr;
Node* firstChild = node()->firstChild();
if (!firstChild)
- return 0;
+ return nullptr;
return axObjectCache()->getOrCreate(firstChild);
}
@@ -184,11 +179,11 @@ AccessibilityObject* AccessibilityNodeObject::firstChild() const
AccessibilityObject* AccessibilityNodeObject::lastChild() const
{
if (!node())
- return 0;
+ return nullptr;
Node* lastChild = node()->lastChild();
if (!lastChild)
- return 0;
+ return nullptr;
return axObjectCache()->getOrCreate(lastChild);
}
@@ -196,11 +191,11 @@ AccessibilityObject* AccessibilityNodeObject::lastChild() const
AccessibilityObject* AccessibilityNodeObject::previousSibling() const
{
if (!node())
- return 0;
+ return nullptr;
Node* previousSibling = node()->previousSibling();
if (!previousSibling)
- return 0;
+ return nullptr;
return axObjectCache()->getOrCreate(previousSibling);
}
@@ -208,11 +203,11 @@ AccessibilityObject* AccessibilityNodeObject::previousSibling() const
AccessibilityObject* AccessibilityNodeObject::nextSibling() const
{
if (!node())
- return 0;
+ return nullptr;
Node* nextSibling = node()->nextSibling();
if (!nextSibling)
- return 0;
+ return nullptr;
return axObjectCache()->getOrCreate(nextSibling);
}
@@ -225,20 +220,23 @@ AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const
AccessibilityObject* AccessibilityNodeObject::parentObject() const
{
if (!node())
- return 0;
+ return nullptr;
Node* parentObj = node()->parentNode();
- if (parentObj)
- return axObjectCache()->getOrCreate(parentObj);
+ if (!parentObj)
+ return nullptr;
- return 0;
+ if (AXObjectCache* cache = axObjectCache())
+ return cache->getOrCreate(parentObj);
+
+ return nullptr;
}
LayoutRect AccessibilityNodeObject::elementRect() const
{
return boundingBoxRect();
}
-
+
LayoutRect AccessibilityNodeObject::boundingBoxRect() const
{
// AccessibilityNodeObjects have no mechanism yet to return a size or position.
@@ -267,7 +265,7 @@ void AccessibilityNodeObject::setNode(Node* node)
Document* AccessibilityNodeObject::document() const
{
if (!node())
- return 0;
+ return nullptr;
return &node()->document();
}
@@ -276,52 +274,52 @@ AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
if (!node())
return UnknownRole;
- m_ariaRole = determineAriaRoleAttribute();
+ if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole)
+ return m_ariaRole;
- 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 (isHTMLInputElement(node())) {
- HTMLInputElement* input = toHTMLInputElement(node());
- if (input->isCheckbox())
+ if (is<HTMLInputElement>(*node())) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node());
+ if (input.isCheckbox())
return CheckBoxRole;
- if (input->isRadioButton())
+ if (input.isRadioButton())
return RadioButtonRole;
- if (input->isTextButton())
+ if (input.isTextButton())
return buttonRoleType();
- if (input->isRangeControl())
+ if (input.isRangeControl())
return SliderRole;
-
+ if (input.isInputTypeHidden())
+ return IgnoredRole;
+ if (input.isSearchField())
+ return SearchFieldRole;
#if ENABLE(INPUT_TYPE_COLOR)
- const AtomicString& type = input->getAttribute(typeAttr);
- if (equalIgnoringCase(type, "color"))
+ if (input.isColorControl())
return ColorWellRole;
#endif
-
return TextFieldRole;
}
if (node()->hasTagName(selectTag)) {
- HTMLSelectElement* selectElement = toHTMLSelectElement(node());
- return selectElement->multiple() ? ListBoxRole : PopUpButtonRole;
+ HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*node());
+ return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
}
- if (isHTMLTextAreaElement(node()))
+ if (is<HTMLTextAreaElement>(*node()))
return TextAreaRole;
if (headingLevel())
return HeadingRole;
+ if (node()->hasTagName(blockquoteTag))
+ return BlockquoteRole;
if (node()->hasTagName(divTag))
return DivRole;
if (node()->hasTagName(pTag))
return ParagraphRole;
- if (isHTMLLabelElement(node()))
+ if (is<HTMLLabelElement>(*node()))
return LabelRole;
- if (node()->isElementNode() && toElement(node())->isFocusable())
+ if (is<Element>(*node()) && downcast<Element>(*node()).isFocusable())
return GroupRole;
return UnknownRole;
@@ -397,11 +395,8 @@ bool AccessibilityNodeObject::canHaveChildren() const
case ListBoxOptionRole:
case ScrollBarRole:
case ProgressIndicatorRole:
+ case SwitchRole:
return false;
- case LegendRole:
- if (Element* element = this->element())
- return !ancestorsOfType<HTMLFieldSetElement>(*element).first();
- FALLTHROUGH;
default:
return true;
}
@@ -426,20 +421,28 @@ bool AccessibilityNodeObject::computeAccessibilityIsIgnored() const
if (!string.length())
return true;
}
-
+
+ AccessibilityObjectInclusion decision = defaultObjectInclusion();
+ if (decision == IncludeObject)
+ return false;
+ if (decision == IgnoreObject)
+ return true;
// If this element is within a parent that cannot have children, it should not be exposed.
if (isDescendantOfBarrenParent())
return true;
+ if (roleValue() == IgnoredRole)
+ return true;
+
return m_role == UnknownRole;
}
bool AccessibilityNodeObject::canvasHasFallbackContent() const
{
Node* node = this->node();
- if (!node || !node->hasTagName(canvasTag))
+ if (!is<HTMLCanvasElement>(node))
return false;
- Element& canvasElement = toElement(*node);
+ HTMLCanvasElement& canvasElement = downcast<HTMLCanvasElement>(*node);
// If it has any children that are elements, we'll assume it might be fallback
// content. If it has no children or its only children are not elements
// (e.g. just text nodes), it doesn't have fallback content.
@@ -451,23 +454,18 @@ 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 (isHTMLTextAreaElement(node))
+ if (is<HTMLTextAreaElement>(*node))
return true;
- if (isHTMLInputElement(node)) {
- HTMLInputElement* input = toHTMLInputElement(node);
- return input->isText() || input->isNumberField();
+ if (is<HTMLInputElement>(*node)) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node);
+ return input.isText() || input.isNumberField();
}
return false;
@@ -479,12 +477,13 @@ bool AccessibilityNodeObject::isSearchField() const
if (!node)
return false;
- HTMLInputElement* inputElement = node->toInputElement();
- if (!inputElement)
+ if (roleValue() == SearchFieldRole)
+ return true;
+
+ if (!is<HTMLInputElement>(*node))
return false;
- if (inputElement->isSearchField())
- return true;
+ auto& inputElement = downcast<HTMLInputElement>(*node);
// Some websites don't label their search fields as such. However, they will
// use the word "search" in either the form or input type. This won't catch every case,
@@ -496,7 +495,7 @@ bool AccessibilityNodeObject::isSearchField() const
return true;
// Check the form action and the name, which will sometimes be "search".
- HTMLFormElement* form = inputElement->form();
+ auto* form = inputElement.form();
if (form && (form->name().contains("search", false) || form->action().contains("search", false)))
return true;
@@ -509,15 +508,15 @@ bool AccessibilityNodeObject::isNativeImage() const
if (!node)
return false;
- if (isHTMLImageElement(node))
+ if (is<HTMLImageElement>(*node))
return true;
if (node->hasTagName(appletTag) || node->hasTagName(embedTag) || node->hasTagName(objectTag))
return true;
- if (isHTMLInputElement(node)) {
- HTMLInputElement* input = toHTMLInputElement(node);
- return input->isImageButton();
+ if (is<HTMLInputElement>(*node)) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node);
+ return input.isImageButton();
}
return false;
@@ -530,29 +529,41 @@ bool AccessibilityNodeObject::isImage() const
bool AccessibilityNodeObject::isPasswordField() const
{
- Node* node = this->node();
- if (!node || !node->isHTMLElement())
+ auto* node = this->node();
+ if (!is<HTMLInputElement>(node))
return false;
if (ariaRoleAttribute() != UnknownRole)
return false;
- HTMLInputElement* inputElement = node->toInputElement();
- if (!inputElement)
- return false;
+ return downcast<HTMLInputElement>(*node).isPasswordField();
+}
- return inputElement->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 (!node)
- return false;
-
- if (roleValue() == ButtonRole && isHTMLInputElement(node)) {
- HTMLInputElement* input = toHTMLInputElement(node);
- return input->isImageButton();
+ if (is<HTMLInputElement>(node) && roleValue() == ButtonRole) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node);
+ return input.isImageButton();
}
return false;
@@ -613,14 +624,11 @@ bool AccessibilityNodeObject::isMenuItem() const
bool AccessibilityNodeObject::isNativeCheckboxOrRadio() const
{
Node* node = this->node();
- if (!node)
+ if (!is<HTMLInputElement>(node))
return false;
- HTMLInputElement* input = node->toInputElement();
- if (input)
- return input->isCheckbox() || input->isRadioButton();
-
- return false;
+ auto& input = downcast<HTMLInputElement>(*node);
+ return input.isCheckbox() || input.isRadioButton();
}
bool AccessibilityNodeObject::isEnabled() const
@@ -628,30 +636,25 @@ bool AccessibilityNodeObject::isEnabled() const
// ARIA says that the disabled status applies to the current element and all descendant elements.
for (AccessibilityObject* object = const_cast<AccessibilityNodeObject*>(this); object; object = object->parentObject()) {
const AtomicString& disabledStatus = object->getAttribute(aria_disabledAttr);
- if (equalIgnoringCase(disabledStatus, "true"))
+ if (equalLettersIgnoringASCIICase(disabledStatus, "true"))
return false;
- if (equalIgnoringCase(disabledStatus, "false"))
+ if (equalLettersIgnoringASCIICase(disabledStatus, "false"))
break;
}
-
+
+ if (roleValue() == HorizontalRuleRole)
+ return false;
+
Node* node = this->node();
- if (!node || !node->isElementNode())
+ if (!is<Element>(node))
return true;
- return !toElement(node)->isDisabledFormControl();
+ return !downcast<Element>(*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();
+ return equalLettersIgnoringASCIICase(getAttribute(indeterminateAttr), "true");
}
bool AccessibilityNodeObject::isPressed() const
@@ -663,16 +666,13 @@ bool AccessibilityNodeObject::isPressed() const
if (!node)
return false;
- // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
- if (ariaRoleAttribute() == ButtonRole) {
- if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
- return true;
- return false;
- }
+ // If this is an toggle button, check the aria-pressed attribute rather than node()->active()
+ if (isToggleButton())
+ return equalLettersIgnoringASCIICase(getAttribute(aria_pressedAttr), "true");
- if (!node->isElementNode())
+ if (!is<Element>(*node))
return false;
- return toElement(node)->active();
+ return downcast<Element>(*node).active();
}
bool AccessibilityNodeObject::isChecked() const
@@ -682,9 +682,8 @@ bool AccessibilityNodeObject::isChecked() const
return false;
// First test for native checkedness semantics
- HTMLInputElement* inputElement = node->toInputElement();
- if (inputElement)
- return inputElement->shouldAppearChecked();
+ if (is<HTMLInputElement>(*node))
+ return downcast<HTMLInputElement>(*node).shouldAppearChecked();
// Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
bool validRole = false;
@@ -694,13 +693,14 @@ bool AccessibilityNodeObject::isChecked() const
case MenuItemRole:
case MenuItemCheckboxRole:
case MenuItemRadioRole:
+ case SwitchRole:
validRole = true;
break;
default:
break;
}
- if (validRole && equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
+ if (validRole && equalLettersIgnoringASCIICase(getAttribute(aria_checkedAttr), "true"))
return true;
return false;
@@ -709,53 +709,32 @@ bool AccessibilityNodeObject::isChecked() const
bool AccessibilityNodeObject::isHovered() const
{
Node* node = this->node();
- if (!node)
- return false;
-
- return node->isElementNode() && toElement(node)->hovered();
+ return is<Element>(node) && downcast<Element>(*node).hovered();
}
bool AccessibilityNodeObject::isMultiSelectable() const
{
const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
- if (equalIgnoringCase(ariaMultiSelectable, "true"))
+ if (equalLettersIgnoringASCIICase(ariaMultiSelectable, "true"))
return true;
- if (equalIgnoringCase(ariaMultiSelectable, "false"))
+ if (equalLettersIgnoringASCIICase(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 (isHTMLTextAreaElement(node))
- return toHTMLFormControlElement(node)->isReadOnly();
-
- if (isHTMLInputElement(node)) {
- HTMLInputElement* input = toHTMLInputElement(node);
- if (input->isTextField())
- return input->isReadOnly();
- }
-
- return !node->hasEditableStyle();
+ return node() && node()->hasTagName(selectTag) && downcast<HTMLSelectElement>(*node()).multiple();
}
bool AccessibilityNodeObject::isRequired() const
{
// Explicit aria-required values should trump native required attributes.
const AtomicString& requiredValue = getAttribute(aria_requiredAttr);
- if (equalIgnoringCase(requiredValue, "true"))
+ if (equalLettersIgnoringASCIICase(requiredValue, "true"))
return true;
- if (equalIgnoringCase(requiredValue, "false"))
+ if (equalLettersIgnoringASCIICase(requiredValue, "false"))
return false;
Node* n = this->node();
- if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
- return toHTMLFormControlElement(n)->isRequired();
+ if (is<HTMLFormControlElement>(n))
+ return downcast<HTMLFormControlElement>(*n).isRequired();
return false;
}
@@ -766,9 +745,11 @@ bool AccessibilityNodeObject::supportsRequiredAttribute() const
case ButtonRole:
return isFileUploadButton();
case CellRole:
+ case ColumnHeaderRole:
case CheckBoxRole:
case ComboBoxRole:
case GridRole:
+ case GridCellRole:
case IncrementorRole:
case ListBoxRole:
case PopUpButtonRole:
@@ -818,6 +799,11 @@ int AccessibilityNodeObject::headingLevel() const
if (node->hasTagName(h6Tag))
return 6;
+ // The implicit value of aria-level is 2 for the heading role.
+ // https://www.w3.org/TR/wai-aria-1.1/#heading
+ if (ariaRoleAttribute() == HeadingRole)
+ return 2;
+
return 0;
}
@@ -831,44 +817,62 @@ String AccessibilityNodeObject::valueDescription() const
float AccessibilityNodeObject::valueForRange() const
{
- if (node() && isHTMLInputElement(node())) {
- HTMLInputElement* input = toHTMLInputElement(node());
- if (input->isRangeControl())
- return input->valueAsNumber();
+ if (is<HTMLInputElement>(node())) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node());
+ if (input.isRangeControl())
+ return input.valueAsNumber();
}
if (!isRangeControl())
return 0.0f;
- return getAttribute(aria_valuenowAttr).toFloat();
+ // In ARIA 1.1, the implicit value for aria-valuenow on a spin button is 0.
+ // For other roles, it is half way between aria-valuemin and aria-valuemax.
+ auto value = getAttribute(aria_valuenowAttr);
+ if (!value.isEmpty())
+ return value.toFloat();
+
+ return isSpinButton() ? 0 : (minValueForRange() + maxValueForRange()) / 2;
}
float AccessibilityNodeObject::maxValueForRange() const
{
- if (node() && isHTMLInputElement(node())) {
- HTMLInputElement* input = toHTMLInputElement(node());
- if (input->isRangeControl())
- return input->maximum();
+ if (is<HTMLInputElement>(node())) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node());
+ if (input.isRangeControl())
+ return input.maximum();
}
if (!isRangeControl())
return 0.0f;
- return getAttribute(aria_valuemaxAttr).toFloat();
+ auto value = getAttribute(aria_valuemaxAttr);
+ if (!value.isEmpty())
+ return value.toFloat();
+
+ // In ARIA 1.1, the implicit value for aria-valuemax on a spin button
+ // is that there is no maximum value. For other roles, it is 100.
+ return isSpinButton() ? std::numeric_limits<float>::max() : 100.0f;
}
float AccessibilityNodeObject::minValueForRange() const
{
- if (node() && isHTMLInputElement(node())) {
- HTMLInputElement* input = toHTMLInputElement(node());
- if (input->isRangeControl())
- return input->minimum();
+ if (is<HTMLInputElement>(node())) {
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node());
+ if (input.isRangeControl())
+ return input.minimum();
}
if (!isRangeControl())
return 0.0f;
- return getAttribute(aria_valueminAttr).toFloat();
+ auto value = getAttribute(aria_valueminAttr);
+ if (!value.isEmpty())
+ return value.toFloat();
+
+ // In ARIA 1.1, the implicit value for aria-valuemin on a spin button
+ // is that there is no minimum value. For other roles, it is 0.
+ return isSpinButton() ? -std::numeric_limits<float>::max() : 0.0f;
}
float AccessibilityNodeObject::stepValueForRange() const
@@ -892,8 +896,7 @@ bool AccessibilityNodeObject::isControl() const
if (!node)
return false;
- return ((node->isElementNode() && toElement(node)->isFormControlElement())
- || AccessibilityObject::isARIAControl(ariaRoleAttribute()));
+ return is<HTMLFormControlElement>(*node) || AccessibilityObject::isARIAControl(ariaRoleAttribute());
}
bool AccessibilityNodeObject::isFieldset() const
@@ -928,12 +931,13 @@ AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
if (!isTabList())
return nullptr;
+ // FIXME: Is this valid? ARIA tab items support aria-selected; not aria-checked.
// Find the child tab item that is selected (ie. the intValue == 1).
AccessibilityObject::AccessibilityChildrenVector tabs;
tabChildren(tabs);
for (const auto& child : children()) {
- if (child->isTabItem() && child->isChecked())
+ if (child->isTabItem() && (child->isChecked() || child->isSelected()))
return child.get();
}
return nullptr;
@@ -942,7 +946,7 @@ AccessibilityObject* AccessibilityNodeObject::selectedTabItem()
AccessibilityButtonState AccessibilityNodeObject::checkboxOrRadioValue() const
{
if (isNativeCheckboxOrRadio())
- return isChecked() ? ButtonStateOn : ButtonStateOff;
+ return isIndeterminate() ? ButtonStateMixed : isChecked() ? ButtonStateOn : ButtonStateOff;
return AccessibilityObject::checkboxOrRadioValue();
}
@@ -951,25 +955,25 @@ Element* AccessibilityNodeObject::anchorElement() const
{
Node* node = this->node();
if (!node)
- return 0;
+ return nullptr;
AXObjectCache* cache = axObjectCache();
// search up the DOM tree for an anchor element
// NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
for ( ; node; node = node->parentNode()) {
- if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
- return toElement(node);
+ if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink()))
+ return downcast<Element>(node);
}
- return 0;
+ return nullptr;
}
static bool isNodeActionElement(Node* node)
{
- if (isHTMLInputElement(node)) {
- HTMLInputElement* input = toHTMLInputElement(node);
- if (!input->isDisabledFormControl() && (input->isRadioButton() || input->isCheckbox() || input->isTextButton() || input->isFileUpload() || input->isImageButton()))
+ 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;
@@ -980,7 +984,7 @@ static bool isNodeActionElement(Node* node)
static Element* nativeActionElement(Node* start)
{
if (!start)
- return 0;
+ return nullptr;
// Do a deep-dive to see if any nodes should be used as the action element.
// We have to look at Nodes, since this method should only be called on objects that do not have children (like buttons).
@@ -988,25 +992,25 @@ static Element* nativeActionElement(Node* start)
for (Node* child = start->firstChild(); child; child = child->nextSibling()) {
if (isNodeActionElement(child))
- return toElement(child);
+ return downcast<Element>(child);
if (Element* subChild = nativeActionElement(child))
return subChild;
}
- return 0;
+ return nullptr;
}
Element* AccessibilityNodeObject::actionElement() const
{
Node* node = this->node();
if (!node)
- return 0;
+ return nullptr;
if (isNodeActionElement(node))
- return toElement(node);
+ return downcast<Element>(node);
if (AccessibilityObject::isARIAInput(ariaRoleAttribute()))
- return toElement(node);
+ return downcast<Element>(node);
switch (roleValue()) {
case ButtonRole:
@@ -1020,7 +1024,7 @@ Element* AccessibilityNodeObject::actionElement() const
// Check if the author is hiding the real control element inside the ARIA element.
if (Element* nativeElement = nativeActionElement(node))
return nativeElement;
- return toElement(node);
+ return downcast<Element>(node);
default:
break;
}
@@ -1031,25 +1035,25 @@ Element* AccessibilityNodeObject::actionElement() const
return elt;
}
-Element* AccessibilityNodeObject::mouseButtonListener() const
+Element* AccessibilityNodeObject::mouseButtonListener(MouseButtonListenerResultFilter filter) const
{
Node* node = this->node();
if (!node)
- return 0;
+ return nullptr;
// check if our parent is a mouse button listener
// FIXME: Do the continuation search like anchorElement does
- for (auto& element : elementLineage(node->isElementNode() ? toElement(node) : node->parentElement())) {
- // If we've reached the body and this is not a control element, do not expose press action for this element.
- // It can cause false positives, where every piece of text is labeled as accepting press actions.
- if (element.hasTagName(bodyTag) && isStaticText())
+ for (auto& element : elementLineage(is<Element>(*node) ? downcast<Element>(node) : node->parentElement())) {
+ // If we've reached the body and this is not a control element, do not expose press action for this element unless filter is IncludeBodyElement.
+ // It can cause false positives, where every piece of text is labeled as accepting press actions.
+ if (element.hasTagName(bodyTag) && isStaticText() && filter == ExcludeBodyElement)
break;
if (element.hasEventListeners(eventNames().clickEvent) || element.hasEventListeners(eventNames().mousedownEvent) || element.hasEventListeners(eventNames().mouseupEvent))
return &element;
}
- return 0;
+ return nullptr;
}
bool AccessibilityNodeObject::isDescendantOfBarrenParent() const
@@ -1075,13 +1079,13 @@ void AccessibilityNodeObject::alterSliderValue(bool increase)
void AccessibilityNodeObject::increment()
{
- UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
+ UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
alterSliderValue(true);
}
void AccessibilityNodeObject::decrement()
{
- UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
+ UserGestureIndicator gestureIndicator(ProcessingUserGesture, document());
alterSliderValue(false);
}
@@ -1154,8 +1158,8 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const
HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
{
- if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
- return 0;
+ if (!is<HTMLElement>(*element) || !downcast<HTMLElement>(*element).isLabelable())
+ return nullptr;
const AtomicString& id = element->getIdAttribute();
if (!id.isEmpty()) {
@@ -1179,15 +1183,16 @@ String AccessibilityNodeObject::ariaAccessibilityDescription() const
return String();
}
-static Element* siblingWithAriaRole(String role, Node* node)
+static Element* siblingWithAriaRole(Node* node, const char* role)
{
+ // FIXME: Either we should add a null check here or change the function to take a reference instead of a pointer.
ContainerNode* parent = node->parentNode();
if (!parent)
return nullptr;
for (auto& sibling : childrenOfType<Element>(*parent)) {
- const AtomicString& siblingAriaRole = sibling.fastGetAttribute(roleAttr);
- if (equalIgnoringCase(siblingAriaRole, role))
+ // FIXME: Should skip sibling that is the same as the node.
+ if (equalIgnoringASCIICase(sibling.attributeWithoutSynchronization(roleAttr), role))
return &sibling;
}
@@ -1199,39 +1204,89 @@ Element* AccessibilityNodeObject::menuElementForMenuButton() const
if (ariaRoleAttribute() != MenuButtonRole)
return nullptr;
- return siblingWithAriaRole("menu", node());
+ return siblingWithAriaRole(node(), "menu");
}
AccessibilityObject* AccessibilityNodeObject::menuForMenuButton() const
{
- return axObjectCache()->getOrCreate(menuElementForMenuButton());
+ if (AXObjectCache* cache = axObjectCache())
+ return cache->getOrCreate(menuElementForMenuButton());
+ return nullptr;
}
Element* AccessibilityNodeObject::menuItemElementForMenu() const
{
if (ariaRoleAttribute() != MenuRole)
- return 0;
+ return nullptr;
- return siblingWithAriaRole("menuitem", node());
+ return siblingWithAriaRole(node(), "menuitem");
}
AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
{
+ AXObjectCache* cache = axObjectCache();
+ if (!cache)
+ return nullptr;
+
Element* menuItem = menuItemElementForMenu();
if (menuItem) {
// ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
- AccessibilityObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
+ AccessibilityObject* menuItemAX = cache->getOrCreate(menuItem);
if (menuItemAX && menuItemAX->isMenuButton())
return menuItemAX;
}
- return 0;
+ return nullptr;
+}
+
+AccessibilityObject* AccessibilityNodeObject::captionForFigure() const
+{
+ if (!isFigure())
+ 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);
+ // Check to see if there's aria-labelledby attribute on the label element.
+ if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label))
+ result = labelObject->ariaLabeledByAttribute();
+
+ // Then check for aria-label attribute.
+ if (result.isEmpty())
+ result = label->attributeWithoutSynchronization(aria_labelAttr);
+
+ return !result.isEmpty() ? result : label->innerText();
+}
void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder) const
{
@@ -1239,15 +1294,14 @@ void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOr
if (!node)
return;
- bool isInputTag = isHTMLInputElement(node);
- if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
- HTMLLabelElement* label = labelForElement(toElement(node));
- if (label) {
+ if (isLabelable()) {
+ if (HTMLLabelElement* label = labelForElement(downcast<Element>(node))) {
AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
- String innerText = label->innerText();
+ String innerText = textForLabelElement(label);
+
// Only use the <label> text if there's no ARIA override.
if (!innerText.isEmpty() && !ariaAccessibilityDescription())
- textOrder.append(AccessibilityText(innerText, LabelByElementText, labelObject));
+ textOrder.append(AccessibilityText(innerText, isMeter() ? AlternativeText : LabelByElementText, labelObject));
return;
}
}
@@ -1273,8 +1327,8 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
if (usesAltTagForTextComputation()) {
- if (renderer() && renderer()->isRenderImage()) {
- String renderAltText = toRenderImage(renderer())->altText();
+ if (is<RenderImage>(renderer())) {
+ String renderAltText = downcast<RenderImage>(*renderer()).altText();
// RenderImage will return title as a fallback from altText, but we don't want title here because we consider that in helpText.
if (!renderAltText.isEmpty() && renderAltText != getAttribute(titleAttr)) {
@@ -1294,17 +1348,22 @@ void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrd
return;
// The fieldset element derives its alternative text from the first associated legend element if one is available.
- if (isHTMLFieldSetElement(node)) {
- AccessibilityObject* object = axObjectCache()->getOrCreate(toHTMLFieldSetElement(node)->legend());
+ if (is<HTMLFieldSetElement>(*node)) {
+ AccessibilityObject* object = axObjectCache()->getOrCreate(downcast<HTMLFieldSetElement>(*node).legend());
if (object && !object->isHidden())
textOrder.append(AccessibilityText(accessibleNameForNode(object->node()), AlternativeText));
}
-#if ENABLE(SVG)
- // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
- if (node->isSVGElement())
- textOrder.append(AccessibilityText(toSVGElement(node)->title(), AlternativeText));
-#endif
+ // The figure element derives its alternative text from the first associated figcaption element if one is available.
+ if (isFigure()) {
+ AccessibilityObject* captionForFigure = this->captionForFigure();
+ if (captionForFigure && !captionForFigure->isHidden())
+ textOrder.append(AccessibilityText(accessibleNameForNode(captionForFigure->node()), AlternativeText));
+ }
+
+ // Tree items missing a label are labeled by all child elements.
+ if (isTreeItem() && ariaLabel.isEmpty() && ariaLabeledByAttribute().isEmpty())
+ textOrder.append(AccessibilityText(accessibleNameForNode(node), AlternativeText));
#if ENABLE(MATHML)
if (node->isMathMLElement())
@@ -1318,11 +1377,11 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
if (!node)
return;
- bool isInputTag = isHTMLInputElement(node);
+ bool isInputTag = is<HTMLInputElement>(*node);
if (isInputTag) {
- HTMLInputElement* input = toHTMLInputElement(node);
- if (input->isTextButton()) {
- textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText));
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node);
+ if (input.isTextButton()) {
+ textOrder.append(AccessibilityText(input.valueWithDefault(), VisibleText));
return;
}
}
@@ -1344,7 +1403,7 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
case CheckBoxRole:
case ListBoxOptionRole:
// MacOS does not expect native <li> elements to expose label information, it only expects leaf node elements to do that.
-#if !PLATFORM(MAC)
+#if !PLATFORM(COCOA)
case ListItemRole:
#endif
case MenuButtonRole:
@@ -1352,8 +1411,8 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
case MenuItemCheckboxRole:
case MenuItemRadioRole:
case RadioButtonRole:
+ case SwitchRole:
case TabRole:
- case ProgressIndicatorRole:
useTextUnderElement = true;
break;
default:
@@ -1365,6 +1424,9 @@ void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder)
if (isHeading() || isLink())
useTextUnderElement = true;
+ if (isOutput())
+ useTextUnderElement = true;
+
if (useTextUnderElement) {
AccessibilityTextUnderElementMode mode;
@@ -1387,27 +1449,23 @@ void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) con
String describedBy = ariaDescribedByAttribute();
if (!describedBy.isEmpty())
textOrder.append(AccessibilityText(describedBy, SummaryText));
-
- // Add help type text that is derived from ancestors.
- for (Node* curr = node(); curr; curr = curr->parentNode()) {
- const AtomicString& summary = getAttribute(summaryAttr);
- if (!summary.isEmpty())
- textOrder.append(AccessibilityText(summary, SummaryText));
-
- // The title attribute should be used as help text unless it is already being used as descriptive text.
- const AtomicString& title = getAttribute(titleAttr);
- if (!title.isEmpty())
+
+ // Summary attribute used as help text on tables.
+ const AtomicString& summary = getAttribute(summaryAttr);
+ if (!summary.isEmpty())
+ textOrder.append(AccessibilityText(summary, SummaryText));
+
+ // The title attribute should be used as help text unless it is already being used as descriptive text.
+ // However, when the title attribute is the only text alternative provided, it may be exposed as the
+ // descriptive text. This is problematic in the case of meters because the HTML spec suggests authors
+ // can expose units through this attribute. Therefore, if the element is a meter, change its source
+ // type to HelpText.
+ const AtomicString& title = getAttribute(titleAttr);
+ if (!title.isEmpty()) {
+ if (!isMeter())
textOrder.append(AccessibilityText(title, TitleTagText));
-
- // Only take help text from an ancestor element if its a group or an unknown role. If help was
- // added to those kinds of elements, it is likely it was meant for a child element.
- AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
- if (!axObj)
- return;
-
- AccessibilityRole role = axObj->roleValue();
- if (role != GroupRole && role != UnknownRole)
- break;
+ else
+ textOrder.append(AccessibilityText(title, HelpText));
}
}
@@ -1436,7 +1494,7 @@ void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textO
axElements.append(axElement);
}
- textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements));
+ textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, WTFMove(axElements)));
}
}
@@ -1458,30 +1516,26 @@ String AccessibilityNodeObject::alternativeTextForWebArea() const
// Check if the HTML element has an aria-label for the webpage.
if (Element* documentElement = document->documentElement()) {
- const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
+ const AtomicString& ariaLabel = documentElement->attributeWithoutSynchronization(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
}
- Node* owner = document->ownerElement();
- if (owner) {
+ if (auto* owner = document->ownerElement()) {
if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
- const AtomicString& title = toElement(owner)->getAttribute(titleAttr);
+ const AtomicString& title = owner->attributeWithoutSynchronization(titleAttr);
if (!title.isEmpty())
return title;
- return toElement(owner)->getNameAttribute();
}
- if (owner->isHTMLElement())
- return toHTMLElement(owner)->getNameAttribute();
+ return owner->getNameAttribute();
}
String documentTitle = document->title();
if (!documentTitle.isEmpty())
return documentTitle;
- owner = document->body();
- if (owner && owner->isHTMLElement())
- return toHTMLElement(owner)->getNameAttribute();
+ if (auto* body = document->bodyOrFrameset())
+ return body->getNameAttribute();
return String();
}
@@ -1503,15 +1557,9 @@ String AccessibilityNodeObject::accessibilityDescription() const
if (!alt.isNull())
return alt;
}
-
-#if ENABLE(SVG)
- // SVG elements all can have a <svg:title> element inside which should act as the descriptive text.
- if (m_node && m_node->isSVGElement())
- return toSVGElement(m_node)->title();
-#endif
#if ENABLE(MATHML)
- if (m_node && m_node->isMathMLElement())
+ if (is<MathMLElement>(m_node))
return getAttribute(MathMLNames::alttextAttr);
#endif
@@ -1540,21 +1588,22 @@ String AccessibilityNodeObject::helpText() const
return describedBy;
String description = accessibilityDescription();
- for (Node* curr = node; curr; curr = curr->parentNode()) {
- if (curr->isHTMLElement()) {
- const AtomicString& summary = toElement(curr)->getAttribute(summaryAttr);
+ 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;
// The title attribute should be used as help text unless it is already being used as descriptive text.
- const AtomicString& title = toElement(curr)->getAttribute(titleAttr);
+ const AtomicString& title = element.getAttribute(titleAttr);
if (!title.isEmpty() && description != title)
return title;
}
// Only take help text from an ancestor element if its a group or an unknown role. If help was
// added to those kinds of elements, it is likely it was meant for a child element.
- AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr);
+ AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor);
if (axObj) {
AccessibilityRole role = axObj->roleValue();
if (role != GroupRole && role != UnknownRole)
@@ -1568,10 +1617,10 @@ String AccessibilityNodeObject::helpText() const
unsigned AccessibilityNodeObject::hierarchicalLevel() const
{
Node* node = this->node();
- if (!node || !node->isElementNode())
+ if (!is<Element>(node))
return 0;
- Element* element = toElement(node);
- String ariaLevel = element->getAttribute(aria_levelAttr);
+ Element& element = downcast<Element>(*node);
+ const AtomicString& ariaLevel = element.attributeWithoutSynchronization(aria_levelAttr);
if (!ariaLevel.isEmpty())
return ariaLevel.toInt();
@@ -1583,7 +1632,7 @@ unsigned AccessibilityNodeObject::hierarchicalLevel() const
// We measure tree hierarchy by the number of groups that the item is within.
unsigned level = 1;
for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) {
- AccessibilityRole parentRole = parent->roleValue();
+ AccessibilityRole parentRole = parent->ariaRoleAttribute();
if (parentRole == GroupRole)
level++;
else if (parentRole == TreeRole)
@@ -1593,9 +1642,18 @@ unsigned AccessibilityNodeObject::hierarchicalLevel() const
return level;
}
+void AccessibilityNodeObject::setIsExpanded(bool expand)
+{
+ if (is<HTMLDetailsElement>(node())) {
+ auto& details = downcast<HTMLDetailsElement>(*node());
+ if (expand != details.isOpen())
+ details.toggleOpen();
+ }
+}
+
// When building the textUnderElement for an object, determine whether or not
// we should include the inner text of this given descendant object or skip it.
-static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
+static bool shouldUseAccessibilityObjectInnerText(AccessibilityObject* obj, AccessibilityTextUnderElementMode mode)
{
// Do not use any heuristic if we are explicitly asking to include all the children.
if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren)
@@ -1618,7 +1676,12 @@ static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, Acces
// quite long. As a heuristic, skip links, controls, and elements that are usually
// containers with lots of children.
- if (equalIgnoringCase(obj->getAttribute(aria_hiddenAttr), "true"))
+ // ARIA states that certain elements are not allowed to expose their children content for name calculation.
+ if (mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeNameFromContentsChildren
+ && !obj->accessibleNameDerivesFromContent())
+ return false;
+
+ if (equalLettersIgnoringASCIICase(obj->getAttribute(aria_hiddenAttr), "true"))
return false;
// If something doesn't expose any children, then we can always take the inner text content.
@@ -1631,13 +1694,19 @@ static bool shouldUseAccessiblityObjectInnerText(AccessibilityObject* obj, Acces
return false;
// Skip big container elements like lists, tables, etc.
- if (obj->isList() || obj->isAccessibilityTable() || obj->isTree() || obj->isCanvas())
+ 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, String& childText)
+static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, const String& childText)
{
if (!builder.length() || !childText.length())
return false;
@@ -1645,41 +1714,46 @@ static bool shouldAddSpaceBeforeAppendingNextElement(StringBuilder& builder, Str
// We don't need to add an additional space before or after a line break.
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 (node && node->isTextNode())
- return toText(node)->wholeText();
-
- // The render tree should be stable before going ahead. Otherwise, further uses of the
- // TextIterator will force a layout update, potentially altering the accessibility tree
- // and leading to crashes in the loop that computes the result text from the children.
- ASSERT(!document()->renderView()->layoutState());
- ASSERT(!document()->childNeedsStyleRecalc());
+ if (is<Text>(node))
+ return downcast<Text>(*node).wholeText();
StringBuilder builder;
for (AccessibilityObject* child = firstChild(); child; child = child->nextSibling()) {
- if (!shouldUseAccessiblityObjectInnerText(child, mode))
+ 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 (child->isAccessibilityNodeObject()) {
+ if (is<AccessibilityNodeObject>(*child)) {
Vector<AccessibilityText> textOrder;
- toAccessibilityNodeObject(child)->alternativeText(textOrder);
+ downcast<AccessibilityNodeObject>(*child).alternativeText(textOrder);
if (textOrder.size() > 0 && textOrder[0].text.length()) {
- if (shouldAddSpaceBeforeAppendingNextElement(builder, textOrder[0].text))
- builder.append(' ');
- builder.append(textOrder[0].text);
+ appendNameToStringBuilder(builder, textOrder[0].text);
continue;
}
}
-
+
String childText = child->textUnderElement(mode);
- if (childText.length()) {
- if (shouldAddSpaceBeforeAppendingNextElement(builder, childText))
- builder.append(' ');
- builder.append(childText);
- }
+ if (childText.length())
+ appendNameToStringBuilder(builder, childText);
}
return builder.toString().stripWhiteSpace().simplifyWhiteSpace(isHTMLSpaceButNotLineBreak);
@@ -1691,18 +1765,18 @@ String AccessibilityNodeObject::title() const
if (!node)
return String();
- bool isInputTag = isHTMLInputElement(node);
+ bool isInputTag = is<HTMLInputElement>(*node);
if (isInputTag) {
- HTMLInputElement* input = toHTMLInputElement(node);
- if (input->isTextButton())
- return input->valueWithDefault();
+ HTMLInputElement& input = downcast<HTMLInputElement>(*node);
+ if (input.isTextButton())
+ return input.valueWithDefault();
}
- if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
- HTMLLabelElement* label = labelForElement(toElement(node));
+ if (isLabelable()) {
+ HTMLLabelElement* label = labelForElement(downcast<Element>(node));
// Use the label text as the title if 1) the title element is NOT an exposed element and 2) there's no ARIA override.
if (label && !exposesTitleUIElement() && !ariaAccessibilityDescription().length())
- return label->innerText();
+ return textForLabelElement(label);
}
// If this node isn't rendered, there's no inner text we can extract from a select element.
@@ -1725,6 +1799,7 @@ String AccessibilityNodeObject::title() const
case MenuItemCheckboxRole:
case MenuItemRadioRole:
case RadioButtonRole:
+ case SwitchRole:
case TabRole:
return textUnderElement();
// SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
@@ -1759,13 +1834,13 @@ String AccessibilityNodeObject::text() const
if (!node)
return String();
- if (isNativeTextControl() && (isHTMLTextAreaElement(node) || isHTMLInputElement(node)))
- return toHTMLTextFormControlElement(node)->value();
+ if (isNativeTextControl() && is<HTMLTextFormControlElement>(*node))
+ return downcast<HTMLTextFormControlElement>(*node).value();
if (!node->isElementNode())
return String();
- return toElement(node)->innerText();
+ return downcast<Element>(node)->innerText();
}
String AccessibilityNodeObject::stringValue() const
@@ -1785,16 +1860,16 @@ String AccessibilityNodeObject::stringValue() const
return textUnderElement();
if (node->hasTagName(selectTag)) {
- HTMLSelectElement* selectElement = toHTMLSelectElement(node);
- int selectedIndex = selectElement->selectedIndex();
- const Vector<HTMLElement*> listItems = selectElement->listItems();
+ 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]->fastGetAttribute(aria_labelAttr);
+ const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr);
if (!overriddenDescription.isNull())
return overriddenDescription;
}
- if (!selectElement->multiple())
- return selectElement->value();
+ if (!selectElement.multiple())
+ return selectElement.value();
return String();
}
@@ -1814,73 +1889,103 @@ void AccessibilityNodeObject::colorValue(int& r, int& g, int& b) const
g = 0;
b = 0;
+#if ENABLE(INPUT_TYPE_COLOR)
if (!isColorWell())
return;
- if (!node() || !isHTMLInputElement(node()))
+ if (!is<HTMLInputElement>(node()))
return;
- HTMLInputElement* input = toHTMLInputElement(node());
- const AtomicString& type = input->getAttribute(typeAttr);
- if (!equalIgnoringCase(type, "color"))
+ auto& input = downcast<HTMLInputElement>(*node());
+ if (!input.isColorControl())
return;
// HTMLInputElement::value always returns a string parseable by Color().
- Color color(input->value());
+ Color color(input.value());
r = color.red();
g = color.green();
b = color.blue();
+#endif
}
// This function implements the ARIA accessible name as described by the Mozilla
// ARIA Implementer's Guide.
-static String accessibleNameForNode(Node* node)
+static String accessibleNameForNode(Node* node, Node* labelledbyNode)
{
- if (!node->isHTMLElement())
+ ASSERT(node);
+ if (!is<Element>(node))
return String();
- HTMLElement* element = toHTMLElement(node);
-
- const AtomicString& ariaLabel = element->fastGetAttribute(aria_labelAttr);
+ Element& element = downcast<Element>(*node);
+ const AtomicString& ariaLabel = element.attributeWithoutSynchronization(aria_labelAttr);
if (!ariaLabel.isEmpty())
return ariaLabel;
- const AtomicString& alt = element->fastGetAttribute(altAttr);
+ const AtomicString& alt = element.attributeWithoutSynchronization(altAttr);
if (!alt.isEmpty())
return alt;
-
- if (isHTMLInputElement(node))
- return toHTMLInputElement(node)->value();
-
+
// If the node can be turned into an AX object, we can use standard name computation rules.
// If however, the node cannot (because there's no renderer e.g.) fallback to using the basic text underneath.
AccessibilityObject* axObject = node->document().axObjectCache()->getOrCreate(node);
- String text;
- if (axObject)
- text = axObject->textUnderElement();
- else if (node->isElementNode())
- text = toElement(node)->innerText();
+ if (axObject) {
+ String valueDescription = axObject->valueDescription();
+ if (!valueDescription.isEmpty())
+ return valueDescription;
+ }
+
+ 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();
+
if (!text.isEmpty())
return text;
- const AtomicString& title = element->fastGetAttribute(titleAttr);
+ 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) {
- if (i)
- builder.append(' ');
-
- builder.append(accessibleNameForNode(elements[i]));
- }
+ for (unsigned i = 0; i < size; ++i)
+ appendNameToStringBuilder(builder, accessibleNameForNode(elements[i], node()));
return builder.toString();
}
@@ -1931,18 +2036,66 @@ bool AccessibilityNodeObject::canSetFocusAttribute() const
// NOTE: It would be more accurate to ask the document whether setFocusedElement() would
// do anything. For example, setFocusedElement() will do nothing if the current focused
// node will not relinquish the focus.
- if (!node)
+ if (!is<Element>(node))
return false;
- if (!node->isElementNode())
+ Element& element = downcast<Element>(*node);
+
+ if (element.isDisabledFormControl())
return false;
- Element* element = toElement(node);
+ return element.supportsFocus();
+}
- if (element->isDisabledFormControl())
+bool AccessibilityNodeObject::canSetValueAttribute() const
+{
+ Node* node = this->node();
+ if (!node)
return false;
- return element->supportsFocus();
+ // The host-language readonly attribute trumps aria-readonly.
+ 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 = ariaReadOnlyValue();
+ if (!readOnly.isEmpty())
+ return readOnly == "true" ? false : true;
+
+ if (isNonNativeTextControl())
+ return true;
+
+ if (isMeter())
+ return false;
+
+ if (isProgressIndicator() || isSlider())
+ return true;
+
+#if PLATFORM(GTK)
+ // In ATK, input types which support aria-readonly are treated as having a
+ // settable value if the user can modify the widget's value or its state.
+ if (supportsARIAReadOnly() || isRadioButton())
+ return 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
@@ -2008,22 +2161,12 @@ AccessibilityRole AccessibilityNodeObject::remapAriaRoleDueToParent(Accessibilit
return role;
}
-// If you call node->hasEditableStyle() since that will return true if an ancestor is editable.
-// This only returns true if this is the element that actually has the contentEditable attribute set.
-bool AccessibilityNodeObject::hasContentEditableAttributeSet() const
-{
- if (!hasAttribute(contenteditableAttr))
- return false;
- const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
- // Both "true" (case-insensitive) and the empty string count as true.
- return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
-}
-
bool AccessibilityNodeObject::canSetSelectedAttribute() const
{
// Elements that can be selected
switch (roleValue()) {
case CellRole:
+ case GridCellRole:
case RadioButtonRole:
case RowHeaderRole:
case RowRole:
@@ -2032,6 +2175,9 @@ bool AccessibilityNodeObject::canSetSelectedAttribute() const
case TreeGridRole:
case TreeItemRole:
case TreeRole:
+ case MenuItemCheckboxRole:
+ case MenuItemRadioRole:
+ case MenuItemRole:
return isEnabled();
default:
return false;