summaryrefslogtreecommitdiffstats
path: root/WebCore/accessibility/AccessibilityRenderObject.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2010-02-02 14:57:50 +0000
committerSteve Block <steveblock@google.com>2010-02-04 15:06:55 +0000
commitd0825bca7fe65beaee391d30da42e937db621564 (patch)
tree7461c49eb5844ffd1f35d1ba2c8b7584c1620823 /WebCore/accessibility/AccessibilityRenderObject.cpp
parent3db770bd97c5a59b6c7574ca80a39e5a51c1defd (diff)
downloadexternal_webkit-d0825bca7fe65beaee391d30da42e937db621564.zip
external_webkit-d0825bca7fe65beaee391d30da42e937db621564.tar.gz
external_webkit-d0825bca7fe65beaee391d30da42e937db621564.tar.bz2
Merge webkit.org at r54127 : Initial merge by git
Change-Id: Ib661abb595522f50ea406f72d3a0ce17f7193c82
Diffstat (limited to 'WebCore/accessibility/AccessibilityRenderObject.cpp')
-rw-r--r--WebCore/accessibility/AccessibilityRenderObject.cpp292
1 files changed, 241 insertions, 51 deletions
diff --git a/WebCore/accessibility/AccessibilityRenderObject.cpp b/WebCore/accessibility/AccessibilityRenderObject.cpp
index c57dd92..d738ca8 100644
--- a/WebCore/accessibility/AccessibilityRenderObject.cpp
+++ b/WebCore/accessibility/AccessibilityRenderObject.cpp
@@ -53,6 +53,7 @@
#include "HitTestResult.h"
#include "LocalizedStrings.h"
#include "NodeList.h"
+#include "ProgressTracker.h"
#include "RenderButton.h"
#include "RenderFieldset.h"
#include "RenderFileUploadControl.h"
@@ -64,6 +65,7 @@
#include "RenderMenuList.h"
#include "RenderText.h"
#include "RenderTextControl.h"
+#include "RenderTextFragment.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "RenderWidget.h"
@@ -85,6 +87,7 @@ AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer)
, m_renderer(renderer)
, m_ariaRole(UnknownRole)
, m_childrenDirty(false)
+ , m_roleForMSAA(UnknownRole)
{
updateAccessibilityRole();
#ifndef NDEBUG
@@ -362,11 +365,21 @@ bool AccessibilityRenderObject::isChecked() const
if (!m_renderer->node() || !m_renderer->node()->isElementNode())
return false;
+ // First test for native checkedness semantics
InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node()));
- if (!inputElement)
+ if (inputElement)
+ return inputElement->isChecked();
+
+ // Else, if this is an ARIA checkbox or radio, respect the aria-checked attribute
+ AccessibilityRole ariaRole = ariaRoleAttribute();
+ if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) {
+ if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
+ return true;
return false;
+ }
- return inputElement->isChecked();
+ // Otherwise it's not checked
+ return false;
}
bool AccessibilityRenderObject::isHovered() const
@@ -375,9 +388,16 @@ bool AccessibilityRenderObject::isHovered() const
return m_renderer->node() && m_renderer->node()->hovered();
}
-bool AccessibilityRenderObject::isMultiSelect() const
+bool AccessibilityRenderObject::isMultiSelectable() const
{
ASSERT(m_renderer);
+
+ const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
+ if (equalIgnoringCase(ariaMultiSelectable, "true"))
+ return true;
+ if (equalIgnoringCase(ariaMultiSelectable, "false"))
+ return false;
+
if (!m_renderer->isListBox())
return false;
return m_renderer->node() && static_cast<HTMLSelectElement*>(m_renderer->node())->multiple();
@@ -597,6 +617,10 @@ Element* AccessibilityRenderObject::actionElement() const
if (m_renderer->isMenuList())
return static_cast<Element*>(m_renderer->node());
+ AccessibilityRole role = roleValue();
+ if (role == ButtonRole || role == PopUpButtonRole)
+ return static_cast<Element*>(m_renderer->node());
+
Element* elt = anchorElement();
if (!elt)
elt = mouseButtonListener();
@@ -783,6 +807,14 @@ String AccessibilityRenderObject::textUnderElement() const
}
}
+ // Sometimes text fragments don't have Node's associated with them (like when
+ // CSS content is used to insert text).
+ if (m_renderer->isText()) {
+ RenderText* renderTextObject = toRenderText(m_renderer);
+ if (renderTextObject->isTextFragment())
+ return String(static_cast<RenderTextFragment*>(m_renderer)->contentString());
+ }
+
// return the null string for anonymous text because it is non-trivial to get
// the actual text and, so far, that is not needed
return String();
@@ -1131,7 +1163,10 @@ IntRect AccessibilityRenderObject::boundingBoxRect() const
obj = obj->node()->renderer();
Vector<FloatQuad> quads;
- obj->absoluteQuads(quads);
+ if (obj->isText())
+ obj->absoluteQuads(quads);
+ else
+ obj->absoluteFocusRingQuads(quads);
const size_t n = quads.size();
if (!n)
return IntRect();
@@ -1399,6 +1434,28 @@ bool AccessibilityRenderObject::isDescendantOfBarrenParent() const
return false;
}
+bool AccessibilityRenderObject::isAllowedChildOfTree() const
+{
+ // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline.
+ AccessibilityObject* axObj = parentObject();
+ bool isInTree = false;
+ while (axObj) {
+ if (axObj->isTree()) {
+ isInTree = true;
+ break;
+ }
+ axObj = axObj->parentObject();
+ }
+
+ // If the object is in a tree, only tree items should be exposed (and the children of tree items).
+ if (isInTree) {
+ AccessibilityRole role = roleValue();
+ if (role != TreeItemRole && role != StaticTextRole)
+ return false;
+ }
+ return true;
+}
+
bool AccessibilityRenderObject::accessibilityIsIgnored() const
{
// Is the platform interested in this object?
@@ -1426,6 +1483,10 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const
if (roleValue() == IgnoredRole)
return true;
+ // An ARIA tree can only have tree items and static text as children.
+ if (!isAllowedChildOfTree())
+ return true;
+
// ignore popup menu items because AppKit does
for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) {
if (parent->isMenuList())
@@ -1468,11 +1529,24 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const
if (isControl())
return false;
+ if (ariaRole != UnknownRole)
+ return false;
+
// don't ignore labels, because they serve as TitleUIElements
Node* node = m_renderer->node();
if (node && node->hasTagName(labelTag))
return false;
+ // Anything that is content editable should not be ignored.
+ // However, one cannot just call node->isContentEditable() since that will ask if its parents
+ // are also editable. Only the top level content editable region should be exposed.
+ if (node && node->isElementNode()) {
+ Element* element = static_cast<Element*>(node);
+ const AtomicString& contentEditable = element->getAttribute(contenteditableAttr);
+ if (equalIgnoringCase(contentEditable, "true"))
+ return false;
+ }
+
if (m_renderer->isBlockFlow() && m_renderer->childrenInline())
return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener();
@@ -1511,9 +1585,6 @@ bool AccessibilityRenderObject::accessibilityIsIgnored() const
return false;
}
- if (ariaRole != UnknownRole)
- return false;
-
// make a platform-specific decision
if (isAttachment())
return accessibilityIgnoreAttachment();
@@ -1526,6 +1597,21 @@ bool AccessibilityRenderObject::isLoaded() const
return !m_renderer->document()->tokenizer();
}
+double AccessibilityRenderObject::estimatedLoadingProgress() const
+{
+ if (!m_renderer)
+ return 0;
+
+ if (isLoaded())
+ return 1.0;
+
+ Page* page = m_renderer->document()->page();
+ if (!page)
+ return 0;
+
+ return page->progress()->estimatedProgress();
+}
+
int AccessibilityRenderObject::layoutCount() const
{
if (!m_renderer->isRenderView())
@@ -1717,23 +1803,28 @@ void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& at
element->setAttribute(attributeName, (value) ? "true" : "false");
}
-bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName)
+bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const
{
if (!m_renderer)
return false;
- return equalIgnoringCase(getAttribute(attributeName).string(), "true");
+ return equalIgnoringCase(getAttribute(attributeName), "true");
}
void AccessibilityRenderObject::setIsExpanded(bool isExpanded)
{
- // Combo boxes and tree items can be expanded (in different ways on different platforms).
+ // Combo boxes, tree items and rows can be expanded (in different ways on different platforms).
// That action translates into setting the aria-expanded attribute to true.
AccessibilityRole role = roleValue();
- if (role != ComboBoxRole && role != TreeItemRole)
- return;
-
- setElementAttributeValue(aria_expandedAttr, isExpanded);
+ switch (role) {
+ case ComboBoxRole:
+ case TreeItemRole:
+ case RowRole:
+ setElementAttributeValue(aria_expandedAttr, isExpanded);
+ break;
+ default:
+ break;
+ }
}
bool AccessibilityRenderObject::isRequired() const
@@ -1858,13 +1949,14 @@ void AccessibilityRenderObject::setSelected(bool enabled)
void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows)
{
- // Setting selected rows only works on trees for now.
- if (roleValue() != TreeRole)
+ // Setting selected only makes sense in trees and tables (and tree-tables).
+ AccessibilityRole role = roleValue();
+ if (role != TreeRole && role != TreeGridRole && role != TableRole)
return;
- bool isMultiselectable = elementAttributeValue(aria_multiselectableAttr);
+ bool isMulti = isMultiSelectable();
unsigned count = selectedRows.size();
- if (count > 1 && !isMultiselectable)
+ if (count > 1 && !isMulti)
count = 1;
for (unsigned k = 0; k < count; ++k)
@@ -1934,6 +2026,8 @@ RenderView* AccessibilityRenderObject::topRenderer() const
Document* AccessibilityRenderObject::document() const
{
+ if (!m_renderer)
+ return 0;
return m_renderer->document();
}
@@ -1957,24 +2051,14 @@ AXObjectCache* AccessibilityRenderObject::axObjectCache() const
AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const
{
// find an image that is using this map
- if (!m_renderer || !map)
+ if (!map)
return 0;
- String mapName = map->getName().string().lower();
- RefPtr<HTMLCollection> coll = m_renderer->document()->images();
- for (Node* curr = coll->firstItem(); curr; curr = coll->nextItem()) {
- RenderObject* obj = curr->renderer();
- if (!obj || !curr->hasTagName(imgTag))
- continue;
-
- // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning,
- // which has to be stripped off
- String useMapName = static_cast<HTMLImageElement*>(curr)->getAttribute(usemapAttr).string().substring(1).lower();
- if (useMapName == mapName)
- return axObjectCache()->getOrCreate(obj);
- }
+ HTMLImageElement* imageElement = map->imageElement();
+ if (!imageElement)
+ return 0;
- return 0;
+ return axObjectCache()->getOrCreate(imageElement->renderer());
}
void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result)
@@ -2538,6 +2622,10 @@ AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const
return UnknownRole;
AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
+
+ if (role == ButtonRole && elementAttributeValue(aria_haspopupAttr))
+ role = PopUpButtonRole;
+
if (role)
return role;
// selects and listboxes both have options as child roles, but they map to different roles within WebCore
@@ -2722,10 +2810,9 @@ bool AccessibilityRenderObject::canSetValueAttribute() const
if (equalIgnoringCase(getAttribute(aria_readonlyAttr).string(), "true"))
return false;
- if (isWebArea() || isTextControl())
- return !isReadOnly();
-
- return isProgressIndicator() || isSlider();
+ // Any node could be contenteditable, so isReadOnly should be relied upon
+ // for this information for all elements.
+ return isProgressIndicator() || isSlider() || !isReadOnly();
}
bool AccessibilityRenderObject::canSetTextRangeAttributes() const
@@ -2733,22 +2820,50 @@ bool AccessibilityRenderObject::canSetTextRangeAttributes() const
return isTextControl();
}
+void AccessibilityRenderObject::contentChanged()
+{
+ // If this element supports ARIA live regions, then notify the AT of changes.
+ for (RenderObject* renderParent = m_renderer->parent(); renderParent; renderParent = renderParent->parent()) {
+ AccessibilityObject* parent = m_renderer->document()->axObjectCache()->get(renderParent);
+ if (!parent)
+ continue;
+
+ // If we find a parent that has ARIA live region on, send the notification and stop processing.
+ // The spec does not talk about nested live regions.
+ if (parent->supportsARIALiveRegion()) {
+ axObjectCache()->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true);
+ break;
+ }
+ }
+}
+
void AccessibilityRenderObject::childrenChanged()
{
// this method is meant as a quick way of marking dirty
// a portion of the accessibility tree
- markChildrenDirty();
-
if (!m_renderer)
return;
// Go up the render parent chain, marking children as dirty.
// We can't rely on the accessibilityParent() because it may not exist and we must not create an AX object here either
- for (RenderObject* renderParent = m_renderer->parent(); renderParent; renderParent = renderParent->parent()) {
+ // At the same time, process ARIA live region changes.
+ for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) {
AccessibilityObject* parent = m_renderer->document()->axObjectCache()->get(renderParent);
- if (parent && parent->isAccessibilityRenderObject())
- static_cast<AccessibilityRenderObject *>(parent)->markChildrenDirty();
+ if (!parent || !parent->isAccessibilityRenderObject())
+ continue;
+
+ AccessibilityRenderObject* axParent = static_cast<AccessibilityRenderObject*>(parent);
+ // Only do work if the children haven't been marked dirty. This has the effect of blocking
+ // future live region change notifications until the AX tree has been accessed again. This
+ // is a good performance win for all parties.
+ if (!axParent->needsToUpdateChildren()) {
+ axParent->setNeedsToUpdateChildren();
+
+ // If this element supports ARIA live regions, then notify the AT of changes.
+ if (axParent->supportsARIALiveRegion())
+ axObjectCache()->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged, true);
+ }
}
}
@@ -2833,21 +2948,69 @@ void AccessibilityRenderObject::addChildren()
}
}
}
+
+const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const
+{
+ DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive"));
+ DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite"));
+ DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off"));
+
+ const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr);
+ // These roles have implicit live region status.
+ if (liveRegionStatus.isEmpty()) {
+ switch (roleValue()) {
+ case ApplicationAlertDialogRole:
+ case ApplicationAlertRole:
+ return liveRegionStatusAssertive;
+ case ApplicationLogRole:
+ case ApplicationStatusRole:
+ return liveRegionStatusPolite;
+ case ApplicationTimerRole:
+ return liveRegionStatusOff;
+ default:
+ break;
+ }
+ }
+
+ return liveRegionStatus;
+}
+
+const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const
+{
+ DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text"));
+ const AtomicString& relevant = getAttribute(aria_relevantAttr);
+
+ // Default aria-relevant = "additions text".
+ if (relevant.isEmpty())
+ return defaultLiveRegionRelevant;
+
+ return relevant;
+}
+
+bool AccessibilityRenderObject::ariaLiveRegionAtomic() const
+{
+ return elementAttributeValue(aria_atomicAttr);
+}
-void AccessibilityRenderObject::ariaTreeSelectedRows(AccessibilityChildrenVector& result)
+bool AccessibilityRenderObject::ariaLiveRegionBusy() const
+{
+ return elementAttributeValue(aria_busyAttr);
+}
+
+void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result)
{
// Get all the rows.
AccessibilityChildrenVector allRows;
ariaTreeRows(allRows);
// Determine which rows are selected.
- bool isMultiselectable = elementAttributeValue(aria_multiselectableAttr);
+ bool isMulti = isMultiSelectable();
// Prefer active descendant over aria-selected.
AccessibilityObject* activeDesc = activeDescendant();
- if (activeDesc && activeDesc->isTreeItem()) {
+ if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) {
result.append(activeDesc);
- if (!isMultiselectable)
+ if (!isMulti)
return;
}
@@ -2855,7 +3018,7 @@ void AccessibilityRenderObject::ariaTreeSelectedRows(AccessibilityChildrenVector
for (unsigned k = 0; k < count; ++k) {
if (allRows[k]->isSelected()) {
result.append(allRows[k]);
- if (!isMultiselectable)
+ if (!isMulti)
break;
}
}
@@ -2869,7 +3032,7 @@ void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildre
if (!element || !element->isElementNode()) // do this check to ensure safety of static_cast above
return;
- bool isMultiselectable = elementAttributeValue(aria_multiselectableAttr);
+ bool isMulti = isMultiSelectable();
while (child) {
// every child should have aria-role option, and if so, check for selected attribute/state
@@ -2883,7 +3046,7 @@ void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildre
String selectedAttrString = childElement->getAttribute(aria_selectedAttr).string();
if (equalIgnoringCase(selectedAttrString, "true")) {
result.append(child);
- if (isMultiselectable)
+ if (isMulti)
return;
}
}
@@ -2900,8 +3063,8 @@ void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& re
AccessibilityRole role = roleValue();
if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes
ariaListboxSelectedChildren(result);
- else if (role == TreeRole)
- ariaTreeSelectedRows(result);
+ else if (role == TreeRole || role == TreeGridRole || role == TableRole)
+ ariaSelectedRows(result);
}
void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result)
@@ -3074,4 +3237,31 @@ String AccessibilityRenderObject::descriptionForMSAA() const
return String();
}
+static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer)
+{
+ if (!renderer)
+ return UnknownRole;
+
+ if (renderer->isText())
+ return EditableTextRole;
+
+ if (renderer->isListItem())
+ return ListItemRole;
+
+ return UnknownRole;
+}
+
+AccessibilityRole AccessibilityRenderObject::roleValueForMSAA() const
+{
+ if (m_roleForMSAA != UnknownRole)
+ return m_roleForMSAA;
+
+ m_roleForMSAA = msaaRoleForRenderer(m_renderer);
+
+ if (m_roleForMSAA == UnknownRole)
+ m_roleForMSAA = roleValue();
+
+ return m_roleForMSAA;
+}
+
} // namespace WebCore