d16897469f01b881b49b9981f0a2650a3dcbd18d
[framework/web/webkit-efl.git] / Source / WebKit2 / WebProcess / WebPage / efl / tizen / ScreenReader.cpp
1 /*
2  * Copyright (C) 2013 Samsung Electronic.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
24  */
25
26 #include "config.h"
27 #include "ScreenReader.h"
28
29 #if ENABLE(TIZEN_SCREEN_READER)
30
31 #include "WebPage.h"
32
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>
51
52 using namespace WebCore;
53
54 namespace WebKit {
55
56 IntSize ScreenReader::s_hitTestPadding = IntSize();
57
58 ScreenReader::ScreenReader(WebPage* page)
59     : m_page(page)
60     , m_focusedObject(0)
61     , m_hasFocus(false)
62     , m_isForward(true)
63 {
64     static bool initialized = false;
65     if (!initialized) {
66         AXObjectCache::enableAccessibility();
67         int unit = static_cast<int>(screenRect(0).width() / 10);
68         s_hitTestPadding.setWidth(unit);
69         s_hitTestPadding.setHeight(unit);
70         initialized = true;
71     }
72 }
73
74 ScreenReader::~ScreenReader()
75 {
76 }
77
78 RenderObject* ScreenReader::traverse(RenderObject* object)
79 {
80     if (m_isForward) {
81         if (object->firstChild())
82             return object->firstChild();
83     } else {
84         if (object->lastChild())
85             return object->lastChild();
86     }
87
88     return traverseSibling(object);
89 }
90
91 RenderObject* ScreenReader::traverseSibling(RenderObject* object)
92 {
93     if (m_isForward) {
94         do {
95             if (object->nextSibling())
96                 return object->nextSibling();
97         } while ((object = object->parent()));
98     } else {
99         do {
100             if (object->previousSibling())
101                 return object->previousSibling();
102         } while ((object = object->parent()));
103     }
104
105     return 0;
106 }
107
108 RenderObject* ScreenReader::ownerElementSibling(RenderObject* object)
109 {
110     Node* node;
111     while ((node = object->document()->ownerElement())) {
112         object = node->renderer();
113         RenderObject* next = traverseSibling(object);
114         if (next)
115             return next;
116     }
117
118     return 0;
119 }
120
121 static RenderObject* contentDocumentBody(RenderObject* object)
122 {
123     HTMLFrameOwnerElement* ownerElement = toFrameOwnerElement(object->node());
124     if (!ownerElement || !ownerElement->contentDocument())
125         return 0;
126
127     return ownerElement->contentDocument()->body()->renderer();
128 }
129
130 static RenderObject* lastLeafChild(RenderObject* object)
131 {
132     while (object->lastChild())
133         object = object->lastChild();
134     return object;
135 }
136
137 static AccessibilityObject* visibleAXObject(RenderObject* object)
138 {
139     if (!object || !object->node() || object->style()->visibility() != VISIBLE)
140         return 0;
141     AccessibilityObject* axObject = object->document()->axObjectCache()->getOrCreate(object);
142     if (!axObject)
143         return 0;
144     IntRect boundingRect = axObject->pixelSnappedBoundingBoxRect();
145     return (boundingRect.maxX() > 0 && boundingRect.maxY() > 0) ? axObject : 0;
146 }
147
148 static bool isSpace(UChar character)
149 {
150     return isASCIISpace(character) || character == noBreakSpace;
151 }
152
153 static bool containsOnlyWhitespace(const String& string)
154 {
155     StringImpl* impl = string.impl();
156     if (!impl)
157         return true;
158
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]))
164                 return false;
165         }
166     } else {
167         const UChar* data = impl->characters16();
168         for (unsigned i = 0; i < length; i++) {
169             if (!isSpace(data[i]))
170                 return false;
171         }
172     }
173
174     return true;
175 }
176
177 static bool isAriaFocusable(Node* node)
178 {
179     AccessibilityObject* axObject = node->document()->axObjectCache()->getOrCreate(node->renderer());
180     if (!axObject)
181         return false;
182
183     switch (axObject->roleValue()) {
184     case ButtonRole:
185     case CheckBoxRole:
186     case ComboBoxRole:
187     case MenuRole:
188     case MenuBarRole:
189     case MenuItemRole:
190     case LinkRole:
191     case ListBoxOptionRole:
192     case ProgressIndicatorRole:
193     case RadioButtonRole:
194     case ScrollBarRole:
195     case SliderRole:
196     case TreeRole:
197     case TreeGridRole:
198     case TreeItemRole:
199     case WebCoreLinkRole:
200         return true;
201     default:
202         break;
203     }
204
205     if (!containsOnlyWhitespace(axObject->ariaLabeledByAttribute())
206         || !containsOnlyWhitespace(axObject->getAttribute(aria_labelAttr)))
207         return true;
208
209     return false;
210 }
211
212 static bool hasText(Node* node)
213 {
214     return !containsOnlyWhitespace(node->nodeValue());
215 }
216
217 static bool isFocusable(Node* node)
218 {
219     if (node->isFocusable() || isAriaFocusable(node))
220         return true;
221     return false;
222 }
223
224 static bool isAriaReadable(Node* node)
225 {
226     AccessibilityObject* axObject = node->document()->axObjectCache()->getOrCreate(node->renderer());
227     if (!axObject)
228         return false;
229
230     switch (axObject->roleValue()) {
231     case ImageRole:
232         return true;
233     default:
234         break;
235     }
236     return false;
237 }
238
239 static bool isReadable(Node* node)
240 {
241     if (!(node->isTextNode() && hasText(node)) && !isHTMLImageElement(node) && !isAriaReadable(node))
242         return false;
243
244     for (Node* parent = node->parentOrHostNode(); parent; parent = parent->parentOrHostNode()) {
245         if (isFocusable(parent))
246             return false;
247     }
248
249     return true;
250 }
251
252 static bool isAriaHidden(AccessibilityObject* axObject)
253 {
254     if (equalIgnoringCase(axObject->getAttribute(aria_hiddenAttr), "true"))
255         return true;
256
257     while ((axObject = axObject->parentObject())) {
258         if (equalIgnoringCase(axObject->getAttribute(aria_hiddenAttr), "true"))
259             return true;
260     }
261
262     return false;
263 }
264
265 RenderObject* ScreenReader::findFocusable(RenderObject* object)
266 {
267     do {
268         AccessibilityObject* axObject = visibleAXObject(object);
269         if (!axObject)
270             continue;
271
272         Node* node = object->node();
273         if (node->isFrameOwnerElement()) {
274             RenderObject* body = contentDocumentBody(object);
275             if (body) {
276                 RenderObject* result = findFocusable(m_isForward ? body : lastLeafChild(body));
277                 if (result)
278                     return result;
279             }
280         } else if ((isFocusable(node) || isReadable(node)) && !isAriaHidden(axObject))
281             return object;
282     } while ((object = traverse(object)));
283
284     return 0;
285 }
286
287 bool ScreenReader::moveFocus(bool forward)
288 {
289     m_isForward = forward;
290     m_page->mainFrame()->document()->updateLayoutIgnorePendingStylesheets();
291
292     RenderObject* object;
293     if (!m_focusedObject) {
294         object = m_page->mainFrame()->document()->body()->renderer();
295         if (!forward)
296             object = lastLeafChild(object);
297     } else {
298         object = traverse(m_focusedObject);
299         if (!object)
300             object = ownerElementSibling(m_focusedObject);
301     }
302
303     if (!object) {
304         clearFocus();
305         return false;
306     }
307
308     RenderObject* candidate;
309     do {
310         candidate = findFocusable(object);
311         if (candidate)
312             break;
313     } while ((object = ownerElementSibling(object)));
314
315     if (!candidate) {
316         clearFocus();
317         return false;
318     }
319
320     Node* node = candidate->node();
321     while (node) {
322         node->renderer()->scrollRectToVisible(node->getRect(), ScrollAlignment::alignCenterIfNeeded, ScrollAlignment::alignCenterIfNeeded);
323         node = node->document()->ownerElement();
324     }
325
326     if (!setFocus(candidate))
327         return moveFocus(forward);
328
329     return true;
330 }
331
332 static Node* findShortestDistanceNode(const IntPoint& point, const HitTestResult::NodeSet& nodeSet)
333 {
334     HashSet<Node*> candidates;
335     HitTestResult::NodeSet::const_iterator it = nodeSet.begin();
336     const HitTestResult::NodeSet::const_iterator end = nodeSet.end();
337
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);
344             }
345         }
346     }
347
348     Node* targetNode = 0;
349     int targetSize = std::numeric_limits<int>::max();
350     int targetDistance = std::numeric_limits<int>::max();
351
352     while (!candidates.isEmpty()) {
353         Node* node = *(candidates.begin());
354         candidates.remove(candidates.begin());
355
356         Vector<FloatQuad> quads;
357         node->renderer()->absoluteQuads(quads);
358
359         Vector<FloatQuad>::const_iterator quadIt = quads.begin();
360         const Vector<FloatQuad>::const_iterator quadEnd = quads.end();
361
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)) {
367                 targetNode = node;
368                 targetSize = size;
369                 targetDistance = distance;
370             }
371         }
372     }
373
374     return targetNode;
375 }
376
377 bool ScreenReader::moveFocus(const IntPoint& point)
378 {
379     Frame* mainFrame = m_page->mainFrame();
380     mainFrame->document()->updateLayoutIgnorePendingStylesheets();
381
382     IntPoint hitTestPoint = mainFrame->view()->windowToContents(point);
383     HitTestResult result = mainFrame->eventHandler()->hitTestResultAtPoint(hitTestPoint, false, false, DontHitTestScrollbars, HitTestRequest::ReadOnly | HitTestRequest::Active, s_hitTestPadding);
384
385     Node* candidate = findShortestDistanceNode(hitTestPoint, result.rectBasedTestResult());
386     if (!candidate || candidate->renderer() == m_focusedObject)
387         return false;
388
389     setFocus(candidate->renderer());
390
391     return true;
392 }
393
394 static String ariaRoleText(AccessibilityRole roleValue)
395 {
396     switch (roleValue) {
397     case ButtonRole:
398     case PopUpButtonRole:
399         return "button";
400     case CheckBoxRole:
401         return "check box";
402     case ComboBoxRole:
403         return "combo box";
404     case ImageRole:
405         return "image";
406     case LinkRole:
407     case WebCoreLinkRole:
408         return "link";
409     case ListBoxOptionRole:
410         return "list box option";
411     case ProgressIndicatorRole:
412         return "";
413     case RadioButtonRole:
414         return "radio button";
415     case SliderRole:
416         return "slider";
417     case TabRole:
418         return "tab";
419     default:
420         break;
421     }
422     return emptyString();
423 }
424
425 static bool addNodeText(Node* node, bool hasSubtreeText, Vector<String>& textList)
426 {
427     AccessibilityObject* axObject = visibleAXObject(node->renderer());
428     if (!axObject || isAriaHidden(axObject))
429         return hasSubtreeText;
430
431     if (node->isElementNode()) {
432         Element* element = toElement(node);
433         String type, text, state;
434
435         if (element->isLink())
436             type = "link";
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)) {
444                 type = "edit field";
445                 text = static_cast<HTMLInputElement*>(element)->alt();
446                 if (text.isEmpty())
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)
457                     state = "1 item";
458                 else
459                     state = String::format("%d items", static_cast<HTMLSelectElement*>(element)->size());
460             } else if (element->hasTagName(HTMLNames::textareaTag)) {
461                 type = "edit field";
462                 text = static_cast<HTMLTextAreaElement*>(element)->value();
463             }
464         } else if (element->hasTagName(HTMLNames::imgTag)) {
465             type = "image";
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())
470                 state = "selected";
471         }
472
473         if (text.isEmpty() && node->isHTMLElement()) {
474             const AtomicString& title = element->fastGetAttribute(HTMLNames::titleAttr);
475             if (!containsOnlyWhitespace(title))
476                 text = title;
477         }
478
479         if (element->fastHasAttribute(HTMLNames::roleAttr))
480             type = element->fastGetAttribute(HTMLNames::roleAttr).isEmpty()? emptyString() : ariaRoleText(axObject->roleValue());
481
482         String more, ariaText;
483         if (!containsOnlyWhitespace((ariaText = axObject->ariaDescribedByAttribute())))
484             more = ariaText;
485         else if (!containsOnlyWhitespace((ariaText = axObject->ariaLabeledByAttribute())))
486             more = ariaText;
487         else if (!containsOnlyWhitespace((ariaText = element->fastGetAttribute(aria_labelAttr))))
488             more = ariaText;
489
490         if (text.isEmpty() && type.isEmpty())
491             return hasSubtreeText;
492
493         if (axObject->isSelected())
494             state = "selected";
495         else if (axObject->isChecked())
496             state = "checked";
497
498         if (!text.isEmpty())
499             textList.append(text);
500         if (!type.isEmpty())
501             textList.append(type);
502         if (!state.isEmpty())
503             textList.append(state);
504         if (!more.isEmpty())
505             textList.append(more);
506     } else if (node->isTextNode() && hasText(node))
507         textList.append(node->nodeValue());
508     else
509         return hasSubtreeText;
510
511     if (!axObject->isEnabled())
512         textList.append("disabled");
513
514     return true;
515 }
516
517 static bool addSubtreeText(Node* top, Vector<String>& textList)
518 {
519     bool hasSubtreeText = false;
520     for (Node* node = top->firstChild(); node; node = node->nextSibling()) {
521         if (!isFocusable(node))
522             hasSubtreeText |= addSubtreeText(node, textList);
523     }
524     return addNodeText(top, hasSubtreeText, textList);
525 }
526
527 static String subtreeText(Node* top)
528 {
529     Vector<String> textList;
530     addSubtreeText(top, textList);
531
532     StringBuilder text;
533     String separator(" ");
534     for (Vector<String>::iterator iter = textList.begin(); iter != textList.end(); ++iter) {
535         if (iter->isEmpty())
536             continue;
537
538         text.append(*iter);
539         text.append(separator);
540     }
541
542     return text.toString().simplifyWhiteSpace();
543 }
544
545 bool ScreenReader::setFocus(RenderObject* object)
546 {
547     m_focusedObject = object;
548     m_hasFocus = true;
549
550     String text = subtreeText(object->node());
551     if (text.isEmpty())
552         return false;
553
554     m_page->send(Messages::WebPageProxy::DidScreenReaderTextChanged(text));
555
556     return true;
557 }
558
559 Node* ScreenReader::getFocusedNode()
560 {
561     if (!m_hasFocus)
562         return 0;
563
564     return m_focusedObject->node();
565 }
566
567 bool ScreenReader::rendererWillBeDestroyed(RenderObject* object)
568 {
569     if (m_focusedObject != object)
570         return false;
571
572     clearFocus();
573     m_isForward = false;
574     m_focusedObject = traverse(object);
575
576     return true;
577 }
578
579 void ScreenReader::clearFocus()
580 {
581     m_focusedObject = 0;
582     m_focusedRect = IntRect();
583     m_hasFocus = false;
584     m_page->send(Messages::WebPageProxy::DidScreenReaderTextChanged(emptyString()));
585 }
586
587 }
588
589 #endif // ENABLE(TIZEN_SCREEN_READER)