1/*
2 * Copyright (C) 2019 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 * 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
27#include "config.h"
28#include "InspectorAuditAccessibilityObject.h"
29
30#include "AXObjectCache.h"
31#include "AccessibilityNodeObject.h"
32#include "AccessibilityObject.h"
33#include "ContainerNode.h"
34#include "Document.h"
35#include "Element.h"
36#include "ElementDescendantIterator.h"
37#include "HTMLNames.h"
38#include "Node.h"
39#include "SpaceSplitString.h"
40#include <wtf/Vector.h>
41#include <wtf/text/WTFString.h>
42
43namespace WebCore {
44
45using namespace Inspector;
46
47#define ERROR_IF_NO_ACTIVE_AUDIT() \
48 if (!m_auditAgent.hasActiveAudit()) \
49 return Exception { NotAllowedError, "Cannot be called outside of a Web Inspector Audit"_s };
50
51InspectorAuditAccessibilityObject::InspectorAuditAccessibilityObject(InspectorAuditAgent& auditAgent)
52 : m_auditAgent(auditAgent)
53{
54}
55
56static AccessibilityObject* accessiblityObjectForNode(Node& node)
57{
58 if (!AXObjectCache::accessibilityEnabled())
59 AXObjectCache::enableAccessibility();
60
61 if (AXObjectCache* axObjectCache = node.document().axObjectCache())
62 return axObjectCache->getOrCreate(&node);
63
64 return nullptr;
65}
66
67ExceptionOr<Vector<Ref<Node>>> InspectorAuditAccessibilityObject::getElementsByComputedRole(Document& document, const String& role, Node* container)
68{
69 ERROR_IF_NO_ACTIVE_AUDIT();
70
71 Vector<Ref<Node>> nodes;
72
73 for (Element& element : elementDescendants(is<ContainerNode>(container) ? downcast<ContainerNode>(*container) : document)) {
74 if (AccessibilityObject* axObject = accessiblityObjectForNode(element)) {
75 if (axObject->computedRoleString() == role)
76 nodes.append(element);
77 }
78 }
79
80 return nodes;
81}
82
83ExceptionOr<RefPtr<Node>> InspectorAuditAccessibilityObject::getActiveDescendant(Node& node)
84{
85 ERROR_IF_NO_ACTIVE_AUDIT();
86
87 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
88 if (AccessibilityObject* activeDescendant = axObject->activeDescendant())
89 return activeDescendant->node();
90 }
91
92 return nullptr;
93}
94
95static void addChildren(AccessibilityObject& parentObject, Vector<RefPtr<Node>>& childNodes)
96{
97 for (const RefPtr<AccessibilityObject>& childObject : parentObject.children()) {
98 if (Node* childNode = childObject->node())
99 childNodes.append(childNode);
100 else
101 addChildren(*childObject, childNodes);
102 }
103}
104
105ExceptionOr<Optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getChildNodes(Node& node)
106{
107 ERROR_IF_NO_ACTIVE_AUDIT();
108
109 Optional<Vector<RefPtr<Node>>> result;
110
111 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
112 Vector<RefPtr<Node>> childNodes;
113 addChildren(*axObject, childNodes);
114 result = WTFMove(childNodes);
115 }
116
117 return result;
118}
119
120ExceptionOr<Optional<InspectorAuditAccessibilityObject::ComputedProperties>> InspectorAuditAccessibilityObject::getComputedProperties(Node& node)
121{
122 ERROR_IF_NO_ACTIVE_AUDIT();
123
124 Optional<InspectorAuditAccessibilityObject::ComputedProperties> result;
125
126 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
127 ComputedProperties computedProperties;
128
129 AccessibilityObject* current = axObject;
130 while (current && (!computedProperties.busy || !computedProperties.busy.value())) {
131 computedProperties.busy = current->isBusy();
132 current = current->parentObject();
133 }
134
135 if (axObject->supportsChecked()) {
136 AccessibilityButtonState checkValue = axObject->checkboxOrRadioValue();
137 if (checkValue == AccessibilityButtonState::On)
138 computedProperties.checked = "true"_s;
139 else if (checkValue == AccessibilityButtonState::Mixed)
140 computedProperties.checked = "mixed"_s;
141 else if (axObject->isChecked())
142 computedProperties.checked = "true"_s;
143 else
144 computedProperties.checked = "false"_s;
145 }
146
147 switch (axObject->currentState()) {
148 case AccessibilityCurrentState::False:
149 computedProperties.currentState = "false"_s;
150 break;
151 case AccessibilityCurrentState::True:
152 computedProperties.currentState = "true"_s;
153 break;
154 case AccessibilityCurrentState::Page:
155 computedProperties.currentState = "page"_s;
156 break;
157 case AccessibilityCurrentState::Step:
158 computedProperties.currentState = "step"_s;
159 break;
160 case AccessibilityCurrentState::Location:
161 computedProperties.currentState = "location"_s;
162 break;
163 case AccessibilityCurrentState::Date:
164 computedProperties.currentState = "date"_s;
165 break;
166 case AccessibilityCurrentState::Time:
167 computedProperties.currentState = "time"_s;
168 break;
169 }
170
171 computedProperties.disabled = !axObject->isEnabled();
172
173 if (axObject->supportsExpanded())
174 computedProperties.expanded = axObject->isExpanded();
175
176 if (is<Element>(node) && axObject->canSetFocusAttribute())
177 computedProperties.focused = axObject->isFocused();
178
179 computedProperties.headingLevel = axObject->headingLevel();
180 computedProperties.hidden = axObject->isAXHidden() || axObject->isDOMHidden();
181 computedProperties.hierarchicalLevel = axObject->hierarchicalLevel();
182 computedProperties.ignored = axObject->accessibilityIsIgnored();
183 computedProperties.ignoredByDefault = axObject->accessibilityIsIgnoredByDefault();
184
185 String invalidValue = axObject->invalidStatus();
186 if (invalidValue == "false")
187 computedProperties.invalidStatus = "false"_s;
188 else if (invalidValue == "grammar")
189 computedProperties.invalidStatus = "grammar"_s;
190 else if (invalidValue == "spelling")
191 computedProperties.invalidStatus = "spelling"_s;
192 else
193 computedProperties.invalidStatus = "true"_s;
194
195 computedProperties.isPopUpButton = axObject->isPopUpButton() || axObject->hasPopup();
196 computedProperties.label = axObject->computedLabel();
197
198 if (axObject->supportsLiveRegion()) {
199 computedProperties.liveRegionAtomic = axObject->liveRegionAtomic();
200
201 String ariaRelevantAttrValue = axObject->liveRegionRelevant();
202 if (!ariaRelevantAttrValue.isEmpty()) {
203 Vector<String> liveRegionRelevant;
204 String ariaRelevantAdditions = "additions";
205 String ariaRelevantRemovals = "removals";
206 String ariaRelevantText = "text";
207
208 const auto& values = SpaceSplitString(ariaRelevantAttrValue, true);
209 if (values.contains("all")) {
210 liveRegionRelevant.append(ariaRelevantAdditions);
211 liveRegionRelevant.append(ariaRelevantRemovals);
212 liveRegionRelevant.append(ariaRelevantText);
213 } else {
214 if (values.contains(ariaRelevantAdditions))
215 liveRegionRelevant.append(ariaRelevantAdditions);
216 if (values.contains(ariaRelevantRemovals))
217 liveRegionRelevant.append(ariaRelevantRemovals);
218 if (values.contains(ariaRelevantText))
219 liveRegionRelevant.append(ariaRelevantText);
220 }
221 computedProperties.liveRegionRelevant = liveRegionRelevant;
222 }
223
224 computedProperties.liveRegionStatus = axObject->liveRegionStatus();
225 }
226
227 computedProperties.pressed = axObject->pressedIsPresent() && axObject->isPressed();
228
229 if (axObject->isTextControl())
230 computedProperties.readonly = !axObject->canSetValueAttribute();
231
232 if (axObject->supportsRequiredAttribute())
233 computedProperties.required = axObject->isRequired();
234
235 computedProperties.role = axObject->computedRoleString();
236 computedProperties.selected = axObject->isSelected();
237
238 result = computedProperties;
239 }
240
241 return result;
242}
243
244ExceptionOr<Optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getControlledNodes(Node& node)
245{
246 ERROR_IF_NO_ACTIVE_AUDIT();
247
248 Optional<Vector<RefPtr<Node>>> result;
249
250 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
251 Vector<RefPtr<Node>> controlledNodes;
252
253 Vector<Element*> controlledElements;
254 axObject->elementsFromAttribute(controlledElements, HTMLNames::aria_controlsAttr);
255 for (Element* controlledElement : controlledElements) {
256 if (controlledElement)
257 controlledNodes.append(controlledElement);
258 }
259
260 result = WTFMove(controlledNodes);
261 }
262
263 return result;
264}
265
266ExceptionOr<Optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getFlowedNodes(Node& node)
267{
268 ERROR_IF_NO_ACTIVE_AUDIT();
269
270 Optional<Vector<RefPtr<Node>>> result;
271
272 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
273 Vector<RefPtr<Node>> flowedNodes;
274
275 Vector<Element*> flowedElements;
276 axObject->elementsFromAttribute(flowedElements, HTMLNames::aria_flowtoAttr);
277 for (Element* flowedElement : flowedElements) {
278 if (flowedElement)
279 flowedNodes.append(flowedElement);
280 }
281
282 result = WTFMove(flowedNodes);
283 }
284
285 return result;
286}
287
288ExceptionOr<RefPtr<Node>> InspectorAuditAccessibilityObject::getMouseEventNode(Node& node)
289{
290 ERROR_IF_NO_ACTIVE_AUDIT();
291
292 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
293 if (is<AccessibilityNodeObject>(axObject))
294 return downcast<AccessibilityNodeObject>(axObject)->mouseButtonListener(MouseButtonListenerResultFilter::IncludeBodyElement);
295 }
296
297 return nullptr;
298}
299
300ExceptionOr<Optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getOwnedNodes(Node& node)
301{
302 ERROR_IF_NO_ACTIVE_AUDIT();
303
304 Optional<Vector<RefPtr<Node>>> result;
305
306 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
307 if (axObject->supportsARIAOwns()) {
308 Vector<RefPtr<Node>> ownedNodes;
309
310 Vector<Element*> ownedElements;
311 axObject->elementsFromAttribute(ownedElements, HTMLNames::aria_ownsAttr);
312 for (Element* ownedElement : ownedElements) {
313 if (ownedElement)
314 ownedNodes.append(ownedElement);
315 }
316
317 result = WTFMove(ownedNodes);
318 }
319 }
320
321 return result;
322}
323
324ExceptionOr<RefPtr<Node>> InspectorAuditAccessibilityObject::getParentNode(Node& node)
325{
326 ERROR_IF_NO_ACTIVE_AUDIT();
327
328 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
329 if (AccessibilityObject* parentObject = axObject->parentObjectUnignored())
330 return parentObject->node();
331 }
332
333 return nullptr;
334}
335
336ExceptionOr<Optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getSelectedChildNodes(Node& node)
337{
338 ERROR_IF_NO_ACTIVE_AUDIT();
339
340 Optional<Vector<RefPtr<Node>>> result;
341
342 if (AccessibilityObject* axObject = accessiblityObjectForNode(node)) {
343 Vector<RefPtr<Node>> selectedChildNodes;
344
345 AccessibilityObject::AccessibilityChildrenVector selectedChildren;
346 axObject->selectedChildren(selectedChildren);
347 for (RefPtr<AccessibilityObject>& selectedChildObject : selectedChildren) {
348 if (Node* selectedChildNode = selectedChildObject->node())
349 selectedChildNodes.append(selectedChildNode);
350 }
351
352 result = WTFMove(selectedChildNodes);
353 }
354
355 return result;
356}
357
358} // namespace WebCore
359