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 "HTMLParserIdioms.h"
36#include "PseudoElement.h"
37#include "RenderListItem.h"
38#include "RenderObject.h"
39
40namespace WebCore {
41
42using namespace HTMLNames;
43
44AccessibilityList::AccessibilityList(RenderObject* renderer)
45 : AccessibilityRenderObject(renderer)
46{
47}
48
49AccessibilityList::~AccessibilityList() = default;
50
51Ref<AccessibilityList> AccessibilityList::create(RenderObject* renderer)
52{
53 return adoptRef(*new AccessibilityList(renderer));
54}
55
56bool AccessibilityList::computeAccessibilityIsIgnored() const
57{
58 return accessibilityIsIgnoredByDefault();
59}
60
61bool AccessibilityList::isUnorderedList() const
62{
63 if (!m_renderer)
64 return false;
65
66 Node* node = m_renderer->node();
67
68 // The ARIA spec says the "list" role is supposed to mimic a UL or OL tag.
69 // Since it can't be both, it's probably OK to say that it's an un-ordered list.
70 // On the Mac, there's no distinction to the client.
71 if (ariaRoleAttribute() == AccessibilityRole::List)
72 return true;
73
74 return node && node->hasTagName(ulTag);
75}
76
77bool AccessibilityList::isOrderedList() const
78{
79 if (!m_renderer)
80 return false;
81
82 // ARIA says a directory is like a static table of contents, which sounds like an ordered list.
83 if (ariaRoleAttribute() == AccessibilityRole::Directory)
84 return true;
85
86 Node* node = m_renderer->node();
87 return node && node->hasTagName(olTag);
88}
89
90bool AccessibilityList::isDescriptionList() const
91{
92 if (!m_renderer)
93 return false;
94
95 Node* node = m_renderer->node();
96 return node && node->hasTagName(dlTag);
97}
98
99bool AccessibilityList::childHasPseudoVisibleListItemMarkers(RenderObject* listItem)
100{
101 // Check if the list item has a pseudo-element that should be accessible (e.g. an image or text)
102 Element* listItemElement = downcast<Element>(listItem->node());
103 if (!listItemElement || !listItemElement->beforePseudoElement())
104 return false;
105
106 AccessibilityObject* axObj = axObjectCache()->getOrCreate(listItemElement->beforePseudoElement()->renderer());
107 if (!axObj)
108 return false;
109
110 if (!axObj->accessibilityIsIgnored())
111 return true;
112
113 for (const auto& child : axObj->children()) {
114 if (!child->accessibilityIsIgnored())
115 return true;
116 }
117
118 // Platforms which expose rendered text content through the parent element will treat
119 // those renderers as "ignored" objects.
120#if USE(ATK)
121 String text = axObj->textUnderElement();
122 return !text.isEmpty() && !text.isAllSpecialCharacters<isHTMLSpace>();
123#else
124 return false;
125#endif
126}
127
128AccessibilityRole AccessibilityList::determineAccessibilityRole()
129{
130 m_ariaRole = determineAriaRoleAttribute();
131
132 // Directory is mapped to list for now, but does not adhere to the same heuristics.
133 if (ariaRoleAttribute() == AccessibilityRole::Directory)
134 return AccessibilityRole::List;
135
136 // Heuristic to determine if this list is being used for layout or for content.
137 // 1. If it's a named list, like ol or aria=list, then it's a list.
138 // 1a. Unless the list has no children, then it's not a list.
139 // 2. If it displays visible list markers, it's a list.
140 // 3. If it does not display list markers and has only one child, it's not a list.
141 // 4. If it does not have any listitem children, it's not a list.
142 // 5. Otherwise it's a list (for now).
143
144 AccessibilityRole role = AccessibilityRole::List;
145
146 // Temporarily set role so that we can query children (otherwise canHaveChildren returns false).
147 m_role = role;
148
149 unsigned listItemCount = 0;
150 bool hasVisibleMarkers = false;
151
152 const auto& children = this->children();
153 // DescriptionLists are always semantically a description list, so do not apply heuristics.
154 if (isDescriptionList() && children.size())
155 return AccessibilityRole::DescriptionList;
156
157 for (const auto& child : children) {
158 if (child->ariaRoleAttribute() == AccessibilityRole::ListItem)
159 listItemCount++;
160 else if (child->roleValue() == AccessibilityRole::ListItem) {
161 RenderObject* listItem = child->renderer();
162 if (!listItem)
163 continue;
164
165 // Rendered list items always count.
166 if (listItem->isListItem()) {
167 if (!hasVisibleMarkers && (listItem->style().listStyleType() != ListStyleType::None || listItem->style().listStyleImage() || childHasPseudoVisibleListItemMarkers(listItem)))
168 hasVisibleMarkers = true;
169 listItemCount++;
170 } else if (listItem->node() && listItem->node()->hasTagName(liTag)) {
171 // Inline elements that are in a list with an explicit role should also count.
172 if (m_ariaRole == AccessibilityRole::List)
173 listItemCount++;
174
175 if (childHasPseudoVisibleListItemMarkers(listItem)) {
176 hasVisibleMarkers = true;
177 listItemCount++;
178 }
179 }
180 }
181 }
182
183 // Non <ul> lists and ARIA lists only need to have one child.
184 // <ul>, <ol> lists need to have visible markers.
185 if (ariaRoleAttribute() != AccessibilityRole::Unknown) {
186 if (!listItemCount)
187 role = AccessibilityRole::ApplicationGroup;
188 } else if (!hasVisibleMarkers)
189 role = AccessibilityRole::Group;
190
191 return role;
192}
193
194AccessibilityRole AccessibilityList::roleValue() const
195{
196 ASSERT(m_role != AccessibilityRole::Unknown);
197 return m_role;
198}
199
200} // namespace WebCore
201