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 | |
43 | namespace WebCore { |
44 | |
45 | using 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 | |
51 | InspectorAuditAccessibilityObject::InspectorAuditAccessibilityObject(InspectorAuditAgent& auditAgent) |
52 | : m_auditAgent(auditAgent) |
53 | { |
54 | } |
55 | |
56 | static 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 | |
67 | ExceptionOr<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 | |
83 | ExceptionOr<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 | |
95 | static 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 | |
105 | ExceptionOr<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 | |
120 | ExceptionOr<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 | |
244 | ExceptionOr<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 | |
266 | ExceptionOr<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 | |
288 | ExceptionOr<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 | |
300 | ExceptionOr<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 | |
324 | ExceptionOr<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 | |
336 | ExceptionOr<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 | |