ScreenReader::ScreenReader(WebPage* page)
: m_page(page)
+ , m_focusedObject(0)
, m_hasFocus(false)
+ , m_isForward(true)
{
if (++s_readerCount) {
AXObjectCache::enableAccessibility();
--s_readerCount;
}
-static Node* traverseNode(Node* node, bool forward)
+RenderObject* ScreenReader::traverse(RenderObject* object)
{
- if (forward)
- return node->traverseNextNode();
- else
- return node->traversePreviousNode();
+ if (m_isForward) {
+ if (object->firstChild())
+ return object->firstChild();
+ } else {
+ if (object->lastChild())
+ return object->lastChild();
+ }
+
+ return traverseSibling(object);
}
-static Node* traverseSibling(Node* node, bool forward)
+RenderObject* ScreenReader::traverseSibling(RenderObject* object)
{
- if (forward)
- return node->traverseNextSibling();
- else
- return node->traversePreviousSibling();
+ if (m_isForward) {
+ do {
+ if (object->nextSibling())
+ return object->nextSibling();
+ } while ((object = object->parent()));
+ } else {
+ do {
+ if (object->previousSibling())
+ return object->previousSibling();
+ } while ((object = object->parent()));
+ }
+
+ return 0;
}
-static Node* traverseOwnerElementSibling(Node* node, bool forward)
+RenderObject* ScreenReader::ownerElementSibling(RenderObject* object)
{
- while ((node = node->document()->ownerElement())) {
- Node* next = traverseSibling(node, forward);
+ Node* node;
+ while ((node = object->document()->ownerElement())) {
+ object = node->renderer();
+ RenderObject* next = traverseSibling(object);
if (next)
return next;
}
return 0;
}
-static Node* contentDocumentBody(HTMLFrameOwnerElement* ownerElement)
+static RenderObject* contentDocumentBody(RenderObject* object)
{
- if (ownerElement->contentDocument())
- return ownerElement->contentDocument()->body();
- else
+ HTMLFrameOwnerElement* ownerElement = toFrameOwnerElement(object->node());
+ if (!ownerElement || !ownerElement->contentDocument())
return 0;
-}
-static Node* lastDescendant(Node* node)
-{
- while (node->hasChildNodes())
- node = node->lastChild();
- return node;
+ return ownerElement->contentDocument()->body()->renderer();
}
-static bool isVisible(AccessibilityObject* object)
+static RenderObject* lastLeafChild(RenderObject* object)
{
- if (!object || !object->renderer() || object->renderer()->style()->visibility() != VISIBLE)
- return false;
- IntRect boundingRect = object->pixelSnappedBoundingBoxRect();
- return (boundingRect.maxX() >= 0 && boundingRect.maxY() >= 0);
+ while (object->lastChild())
+ object = object->lastChild();
+ return object;
}
-static bool isAriaFocusable(Node* node)
+static AccessibilityObject* visibleAXObject(RenderObject* object)
{
- AccessibilityObject* object = node->document()->axObjectCache()->getOrCreate(node->renderer());
- if (!object)
- return false;
-
- switch (object->roleValue()) {
- case ButtonRole:
- case CheckBoxRole:
- case ComboBoxRole:
- case MenuRole:
- case MenuBarRole:
- case MenuItemRole:
- case LinkRole:
- case ListBoxOptionRole:
- case ProgressIndicatorRole:
- case RadioButtonRole:
- case ScrollBarRole:
- case SliderRole:
- case TreeRole:
- case TreeGridRole:
- case TreeItemRole:
- case WebCoreLinkRole:
- return true;
- default:
- break;
- }
- return false;
+ if (!object || !object->node() || object->style()->visibility() != VISIBLE)
+ return 0;
+ AccessibilityObject* axObject = object->document()->axObjectCache()->getOrCreate(object);
+ if (!axObject)
+ return 0;
+ IntRect boundingRect = axObject->pixelSnappedBoundingBoxRect();
+ return (boundingRect.maxX() > 0 && boundingRect.maxY() > 0) ? axObject : 0;
}
static bool isSpace(UChar character)
return true;
}
+static bool isAriaFocusable(Node* node)
+{
+ AccessibilityObject* axObject = node->document()->axObjectCache()->getOrCreate(node->renderer());
+ if (!axObject)
+ return false;
+
+ switch (axObject->roleValue()) {
+ case ButtonRole:
+ case CheckBoxRole:
+ case ComboBoxRole:
+ case MenuRole:
+ case MenuBarRole:
+ case MenuItemRole:
+ case LinkRole:
+ case ListBoxOptionRole:
+ case ProgressIndicatorRole:
+ case RadioButtonRole:
+ case ScrollBarRole:
+ case SliderRole:
+ case TreeRole:
+ case TreeGridRole:
+ case TreeItemRole:
+ case WebCoreLinkRole:
+ return true;
+ default:
+ break;
+ }
+
+ if (!containsOnlyWhitespace(axObject->ariaLabeledByAttribute())
+ || !containsOnlyWhitespace(axObject->getAttribute(aria_labelAttr)))
+ return true;
+
+ return false;
+}
+
static bool hasText(Node* node)
{
return !containsOnlyWhitespace(node->nodeValue());
static bool isAriaReadable(Node* node)
{
- AccessibilityObject* object = node->document()->axObjectCache()->getOrCreate(node->renderer());
- if (!object)
+ AccessibilityObject* axObject = node->document()->axObjectCache()->getOrCreate(node->renderer());
+ if (!axObject)
return false;
- switch (object->roleValue()) {
+ switch (axObject->roleValue()) {
case ImageRole:
return true;
default:
return true;
}
-static Node* findFocusableNode(Node* node, bool forward)
+static bool isAriaHidden(AccessibilityObject* axObject)
{
- node->document()->updateLayoutIgnorePendingStylesheets();
+ if (equalIgnoringCase(axObject->getAttribute(aria_hiddenAttr), "true"))
+ return true;
+ while ((axObject = axObject->parentObject())) {
+ if (equalIgnoringCase(axObject->getAttribute(aria_hiddenAttr), "true"))
+ return true;
+ }
+
+ return false;
+}
+
+RenderObject* ScreenReader::findFocusable(RenderObject* object)
+{
do {
- AccessibilityObject* object = node->document()->axObjectCache()->getOrCreate(node->renderer());
- if (!isVisible(object))
+ AccessibilityObject* axObject = visibleAXObject(object);
+ if (!axObject)
continue;
+ Node* node = object->node();
if (node->isFrameOwnerElement()) {
- Node* body = contentDocumentBody(toFrameOwnerElement(node));
+ RenderObject* body = contentDocumentBody(object);
if (body) {
- Node* result = findFocusableNode(forward ? body : lastDescendant(body), forward);
+ RenderObject* result = findFocusable(m_isForward ? body : lastLeafChild(body));
if (result)
return result;
}
- } else if (isFocusable(node) || isReadable(node))
- return node;
- } while ((node = traverseNode(node, forward)));
+ } else if ((isFocusable(node) || isReadable(node)) && !isAriaHidden(axObject))
+ return object;
+ } while ((object = traverse(object)));
return 0;
}
bool ScreenReader::moveFocus(bool forward)
{
- Node* node;
- if (!m_focusedNode) {
- node = m_page->mainFrame()->document()->body();
+ m_isForward = forward;
+ m_page->mainFrame()->document()->updateLayoutIgnorePendingStylesheets();
+
+ RenderObject* object;
+ if (!m_focusedObject) {
+ object = m_page->mainFrame()->document()->body()->renderer();
if (!forward)
- node = lastDescendant(node);
+ object = lastLeafChild(object);
} else {
- node = traverseNode(m_focusedNode.get(), forward);
- if (!node)
- node = traverseOwnerElementSibling(m_focusedNode.get(), forward);
+ object = traverse(m_focusedObject);
+ if (!object)
+ object = ownerElementSibling(m_focusedObject);
}
- if (!node) {
+ if (!object) {
clearFocus();
return false;
}
- Node* candidate;
+ RenderObject* candidate;
do {
- candidate = findFocusableNode(node, forward);
+ candidate = findFocusable(object);
if (candidate)
break;
- } while ((node = traverseOwnerElementSibling(node, forward)));
+ } while ((object = ownerElementSibling(object)));
if (!candidate) {
clearFocus();
return false;
}
- node = candidate;
+ Node* node = candidate->node();
while (node) {
node->renderer()->scrollRectToVisible(node->getRect(), ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
node = node->document()->ownerElement();
for (; it != end; ++it) {
for (Node* node = it->get(); node; node = node->parentOrHostNode()) {
- if (isFocusable(node) || isReadable(node))
- candidates.add(node);
+ if (isFocusable(node) || isReadable(node)) {
+ AccessibilityObject* object = node->document()->axObjectCache()->getOrCreate(node->renderer());
+ if (object && !isAriaHidden(object))
+ candidates.add(node);
+ }
}
}
bool ScreenReader::moveFocus(const IntPoint& point)
{
Frame* mainFrame = m_page->mainFrame();
- FrameView* view = mainFrame->view();
- view->updateLayoutAndStyleIfNeededRecursive();
+ mainFrame->document()->updateLayoutIgnorePendingStylesheets();
- IntPoint hitTestPoint = view->windowToContents(point);
+ IntPoint hitTestPoint = mainFrame->view()->windowToContents(point);
HitTestResult result = mainFrame->eventHandler()->hitTestResultAtPoint(hitTestPoint, false, false, DontHitTestScrollbars, HitTestRequest::ReadOnly | HitTestRequest::Active, s_hitTestPadding);
Node* candidate = findShortestDistanceNode(hitTestPoint, result.rectBasedTestResult());
- if (!candidate || candidate == m_focusedNode)
+ if (!candidate || candidate->renderer() == m_focusedObject)
return false;
- setFocus(candidate);
+ setFocus(candidate->renderer());
return true;
}
{
switch (roleValue) {
case ButtonRole:
+ case PopUpButtonRole:
return "button";
case CheckBoxRole:
return "check box";
case ImageRole:
return "image";
case LinkRole:
+ case WebCoreLinkRole:
return "link";
case ListBoxOptionRole:
return "list box option";
case ProgressIndicatorRole:
- return "progress bar";
+ return "";
case RadioButtonRole:
return "radio button";
case SliderRole:
return "slider";
- case WebCoreLinkRole:
- return "link";
+ case TabRole:
+ return "tab";
default:
break;
}
static bool addNodeText(Node* node, bool hasSubtreeText, Vector<String>& textList)
{
- AccessibilityObject* object = node->document()->axObjectCache()->getOrCreate(node->renderer());
- if (!isVisible(object))
+ AccessibilityObject* axObject = visibleAXObject(node->renderer());
+ if (!axObject || isAriaHidden(axObject))
return hasSubtreeText;
if (node->isElementNode()) {
Element* element = toElement(node);
- String label, text, more;
+ String type, text, state;
if (element->isLink())
- label = "link";
+ type = "link";
else if (element->isFormControlElement()) {
if (element->hasTagName(HTMLNames::buttonTag)) {
- label = static_cast<HTMLFormControlElement*>(element)->type();
+ type = static_cast<HTMLFormControlElement*>(element)->type();
text = static_cast<HTMLButtonElement*>(element)->value();
} else if (element->hasTagName(HTMLNames::fieldsetTag))
- label = static_cast<HTMLFormControlElement*>(element)->type();
+ type = static_cast<HTMLFormControlElement*>(element)->type();
else if (element->hasTagName(HTMLNames::inputTag)) {
- label = "edit field";
+ type = "edit field";
text = static_cast<HTMLInputElement*>(element)->alt();
if (text.isEmpty())
text = static_cast<HTMLInputElement*>(element)->value();
} else if (element->hasTagName(HTMLNames::keygenTag))
- label = static_cast<HTMLFormControlElement*>(element)->type();
+ type = static_cast<HTMLFormControlElement*>(element)->type();
else if (element->hasTagName(HTMLNames::outputTag)) {
- label = static_cast<HTMLFormControlElement*>(element)->type();
+ type = static_cast<HTMLFormControlElement*>(element)->type();
text = static_cast<HTMLOutputElement*>(element)->value();
} else if (element->hasTagName(HTMLNames::selectTag)) {
- label = static_cast<HTMLFormControlElement*>(element)->type();
+ type = static_cast<HTMLFormControlElement*>(element)->type();
text = static_cast<HTMLSelectElement*>(element)->value();
if (static_cast<HTMLSelectElement*>(element)->size() == 1)
- more = "1 item";
+ state = "1 item";
else
- more = String::format("%d items", static_cast<HTMLSelectElement*>(element)->size());
+ state = String::format("%d items", static_cast<HTMLSelectElement*>(element)->size());
} else if (element->hasTagName(HTMLNames::textareaTag)) {
- label = "edit field";
+ type = "edit field";
text = static_cast<HTMLTextAreaElement*>(element)->value();
}
} else if (element->hasTagName(HTMLNames::imgTag)) {
- label = "image";
+ type = "image";
text = static_cast<HTMLImageElement*>(element)->altText();
} else if (element->hasTagName(HTMLNames::optionTag)) {
text = static_cast<HTMLOptionElement*>(element)->label();
if (static_cast<HTMLOptionElement*>(element)->selected())
- more = "selected";
+ state = "selected";
}
- String ariaText = object->ariaLabeledByAttribute();
- if (!containsOnlyWhitespace(ariaText))
- label = ariaText;
- else if (!containsOnlyWhitespace((ariaText = element->fastGetAttribute(aria_labelAttr))))
- label = ariaText;
- else if (!containsOnlyWhitespace((ariaText = ariaRoleText(object->roleValue()))))
- label = ariaText;
-
- if (!containsOnlyWhitespace((ariaText = object->ariaDescribedByAttribute())))
- text = ariaText;
- else if (text.isEmpty() && node->isHTMLElement()) {
+ if (text.isEmpty() && node->isHTMLElement()) {
const AtomicString& title = element->fastGetAttribute(HTMLNames::titleAttr);
if (!containsOnlyWhitespace(title))
text = title;
}
- if (text.isEmpty() && label.isEmpty())
+ if (element->fastHasAttribute(HTMLNames::roleAttr))
+ type = element->fastGetAttribute(HTMLNames::roleAttr).isEmpty()? emptyString() : ariaRoleText(axObject->roleValue());
+
+ String more, ariaText;
+ if (!containsOnlyWhitespace((ariaText = axObject->ariaDescribedByAttribute())))
+ more = ariaText;
+ else if (!containsOnlyWhitespace((ariaText = axObject->ariaLabeledByAttribute())))
+ more = ariaText;
+ else if (!containsOnlyWhitespace((ariaText = element->fastGetAttribute(aria_labelAttr))))
+ more = ariaText;
+
+ if (text.isEmpty() && type.isEmpty())
return hasSubtreeText;
+ if (axObject->isSelected())
+ state = "selected";
+ else if (axObject->isChecked())
+ state = "checked";
+
if (!text.isEmpty())
textList.append(text);
- if (!label.isEmpty())
- textList.append(label);
+ if (!type.isEmpty())
+ textList.append(type);
+ if (!state.isEmpty())
+ textList.append(state);
if (!more.isEmpty())
textList.append(more);
} else if (node->isTextNode() && hasText(node))
else
return hasSubtreeText;
- if (!object->isEnabled())
+ if (!axObject->isEnabled())
textList.append("disabled");
return true;
return text.toString().simplifyWhiteSpace();
}
-bool ScreenReader::setFocus(Node* node)
+bool ScreenReader::setFocus(RenderObject* object)
{
- m_focusedNode = node;
+ m_focusedObject = object;
m_hasFocus = true;
- String text = subtreeText(node);
+ String text = subtreeText(object->node());
if (text.isEmpty())
return false;
{
if (!m_hasFocus)
return 0;
- return m_focusedNode.get();
+
+ return m_focusedObject->node();
}
-bool ScreenReader::nodeWillBeRemoved(Node* node)
+bool ScreenReader::rendererWillBeDestroyed(RenderObject* object)
{
- if (!node->contains(m_focusedNode.get()))
+ if (m_focusedObject != object)
return false;
clearFocus();
- m_focusedNode = traverseNode(node, false);
+ m_isForward = false;
+ m_focusedObject = traverse(object);
return true;
}
void ScreenReader::clearFocus()
{
- m_focusedNode = 0;
+ m_focusedObject = 0;
m_hasFocus = false;
m_page->send(Messages::WebPageProxy::DidScreenReaderTextChanged(emptyString()));
}