summaryrefslogtreecommitdiff
path: root/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@digia.com>2012-10-22 15:40:17 +0200
committerSimon Hausmann <simon.hausmann@digia.com>2012-10-22 15:40:17 +0200
commit43a42f108af6bcbd91f2672731c3047c26213af1 (patch)
tree7fa092e5f5d873c72f2486a70e26be26f7a38bec /Source/WebCore/accessibility/AccessibilityNodeObject.cpp
parentd9cf437c840c6eb7417bdd97e6c40979255d3158 (diff)
downloadqtwebkit-43a42f108af6bcbd91f2672731c3047c26213af1.tar.gz
Imported WebKit commit 302e7806bff028bd1167a1ec7c86a1ee00ecfb49 (http://svn.webkit.org/repository/webkit/trunk@132067)
New snapshot that fixes build without QtWidgets
Diffstat (limited to 'Source/WebCore/accessibility/AccessibilityNodeObject.cpp')
-rw-r--r--Source/WebCore/accessibility/AccessibilityNodeObject.cpp303
1 files changed, 281 insertions, 22 deletions
diff --git a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
index 0f94838e7..49b83ea6d 100644
--- a/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
+++ b/Source/WebCore/accessibility/AccessibilityNodeObject.cpp
@@ -59,6 +59,7 @@
#include "HTMLTextFormControlElement.h"
#include "HitTestRequest.h"
#include "HitTestResult.h"
+#include "LabelableElement.h"
#include "LocalizedStrings.h"
#include "MathMLNames.h"
#include "NodeList.h"
@@ -221,6 +222,26 @@ LayoutRect AccessibilityNodeObject::elementRect() const
{
return boundingBoxRect();
}
+
+LayoutRect AccessibilityNodeObject::boundingBoxRect() const
+{
+ // AccessibilityNodeObjects have no mechanism yet to return a size or position.
+ // For now, let's return the position of the ancestor that does have a position,
+ // and make it the width of that parent, and about the height of a line of text, so that it's clear the object is a child of the parent.
+
+ LayoutRect boundingBox;
+
+ for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
+ if (positionProvider->isAccessibilityRenderObject()) {
+ LayoutRect parentRect = positionProvider->elementRect();
+ boundingBox.setSize(LayoutSize(parentRect.width(), FractionalLayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
+ boundingBox.setLocation(parentRect.location());
+ break;
+ }
+ }
+
+ return boundingBox;
+}
void AccessibilityNodeObject::setNode(Node* node)
{
@@ -283,6 +304,32 @@ AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole()
return UnknownRole;
}
+void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index)
+{
+ if (!child)
+ return;
+
+ // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op),
+ // or its visibility has changed. In the latter case, this child may have a stale child cached.
+ // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale.
+ child->clearChildren();
+
+ if (child->accessibilityIsIgnored()) {
+ AccessibilityChildrenVector children = child->children();
+ size_t length = children.size();
+ for (size_t i = 0; i < length; ++i)
+ m_children.insert(index + i, children[i]);
+ } else {
+ ASSERT(child->parentObject() == this);
+ m_children.insert(index, child);
+ }
+}
+
+void AccessibilityNodeObject::addChild(AccessibilityObject* child)
+{
+ insertChild(child, m_children.size());
+}
+
void AccessibilityNodeObject::addChildren()
{
// If the need to add more children in addition to existing children arises,
@@ -298,19 +345,8 @@ void AccessibilityNodeObject::addChildren()
if (renderer() && !m_node->hasTagName(canvasTag))
return;
- for (Node* child = m_node->firstChild(); child; child = child->nextSibling()) {
- RefPtr<AccessibilityObject> obj = axObjectCache()->getOrCreate(child);
- obj->clearChildren();
- if (obj->accessibilityIsIgnored()) {
- AccessibilityChildrenVector children = obj->children();
- size_t length = children.size();
- for (size_t i = 0; i < length; ++i)
- m_children.append(children[i]);
- } else {
- ASSERT(obj->parentObject() == this);
- m_children.append(obj);
- }
- }
+ for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
+ addChild(axObjectCache()->getOrCreate(child));
}
bool AccessibilityNodeObject::canHaveChildren() const
@@ -1001,14 +1037,18 @@ bool AccessibilityNodeObject::isGenericFocusableElement() const
HTMLLabelElement* AccessibilityNodeObject::labelForElement(Element* element) const
{
- RefPtr<NodeList> list = element->document()->getElementsByTagName("label");
- unsigned len = list->length();
- for (unsigned i = 0; i < len; i++) {
- if (list->item(i)->hasTagName(labelTag)) {
- HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i));
- if (label->control() == element)
- return label;
- }
+ if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
+ return 0;
+
+ const AtomicString& id = element->getIdAttribute();
+ if (!id.isEmpty()) {
+ if (HTMLLabelElement* label = element->treeScope()->labelElementForId(id))
+ return label;
+ }
+
+ for (Element* parent = element->parentElement(); parent; parent = parent->parentElement()) {
+ if (parent->hasTagName(labelTag))
+ return static_cast<HTMLLabelElement*>(parent);
}
return 0;
@@ -1074,6 +1114,215 @@ AccessibilityObject* AccessibilityNodeObject::menuButtonForMenu() const
return 0;
}
+void AccessibilityNodeObject::titleElementText(Vector<AccessibilityText>& textOrder)
+{
+ Node* node = this->node();
+ if (!node)
+ return;
+
+ bool isInputTag = node->hasTagName(inputTag);
+ if (isInputTag || AccessibilityObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
+ HTMLLabelElement* label = labelForElement(toElement(node));
+ if (label) {
+ AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label);
+ textOrder.append(AccessibilityText(label->innerText(), LabelByElementText, labelObject));
+ return;
+ }
+ }
+
+ AccessibilityObject* titleUIElement = this->titleUIElement();
+ if (titleUIElement)
+ textOrder.append(AccessibilityText(String(), LabelByElementText, titleUIElement));
+}
+
+void AccessibilityNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
+{
+ if (isWebArea()) {
+ String webAreaText = alternativeTextForWebArea();
+ if (!webAreaText.isEmpty())
+ textOrder.append(AccessibilityText(webAreaText, AlternativeText));
+ return;
+ }
+
+ ariaLabeledByText(textOrder);
+
+ const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
+ if (!ariaLabel.isEmpty())
+ textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
+
+ if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
+ // Images should use alt as long as the attribute is present, even if empty.
+ // Otherwise, it should fallback to other methods, like the title attribute.
+ const AtomicString& alt = getAttribute(altAttr);
+ if (!alt.isNull())
+ textOrder.append(AccessibilityText(alt, AlternativeText));
+ }
+
+#if ENABLE(MATHML)
+ Node* node = this->node();
+ if (node && node->isElementNode() && toElement(node)->isMathMLElement())
+ textOrder.append(AccessibilityText(getAttribute(MathMLNames::alttextAttr), AlternativeText));
+#endif
+}
+
+void AccessibilityNodeObject::visibleText(Vector<AccessibilityText>& textOrder) const
+{
+ Node* node = this->node();
+ if (!node)
+ return;
+
+ bool isInputTag = node->hasTagName(inputTag);
+ if (isInputTag) {
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node);
+ if (input->isTextButton()) {
+ textOrder.append(AccessibilityText(input->valueWithDefault(), VisibleText));
+ return;
+ }
+ }
+
+ // If this node isn't rendered, there's no inner text we can extract from a select element.
+ if (!isAccessibilityRenderObject() && node->hasTagName(selectTag))
+ return;
+
+ bool useTextUnderElement = false;
+
+ switch (roleValue()) {
+ case PopUpButtonRole:
+ case ButtonRole:
+ case ToggleButtonRole:
+ case CheckBoxRole:
+ case ListBoxOptionRole:
+ case MenuButtonRole:
+ case MenuItemRole:
+ case RadioButtonRole:
+ case TabRole:
+ useTextUnderElement = true;
+ break;
+ default:
+ break;
+ }
+
+ // If it's focusable but it's not content editable or a known control type, then it will appear to
+ // the user as a single atomic object, so we should use its text as the default title.
+ if (isHeading() || isLink() || isGenericFocusableElement())
+ useTextUnderElement = true;
+
+ if (useTextUnderElement) {
+ String text = textUnderElement();
+ if (!text.isEmpty())
+ textOrder.append(AccessibilityText(text, ChildrenText));
+ }
+}
+
+void AccessibilityNodeObject::helpText(Vector<AccessibilityText>& textOrder) const
+{
+ const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
+ if (!ariaHelp.isEmpty())
+ textOrder.append(AccessibilityText(ariaHelp, HelpText));
+
+ String describedBy = ariaDescribedByAttribute();
+ if (!describedBy.isEmpty())
+ textOrder.append(AccessibilityText(describedBy, SummaryText));
+
+ // 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())
+ 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;
+ }
+}
+
+void AccessibilityNodeObject::accessibilityText(Vector<AccessibilityText>& textOrder)
+{
+ titleElementText(textOrder);
+ alternativeText(textOrder);
+ visibleText(textOrder);
+ helpText(textOrder);
+
+ String placeholder = placeholderValue();
+ if (!placeholder.isEmpty())
+ textOrder.append(AccessibilityText(placeholder, PlaceholderText));
+}
+
+void AccessibilityNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
+{
+ String ariaLabeledBy = ariaLabeledByAttribute();
+ if (!ariaLabeledBy.isEmpty()) {
+ Vector<Element*> elements;
+ ariaLabeledByElements(elements);
+
+ Vector<RefPtr<AccessibilityObject> > axElements;
+ unsigned length = elements.size();
+ for (unsigned k = 0; k < length; k++) {
+ RefPtr<AccessibilityObject> axElement = axObjectCache()->getOrCreate(elements[k]);
+ axElements.append(axElement);
+ }
+
+ textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElements));
+ }
+}
+
+String AccessibilityNodeObject::alternativeTextForWebArea() const
+{
+ // The WebArea description should follow this order:
+ // aria-label on the <html>
+ // title on the <html>
+ // <title> inside the <head> (of it was set through JS)
+ // name on the <html>
+ // For iframes:
+ // aria-label on the <iframe>
+ // title on the <iframe>
+ // name on the <iframe>
+
+ Document* document = this->document();
+ if (!document)
+ return String();
+
+ // Check if the HTML element has an aria-label for the webpage.
+ if (Element* documentElement = document->documentElement()) {
+ const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
+ if (!ariaLabel.isEmpty())
+ return ariaLabel;
+ }
+
+ Node* owner = document->ownerElement();
+ if (owner) {
+ if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) {
+ const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr);
+ if (!title.isEmpty())
+ return title;
+ return static_cast<HTMLFrameElementBase*>(owner)->getNameAttribute();
+ }
+ if (owner->isHTMLElement())
+ return toHTMLElement(owner)->getNameAttribute();
+ }
+
+ String documentTitle = document->title();
+ if (!documentTitle.isEmpty())
+ return documentTitle;
+
+ owner = document->body();
+ if (owner && owner->isHTMLElement())
+ return toHTMLElement(owner)->getNameAttribute();
+
+ return String();
+}
+
String AccessibilityNodeObject::accessibilityDescription() const
{
// Static text should not have a description, it should only have a stringValue.
@@ -1186,7 +1435,9 @@ String AccessibilityNodeObject::textUnderElement() const
// If this could be fixed, it'd be more accurate use TextIterator here.
if (node->isElementNode())
return toElement(node)->innerText();
-
+ else if (node->isTextNode())
+ return toText(node)->wholeText();
+
return String();
}
@@ -1341,6 +1592,14 @@ String AccessibilityNodeObject::accessibilityDescriptionForElements(Vector<Eleme
return builder.toString();
}
+String AccessibilityNodeObject::ariaDescribedByAttribute() const
+{
+ Vector<Element*> elements;
+ elementsFromAttribute(elements, aria_describedbyAttr);
+
+ return accessibilityDescriptionForElements(elements);
+}
+
void AccessibilityNodeObject::elementsFromAttribute(Vector<Element*>& elements, const QualifiedName& attribute) const
{
Node* node = this->node();