From 315c188c67cfe82338b5e1b60c41c11882da7981 Mon Sep 17 00:00:00 2001 From: SangYong Park Date: Tue, 19 Feb 2013 08:33:01 +0900 Subject: [PATCH] Change traversing tree to render tree in screen reader. [Title] Change traversing tree to render tree in screen reader. [Issue#] N/A [Problem] [Cause] [Solution] Change-Id: Ic4c426d8fc0044ca78a13fc0a7e9018a1d5868e2 --- Source/WebCore/dom/Document.cpp | 3 - Source/WebCore/page/ChromeClient.h | 2 +- Source/WebCore/rendering/RenderObject.cpp | 5 + .../WebProcess/WebCoreSupport/WebChromeClient.cpp | 4 +- .../WebProcess/WebCoreSupport/WebChromeClient.h | 2 +- Source/WebKit2/WebProcess/WebPage/WebPage.h | 2 +- .../WebKit2/WebProcess/WebPage/efl/WebPageEfl.cpp | 6 +- .../WebProcess/WebPage/efl/tizen/ScreenReader.cpp | 310 ++++++++++++--------- .../WebProcess/WebPage/efl/tizen/ScreenReader.h | 14 +- 9 files changed, 207 insertions(+), 141 deletions(-) diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index 965a879..4b9e5f5 100755 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -3995,9 +3995,6 @@ void Document::nodeWillBeRemoved(Node* n) frame->eventHandler()->nodeWillBeRemoved(n); frame->selection()->nodeWillBeRemoved(n); frame->page()->dragCaretController()->nodeWillBeRemoved(n); -#if ENABLE(TIZEN_SCREEN_READER) - frame->page()->chrome()->client()->nodeWillBeRemoved(n); -#endif } } diff --git a/Source/WebCore/page/ChromeClient.h b/Source/WebCore/page/ChromeClient.h index cb327b5..39759da 100755 --- a/Source/WebCore/page/ChromeClient.h +++ b/Source/WebCore/page/ChromeClient.h @@ -395,7 +395,7 @@ namespace WebCore { #endif #if ENABLE(TIZEN_SCREEN_READER) - virtual void nodeWillBeRemoved(Node*) { } + virtual void rendererWillBeDestroyed(RenderObject*) { } #endif protected: diff --git a/Source/WebCore/rendering/RenderObject.cpp b/Source/WebCore/rendering/RenderObject.cpp index 45f58ef..eb2a012 100755 --- a/Source/WebCore/rendering/RenderObject.cpp +++ b/Source/WebCore/rendering/RenderObject.cpp @@ -2307,6 +2307,11 @@ void RenderObject::willBeDestroyed() if (frame() && frame()->eventHandler()->autoscrollRenderer() == this) frame()->eventHandler()->stopAutoscrollTimer(true); +#if ENABLE(TIZEN_SCREEN_READER) + if (frame()) + frame()->page()->chrome()->client()->rendererWillBeDestroyed(this); +#endif + if (AXObjectCache::accessibilityEnabled()) { document()->axObjectCache()->childrenChanged(this->parent()); document()->axObjectCache()->remove(this); diff --git a/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.cpp b/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.cpp index edd8dd4..ed999b1 100755 --- a/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.cpp +++ b/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.cpp @@ -965,9 +965,9 @@ float WebChromeClient::contentsScaleFactor() const #endif #if ENABLE(TIZEN_SCREEN_READER) -void WebChromeClient::nodeWillBeRemoved(Node* node) +void WebChromeClient::rendererWillBeDestroyed(RenderObject* object) { - m_page->updateScreenReaderFocus(node); + m_page->updateScreenReaderFocus(object); } #endif diff --git a/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.h b/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.h index 86e9a75..b990992 100755 --- a/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.h +++ b/Source/WebKit2/WebProcess/WebCoreSupport/WebChromeClient.h @@ -258,7 +258,7 @@ private: #endif #if ENABLE(TIZEN_SCREEN_READER) - virtual void nodeWillBeRemoved(WebCore::Node*); + virtual void rendererWillBeDestroyed(WebCore::RenderObject*); #endif String m_cachedToolTip; diff --git a/Source/WebKit2/WebProcess/WebPage/WebPage.h b/Source/WebKit2/WebProcess/WebPage/WebPage.h index f39ea2a..44eaa42 100755 --- a/Source/WebKit2/WebProcess/WebPage/WebPage.h +++ b/Source/WebKit2/WebProcess/WebPage/WebPage.h @@ -756,7 +756,7 @@ public: void moveScreenReaderFocus(bool, bool&); void moveScreenReaderFocusByPoint(const WebCore::IntPoint&); void recalcScreenReaderFocusRect(); - void updateScreenReaderFocus(WebCore::Node*); + void updateScreenReaderFocus(WebCore::RenderObject*); void clearScreenReader(); #endif diff --git a/Source/WebKit2/WebProcess/WebPage/efl/WebPageEfl.cpp b/Source/WebKit2/WebProcess/WebPage/efl/WebPageEfl.cpp index 93530d1..8e1a7ca 100755 --- a/Source/WebKit2/WebProcess/WebPage/efl/WebPageEfl.cpp +++ b/Source/WebKit2/WebProcess/WebPage/efl/WebPageEfl.cpp @@ -1537,14 +1537,14 @@ void WebPage::recalcScreenReaderFocusRect() sendScreenReaderFocusRect(this, m_screenReader->getFocusedNode()); } -void WebPage::updateScreenReaderFocus(Node* willRemoveNode) +void WebPage::updateScreenReaderFocus(RenderObject* object) { if (!m_screenReader) return; - if (!willRemoveNode) + if (!object) m_screenReader->clearFocus(); - else if (!m_screenReader->nodeWillBeRemoved(willRemoveNode)) + else if (!m_screenReader->rendererWillBeDestroyed(object)) return; send(Messages::WebPageProxy::DidScreenReaderFocusRectChanged(IntRect())); diff --git a/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.cpp b/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.cpp index d2584ed..67b8ec3 100755 --- a/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.cpp +++ b/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.cpp @@ -58,7 +58,9 @@ IntSize ScreenReader::s_hitTestPadding = IntSize(); ScreenReader::ScreenReader(WebPage* page) : m_page(page) + , m_focusedObject(0) , m_hasFocus(false) + , m_isForward(true) { if (++s_readerCount) { AXObjectCache::enableAccessibility(); @@ -73,26 +75,42 @@ ScreenReader::~ScreenReader() --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; } @@ -100,57 +118,31 @@ static Node* traverseOwnerElementSibling(Node* node, bool forward) 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) @@ -182,6 +174,41 @@ static bool containsOnlyWhitespace(const String& string) 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()); @@ -196,11 +223,11 @@ static bool isFocusable(Node* node) 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: @@ -222,60 +249,75 @@ static bool isReadable(Node* node) 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(); @@ -295,8 +337,11 @@ static Node* findShortestDistanceNode(const IntPoint& point, const HitTestResult 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); + } } } @@ -332,17 +377,16 @@ static Node* findShortestDistanceNode(const IntPoint& point, const HitTestResult 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; } @@ -351,6 +395,7 @@ static String ariaRoleText(AccessibilityRole roleValue) { switch (roleValue) { case ButtonRole: + case PopUpButtonRole: return "button"; case CheckBoxRole: return "check box"; @@ -359,17 +404,18 @@ static String ariaRoleText(AccessibilityRole roleValue) 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; } @@ -378,75 +424,83 @@ static String ariaRoleText(AccessibilityRole roleValue) static bool addNodeText(Node* node, bool hasSubtreeText, Vector& 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(element)->type(); + type = static_cast(element)->type(); text = static_cast(element)->value(); } else if (element->hasTagName(HTMLNames::fieldsetTag)) - label = static_cast(element)->type(); + type = static_cast(element)->type(); else if (element->hasTagName(HTMLNames::inputTag)) { - label = "edit field"; + type = "edit field"; text = static_cast(element)->alt(); if (text.isEmpty()) text = static_cast(element)->value(); } else if (element->hasTagName(HTMLNames::keygenTag)) - label = static_cast(element)->type(); + type = static_cast(element)->type(); else if (element->hasTagName(HTMLNames::outputTag)) { - label = static_cast(element)->type(); + type = static_cast(element)->type(); text = static_cast(element)->value(); } else if (element->hasTagName(HTMLNames::selectTag)) { - label = static_cast(element)->type(); + type = static_cast(element)->type(); text = static_cast(element)->value(); if (static_cast(element)->size() == 1) - more = "1 item"; + state = "1 item"; else - more = String::format("%d items", static_cast(element)->size()); + state = String::format("%d items", static_cast(element)->size()); } else if (element->hasTagName(HTMLNames::textareaTag)) { - label = "edit field"; + type = "edit field"; text = static_cast(element)->value(); } } else if (element->hasTagName(HTMLNames::imgTag)) { - label = "image"; + type = "image"; text = static_cast(element)->altText(); } else if (element->hasTagName(HTMLNames::optionTag)) { text = static_cast(element)->label(); if (static_cast(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)) @@ -454,7 +508,7 @@ static bool addNodeText(Node* node, bool hasSubtreeText, Vector& textLis else return hasSubtreeText; - if (!object->isEnabled()) + if (!axObject->isEnabled()) textList.append("disabled"); return true; @@ -488,12 +542,12 @@ static String subtreeText(Node* top) 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; @@ -506,23 +560,25 @@ Node* ScreenReader::getFocusedNode() { 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())); } diff --git a/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.h b/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.h index b9ea56e..b1f30cc 100755 --- a/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.h +++ b/Source/WebKit2/WebProcess/WebPage/efl/tizen/ScreenReader.h @@ -34,6 +34,7 @@ namespace WebCore { class IntPoint; class Node; + class RenderObject; } namespace WebKit { @@ -53,17 +54,24 @@ public: WebCore::Node* getFocusedNode(); - bool nodeWillBeRemoved(WebCore::Node*); + bool rendererWillBeDestroyed(WebCore::RenderObject*); void clearFocus(); private: ScreenReader(WebPage*); - bool setFocus(WebCore::Node*); + WebCore::RenderObject* traverse(WebCore::RenderObject*); + WebCore::RenderObject* traverseSibling(WebCore::RenderObject*); + WebCore::RenderObject* ownerElementSibling(WebCore::RenderObject*); + + WebCore::RenderObject* findFocusable(WebCore::RenderObject*); + + bool setFocus(WebCore::RenderObject*); WebPage* m_page; - RefPtr m_focusedNode; + WebCore::RenderObject* m_focusedObject; bool m_hasFocus; + bool m_isForward; static int s_readerCount; static WebCore::IntSize s_hitTestPadding; -- 2.7.4