2 * Copyright (C) 2013 Samsung Electronic.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #include "ScreenReader.h"
29 #if ENABLE(TIZEN_SCREEN_READER)
33 #include <WebCore/AccessibilityObject.h>
34 #include <WebCore/Document.h>
35 #include <WebCore/Frame.h>
36 #include <WebCore/FrameView.h>
37 #include <WebCore/HTMLButtonElement.h>
38 #include <WebCore/HTMLFormControlElement.h>
39 #include <WebCore/HTMLFrameOwnerElement.h>
40 #include <WebCore/HTMLImageElement.h>
41 #include <WebCore/HTMLInputElement.h>
42 #include <WebCore/HTMLOptionElement.h>
43 #include <WebCore/HTMLOutputElement.h>
44 #include <WebCore/HTMLSelectElement.h>
45 #include <WebCore/HTMLTextAreaElement.h>
46 #include <WebCore/Page.h>
47 #include <WebCore/PlatformScreen.h>
48 #include <WebCore/RenderObject.h>
49 #include <WebCore/Text.h>
50 #include <wtf/text/StringBuilder.h>
52 using namespace WebCore;
56 IntSize ScreenReader::s_hitTestPadding = IntSize();
58 ScreenReader::ScreenReader(WebPage* page)
64 static bool initialized = false;
66 AXObjectCache::enableAccessibility();
67 int unit = static_cast<int>(screenRect(0).width() / 10);
68 s_hitTestPadding.setWidth(unit);
69 s_hitTestPadding.setHeight(unit);
74 ScreenReader::~ScreenReader()
78 RenderObject* ScreenReader::traverse(RenderObject* object)
81 if (object->firstChild())
82 return object->firstChild();
84 if (object->lastChild())
85 return object->lastChild();
88 return traverseSibling(object);
91 RenderObject* ScreenReader::traverseSibling(RenderObject* object)
95 if (object->nextSibling())
96 return object->nextSibling();
97 } while ((object = object->parent()));
100 if (object->previousSibling())
101 return object->previousSibling();
102 } while ((object = object->parent()));
108 RenderObject* ScreenReader::ownerElementSibling(RenderObject* object)
111 while ((node = object->document()->ownerElement())) {
112 object = node->renderer();
113 RenderObject* next = traverseSibling(object);
121 static RenderObject* contentDocumentBody(RenderObject* object)
123 HTMLFrameOwnerElement* ownerElement = toFrameOwnerElement(object->node());
124 if (!ownerElement || !ownerElement->contentDocument())
127 return ownerElement->contentDocument()->body()->renderer();
130 static RenderObject* lastLeafChild(RenderObject* object)
132 while (object->lastChild())
133 object = object->lastChild();
137 static AccessibilityObject* visibleAXObject(RenderObject* object)
139 if (!object || !object->node() || object->style()->visibility() != VISIBLE)
141 AccessibilityObject* axObject = object->document()->axObjectCache()->getOrCreate(object);
144 IntRect boundingRect = axObject->pixelSnappedBoundingBoxRect();
145 return (boundingRect.maxX() > 0 && boundingRect.maxY() > 0) ? axObject : 0;
148 static bool isSpace(UChar character)
150 return isASCIISpace(character) || character == noBreakSpace;
153 static bool containsOnlyWhitespace(const String& string)
155 StringImpl* impl = string.impl();
159 unsigned length = impl->length();
160 if (impl->is8Bit()) {
161 const LChar* data = impl->characters8();
162 for (unsigned i = 0; i < length; i++) {
163 if (!isSpace(data[i]))
167 const UChar* data = impl->characters16();
168 for (unsigned i = 0; i < length; i++) {
169 if (!isSpace(data[i]))
177 static bool isAriaFocusable(Node* node)
179 AccessibilityObject* axObject = node->document()->axObjectCache()->getOrCreate(node->renderer());
183 switch (axObject->roleValue()) {
191 case ListBoxOptionRole:
192 case ProgressIndicatorRole:
193 case RadioButtonRole:
199 case WebCoreLinkRole:
205 if (!containsOnlyWhitespace(axObject->ariaLabeledByAttribute())
206 || !containsOnlyWhitespace(axObject->getAttribute(aria_labelAttr)))
212 static bool hasText(Node* node)
214 return !containsOnlyWhitespace(node->nodeValue());
217 static bool isFocusable(Node* node)
219 if (node->isFocusable() || isAriaFocusable(node))
224 static bool isAriaReadable(Node* node)
226 AccessibilityObject* axObject = node->document()->axObjectCache()->getOrCreate(node->renderer());
230 switch (axObject->roleValue()) {
239 static bool isReadable(Node* node)
241 if (!(node->isTextNode() && hasText(node)) && !isHTMLImageElement(node) && !isAriaReadable(node))
244 for (Node* parent = node->parentOrHostNode(); parent; parent = parent->parentOrHostNode()) {
245 if (isFocusable(parent))
252 static bool isAriaHidden(AccessibilityObject* axObject)
254 if (equalIgnoringCase(axObject->getAttribute(aria_hiddenAttr), "true"))
257 while ((axObject = axObject->parentObject())) {
258 if (equalIgnoringCase(axObject->getAttribute(aria_hiddenAttr), "true"))
265 RenderObject* ScreenReader::findFocusable(RenderObject* object)
268 AccessibilityObject* axObject = visibleAXObject(object);
272 Node* node = object->node();
273 if (node->isFrameOwnerElement()) {
274 RenderObject* body = contentDocumentBody(object);
276 RenderObject* result = findFocusable(m_isForward ? body : lastLeafChild(body));
280 } else if ((isFocusable(node) || isReadable(node)) && !isAriaHidden(axObject))
282 } while ((object = traverse(object)));
287 bool ScreenReader::moveFocus(bool forward)
289 m_isForward = forward;
290 m_page->mainFrame()->document()->updateLayoutIgnorePendingStylesheets();
292 RenderObject* object;
293 if (!m_focusedObject) {
294 object = m_page->mainFrame()->document()->body()->renderer();
296 object = lastLeafChild(object);
298 object = traverse(m_focusedObject);
300 object = ownerElementSibling(m_focusedObject);
308 RenderObject* candidate;
310 candidate = findFocusable(object);
313 } while ((object = ownerElementSibling(object)));
320 Node* node = candidate->node();
322 node->renderer()->scrollRectToVisible(node->getRect(), ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
323 node = node->document()->ownerElement();
326 if (!setFocus(candidate))
327 return moveFocus(forward);
332 static Node* findShortestDistanceNode(const IntPoint& point, const HitTestResult::NodeSet& nodeSet)
334 HashSet<Node*> candidates;
335 HitTestResult::NodeSet::const_iterator it = nodeSet.begin();
336 const HitTestResult::NodeSet::const_iterator end = nodeSet.end();
338 for (; it != end; ++it) {
339 for (Node* node = it->get(); node; node = node->parentOrHostNode()) {
340 if (isFocusable(node) || isReadable(node)) {
341 AccessibilityObject* object = node->document()->axObjectCache()->getOrCreate(node->renderer());
342 if (object && !isAriaHidden(object))
343 candidates.add(node);
348 Node* targetNode = 0;
349 int targetSize = std::numeric_limits<int>::max();
350 int targetDistance = std::numeric_limits<int>::max();
352 while (!candidates.isEmpty()) {
353 Node* node = *(candidates.begin());
354 candidates.remove(candidates.begin());
356 Vector<FloatQuad> quads;
357 node->renderer()->absoluteQuads(quads);
359 Vector<FloatQuad>::const_iterator quadIt = quads.begin();
360 const Vector<FloatQuad>::const_iterator quadEnd = quads.end();
362 for (; quadIt != quadEnd; ++quadIt) {
363 IntRect box = quadIt->enclosingBoundingBox();
364 int size = box.size().area();
365 int distance = box.distanceSquaredToPoint(point);
366 if (distance < targetDistance || (distance == targetDistance && size < targetSize)) {
369 targetDistance = distance;
377 bool ScreenReader::moveFocus(const IntPoint& point)
379 Frame* mainFrame = m_page->mainFrame();
380 mainFrame->document()->updateLayoutIgnorePendingStylesheets();
382 IntPoint hitTestPoint = mainFrame->view()->windowToContents(point);
383 HitTestResult result = mainFrame->eventHandler()->hitTestResultAtPoint(hitTestPoint, false, false, DontHitTestScrollbars, HitTestRequest::ReadOnly | HitTestRequest::Active, s_hitTestPadding);
385 Node* candidate = findShortestDistanceNode(hitTestPoint, result.rectBasedTestResult());
386 if (!candidate || candidate->renderer() == m_focusedObject)
389 setFocus(candidate->renderer());
394 static String ariaRoleText(AccessibilityRole roleValue)
398 case PopUpButtonRole:
407 case WebCoreLinkRole:
409 case ListBoxOptionRole:
410 return "list box option";
411 case ProgressIndicatorRole:
413 case RadioButtonRole:
414 return "radio button";
422 return emptyString();
425 static bool addNodeText(Node* node, bool hasSubtreeText, Vector<String>& textList)
427 AccessibilityObject* axObject = visibleAXObject(node->renderer());
428 if (!axObject || isAriaHidden(axObject))
429 return hasSubtreeText;
431 if (node->isElementNode()) {
432 Element* element = toElement(node);
433 String type, text, state;
435 if (element->isLink())
437 else if (element->isFormControlElement()) {
438 if (element->hasTagName(HTMLNames::buttonTag)) {
439 type = static_cast<HTMLFormControlElement*>(element)->type();
440 text = static_cast<HTMLButtonElement*>(element)->value();
441 } else if (element->hasTagName(HTMLNames::fieldsetTag))
442 type = static_cast<HTMLFormControlElement*>(element)->type();
443 else if (element->hasTagName(HTMLNames::inputTag)) {
445 text = static_cast<HTMLInputElement*>(element)->alt();
447 text = static_cast<HTMLInputElement*>(element)->value();
448 } else if (element->hasTagName(HTMLNames::keygenTag))
449 type = static_cast<HTMLFormControlElement*>(element)->type();
450 else if (element->hasTagName(HTMLNames::outputTag)) {
451 type = static_cast<HTMLFormControlElement*>(element)->type();
452 text = static_cast<HTMLOutputElement*>(element)->value();
453 } else if (element->hasTagName(HTMLNames::selectTag)) {
454 type = static_cast<HTMLFormControlElement*>(element)->type();
455 text = static_cast<HTMLSelectElement*>(element)->value();
456 if (static_cast<HTMLSelectElement*>(element)->size() == 1)
459 state = String::format("%d items", static_cast<HTMLSelectElement*>(element)->size());
460 } else if (element->hasTagName(HTMLNames::textareaTag)) {
462 text = static_cast<HTMLTextAreaElement*>(element)->value();
464 } else if (element->hasTagName(HTMLNames::imgTag)) {
466 text = static_cast<HTMLImageElement*>(element)->altText();
467 } else if (element->hasTagName(HTMLNames::optionTag)) {
468 text = static_cast<HTMLOptionElement*>(element)->label();
469 if (static_cast<HTMLOptionElement*>(element)->selected())
473 if (text.isEmpty() && node->isHTMLElement()) {
474 const AtomicString& title = element->fastGetAttribute(HTMLNames::titleAttr);
475 if (!containsOnlyWhitespace(title))
479 if (element->fastHasAttribute(HTMLNames::roleAttr))
480 type = element->fastGetAttribute(HTMLNames::roleAttr).isEmpty()? emptyString() : ariaRoleText(axObject->roleValue());
482 String more, ariaText;
483 if (!containsOnlyWhitespace((ariaText = axObject->ariaDescribedByAttribute())))
485 else if (!containsOnlyWhitespace((ariaText = axObject->ariaLabeledByAttribute())))
487 else if (!containsOnlyWhitespace((ariaText = element->fastGetAttribute(aria_labelAttr))))
490 if (text.isEmpty() && type.isEmpty())
491 return hasSubtreeText;
493 if (axObject->isSelected())
495 else if (axObject->isChecked())
499 textList.append(text);
501 textList.append(type);
502 if (!state.isEmpty())
503 textList.append(state);
505 textList.append(more);
506 } else if (node->isTextNode() && hasText(node))
507 textList.append(node->nodeValue());
509 return hasSubtreeText;
511 if (!axObject->isEnabled())
512 textList.append("disabled");
517 static bool addSubtreeText(Node* top, Vector<String>& textList)
519 bool hasSubtreeText = false;
520 for (Node* node = top->firstChild(); node; node = node->nextSibling()) {
521 if (!isFocusable(node))
522 hasSubtreeText |= addSubtreeText(node, textList);
524 return addNodeText(top, hasSubtreeText, textList);
527 static String subtreeText(Node* top)
529 Vector<String> textList;
530 addSubtreeText(top, textList);
533 String separator(" ");
534 for (Vector<String>::iterator iter = textList.begin(); iter != textList.end(); ++iter) {
539 text.append(separator);
542 return text.toString().simplifyWhiteSpace();
545 bool ScreenReader::setFocus(RenderObject* object)
547 m_focusedObject = object;
550 String text = subtreeText(object->node());
554 m_page->send(Messages::WebPageProxy::DidScreenReaderTextChanged(text));
559 Node* ScreenReader::getFocusedNode()
564 return m_focusedObject->node();
567 bool ScreenReader::rendererWillBeDestroyed(RenderObject* object)
569 if (m_focusedObject != object)
574 m_focusedObject = traverse(object);
579 void ScreenReader::clearFocus()
582 m_focusedRect = IntRect();
584 m_page->send(Messages::WebPageProxy::DidScreenReaderTextChanged(emptyString()));
589 #endif // ENABLE(TIZEN_SCREEN_READER)