AX: improve list heuristics (presentational use versus actual lists)
[WebKit-https.git] / Source / WebCore / accessibility / AccessibilityList.cpp
1 /*
2  * Copyright (C) 2008 Apple Inc. All rights reserved.
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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "AccessibilityList.h"
31
32 #include "AXObjectCache.h"
33 #include "HTMLElement.h"
34 #include "HTMLNames.h"
35 #include "PseudoElement.h"
36 #include "RenderListItem.h"
37 #include "RenderObject.h"
38 #include "RenderStyle.h"
39
40 namespace WebCore {
41     
42 using namespace HTMLNames;
43
44 AccessibilityList::AccessibilityList(RenderObject* renderer)
45     : AccessibilityRenderObject(renderer)
46 {
47 }
48
49 AccessibilityList::~AccessibilityList()
50 {
51 }
52
53 Ref<AccessibilityList> AccessibilityList::create(RenderObject* renderer)
54 {
55     return adoptRef(*new AccessibilityList(renderer));
56 }
57
58 bool AccessibilityList::computeAccessibilityIsIgnored() const
59 {
60     return accessibilityIsIgnoredByDefault();
61 }
62     
63 bool AccessibilityList::isUnorderedList() const
64 {
65     if (!m_renderer)
66         return false;
67     
68     Node* node = m_renderer->node();
69
70     // The ARIA spec says the "list" role is supposed to mimic a UL or OL tag.
71     // Since it can't be both, it's probably OK to say that it's an un-ordered list.
72     // On the Mac, there's no distinction to the client.
73     if (ariaRoleAttribute() == ListRole)
74         return true;
75     
76     return node && node->hasTagName(ulTag);
77 }
78
79 bool AccessibilityList::isOrderedList() const
80 {
81     if (!m_renderer)
82         return false;
83
84     // ARIA says a directory is like a static table of contents, which sounds like an ordered list.
85     if (ariaRoleAttribute() == DirectoryRole)
86         return true;
87
88     Node* node = m_renderer->node();
89     return node && node->hasTagName(olTag);    
90 }
91
92 bool AccessibilityList::isDescriptionList() const
93 {
94     if (!m_renderer)
95         return false;
96     
97     Node* node = m_renderer->node();
98     return node && node->hasTagName(dlTag);
99 }
100
101 bool AccessibilityList::childHasPseudoVisibleListItemMarkers(RenderObject* listItem)
102 {
103     // Check if the list item has a pseudo-element that should be accessible (e.g. an image or text)
104     Element* listItemElement = downcast<Element>(listItem->node());
105     if (!listItemElement || !listItemElement->beforePseudoElement())
106         return false;
107
108     AccessibilityObject* axObj = axObjectCache()->getOrCreate(listItemElement->beforePseudoElement()->renderer());
109     if (!axObj)
110         return false;
111     
112     if (!axObj->accessibilityIsIgnored())
113         return true;
114     
115     for (const auto& child : axObj->children()) {
116         if (!child->accessibilityIsIgnored())
117             return true;
118     }
119     
120     return false;
121 }
122     
123 AccessibilityRole AccessibilityList::determineAccessibilityRole()
124 {
125     m_ariaRole = determineAriaRoleAttribute();
126     
127     // Directory is mapped to list for now, but does not adhere to the same heuristics.
128     if (ariaRoleAttribute() == DirectoryRole)
129         return ListRole;
130     
131     // Heuristic to determine if this list is being used for layout or for content.
132     //   1. If it's a named list, like ol or aria=list, then it's a list.
133     //      1a. Unless the list has no children, then it's not a list.
134     //   2. If it displays visible list markers, it's a list.
135     //   3. If it does not display list markers and has only one child, it's not a list.
136     //   4. If it does not have any listitem children, it's not a list.
137     //   5. Otherwise it's a list (for now).
138     
139     AccessibilityRole role = ListRole;
140     
141     // Temporarily set role so that we can query children (otherwise canHaveChildren returns false).
142     m_role = role;
143     
144     unsigned listItemCount = 0;
145     bool hasVisibleMarkers = false;
146
147     const auto& children = this->children();
148     // DescriptionLists are always semantically a description list, so do not apply heuristics.
149     if (isDescriptionList() && children.size())
150         return DescriptionListRole;
151
152     for (const auto& child : children) {
153         if (child->ariaRoleAttribute() == ListItemRole)
154             listItemCount++;
155         else if (child->roleValue() == ListItemRole) {
156             RenderObject* listItem = child->renderer();
157             if (!listItem)
158                 continue;
159             
160             // Rendered list items always count.
161             if (listItem->isListItem()) {
162                 if (!hasVisibleMarkers && (listItem->style().listStyleType() != NoneListStyle || listItem->style().listStyleImage() || childHasPseudoVisibleListItemMarkers(listItem)))
163                     hasVisibleMarkers = true;
164                 listItemCount++;
165             } else if (listItem->node() && listItem->node()->hasTagName(liTag)) {
166                 // Inline elements that are in a list with an explicit role should also count.
167                 if (m_ariaRole == ListRole)
168                     listItemCount++;
169
170                 if (childHasPseudoVisibleListItemMarkers(listItem)) {
171                     hasVisibleMarkers = true;
172                     listItemCount++;
173                 }
174             }
175         }
176     }
177     
178     // Non <ul> lists and ARIA lists only need to have one child.
179     // <ul>, <ol> lists need to have visible markers.
180     if (ariaRoleAttribute() != UnknownRole) {
181         if (!listItemCount)
182             role = GroupRole;
183     } else if (!hasVisibleMarkers)
184         role = GroupRole;
185
186     return role;
187 }
188     
189 AccessibilityRole AccessibilityList::roleValue() const
190 {
191     ASSERT(m_role != UnknownRole);
192     return m_role;
193 }
194     
195 } // namespace WebCore