357 lines
13 KiB
C++
357 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2019 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#include "config.h"
|
|
#include "InspectorAuditAccessibilityObject.h"
|
|
|
|
#include "AXObjectCache.h"
|
|
#include "AccessibilityNodeObject.h"
|
|
#include "AccessibilityObjectInterface.h"
|
|
#include "ContainerNode.h"
|
|
#include "Document.h"
|
|
#include "ElementIterator.h"
|
|
#include "HTMLNames.h"
|
|
#include "SpaceSplitString.h"
|
|
#include <wtf/Vector.h>
|
|
#include <wtf/text/WTFString.h>
|
|
|
|
namespace WebCore {
|
|
|
|
using namespace Inspector;
|
|
|
|
#define ERROR_IF_NO_ACTIVE_AUDIT() \
|
|
if (!m_auditAgent.hasActiveAudit()) \
|
|
return Exception { NotAllowedError, "Cannot be called outside of a Web Inspector Audit"_s };
|
|
|
|
InspectorAuditAccessibilityObject::InspectorAuditAccessibilityObject(InspectorAuditAgent& auditAgent)
|
|
: m_auditAgent(auditAgent)
|
|
{
|
|
}
|
|
|
|
static AXCoreObject* accessiblityObjectForNode(Node& node)
|
|
{
|
|
if (!AXObjectCache::accessibilityEnabled())
|
|
AXObjectCache::enableAccessibility();
|
|
|
|
if (AXObjectCache* axObjectCache = node.document().axObjectCache())
|
|
return axObjectCache->getOrCreate(&node);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExceptionOr<Vector<Ref<Node>>> InspectorAuditAccessibilityObject::getElementsByComputedRole(Document& document, const String& role, Node* container)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
Vector<Ref<Node>> nodes;
|
|
|
|
for (Element& element : descendantsOfType<Element>(is<ContainerNode>(container) ? downcast<ContainerNode>(*container) : document)) {
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(element)) {
|
|
if (axObject->computedRoleString() == role)
|
|
nodes.append(element);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
ExceptionOr<RefPtr<Node>> InspectorAuditAccessibilityObject::getActiveDescendant(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
if (AXCoreObject* activeDescendant = axObject->activeDescendant())
|
|
return activeDescendant->node();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static void addChildren(AXCoreObject& parentObject, Vector<RefPtr<Node>>& childNodes)
|
|
{
|
|
for (const auto& childObject : parentObject.children()) {
|
|
if (Node* childNode = childObject->node())
|
|
childNodes.append(childNode);
|
|
else
|
|
addChildren(*childObject, childNodes);
|
|
}
|
|
}
|
|
|
|
ExceptionOr<std::optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getChildNodes(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
std::optional<Vector<RefPtr<Node>>> result;
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
Vector<RefPtr<Node>> childNodes;
|
|
addChildren(*axObject, childNodes);
|
|
result = WTFMove(childNodes);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExceptionOr<std::optional<InspectorAuditAccessibilityObject::ComputedProperties>> InspectorAuditAccessibilityObject::getComputedProperties(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
std::optional<InspectorAuditAccessibilityObject::ComputedProperties> result;
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
ComputedProperties computedProperties;
|
|
|
|
AXCoreObject* current = axObject;
|
|
while (current && (!computedProperties.busy || !computedProperties.busy.value())) {
|
|
computedProperties.busy = current->isBusy();
|
|
current = current->parentObject();
|
|
}
|
|
|
|
if (axObject->supportsChecked()) {
|
|
AccessibilityButtonState checkValue = axObject->checkboxOrRadioValue();
|
|
if (checkValue == AccessibilityButtonState::On)
|
|
computedProperties.checked = "true"_s;
|
|
else if (checkValue == AccessibilityButtonState::Mixed)
|
|
computedProperties.checked = "mixed"_s;
|
|
else if (axObject->isChecked())
|
|
computedProperties.checked = "true"_s;
|
|
else
|
|
computedProperties.checked = "false"_s;
|
|
}
|
|
|
|
switch (axObject->currentState()) {
|
|
case AccessibilityCurrentState::False:
|
|
computedProperties.currentState = "false"_s;
|
|
break;
|
|
case AccessibilityCurrentState::True:
|
|
computedProperties.currentState = "true"_s;
|
|
break;
|
|
case AccessibilityCurrentState::Page:
|
|
computedProperties.currentState = "page"_s;
|
|
break;
|
|
case AccessibilityCurrentState::Step:
|
|
computedProperties.currentState = "step"_s;
|
|
break;
|
|
case AccessibilityCurrentState::Location:
|
|
computedProperties.currentState = "location"_s;
|
|
break;
|
|
case AccessibilityCurrentState::Date:
|
|
computedProperties.currentState = "date"_s;
|
|
break;
|
|
case AccessibilityCurrentState::Time:
|
|
computedProperties.currentState = "time"_s;
|
|
break;
|
|
}
|
|
|
|
computedProperties.disabled = !axObject->isEnabled();
|
|
|
|
if (axObject->supportsExpanded())
|
|
computedProperties.expanded = axObject->isExpanded();
|
|
|
|
if (is<Element>(node) && axObject->canSetFocusAttribute())
|
|
computedProperties.focused = axObject->isFocused();
|
|
|
|
computedProperties.headingLevel = axObject->headingLevel();
|
|
computedProperties.hidden = axObject->isAXHidden() || axObject->isDOMHidden();
|
|
computedProperties.hierarchicalLevel = axObject->hierarchicalLevel();
|
|
computedProperties.ignored = axObject->accessibilityIsIgnored();
|
|
computedProperties.ignoredByDefault = axObject->accessibilityIsIgnoredByDefault();
|
|
|
|
String invalidValue = axObject->invalidStatus();
|
|
if (invalidValue == "false")
|
|
computedProperties.invalidStatus = "false"_s;
|
|
else if (invalidValue == "grammar")
|
|
computedProperties.invalidStatus = "grammar"_s;
|
|
else if (invalidValue == "spelling")
|
|
computedProperties.invalidStatus = "spelling"_s;
|
|
else
|
|
computedProperties.invalidStatus = "true"_s;
|
|
|
|
computedProperties.isPopUpButton = axObject->isPopUpButton() || axObject->hasPopup();
|
|
computedProperties.label = axObject->computedLabel();
|
|
|
|
if (axObject->supportsLiveRegion()) {
|
|
computedProperties.liveRegionAtomic = axObject->liveRegionAtomic();
|
|
|
|
String ariaRelevantAttrValue = axObject->liveRegionRelevant();
|
|
if (!ariaRelevantAttrValue.isEmpty()) {
|
|
Vector<String> liveRegionRelevant;
|
|
String ariaRelevantAdditions = "additions";
|
|
String ariaRelevantRemovals = "removals";
|
|
String ariaRelevantText = "text";
|
|
|
|
const auto& values = SpaceSplitString(ariaRelevantAttrValue, true);
|
|
if (values.contains("all")) {
|
|
liveRegionRelevant.append(ariaRelevantAdditions);
|
|
liveRegionRelevant.append(ariaRelevantRemovals);
|
|
liveRegionRelevant.append(ariaRelevantText);
|
|
} else {
|
|
if (values.contains(ariaRelevantAdditions))
|
|
liveRegionRelevant.append(ariaRelevantAdditions);
|
|
if (values.contains(ariaRelevantRemovals))
|
|
liveRegionRelevant.append(ariaRelevantRemovals);
|
|
if (values.contains(ariaRelevantText))
|
|
liveRegionRelevant.append(ariaRelevantText);
|
|
}
|
|
computedProperties.liveRegionRelevant = liveRegionRelevant;
|
|
}
|
|
|
|
computedProperties.liveRegionStatus = axObject->liveRegionStatus();
|
|
}
|
|
|
|
computedProperties.pressed = axObject->pressedIsPresent() && axObject->isPressed();
|
|
|
|
if (axObject->isTextControl())
|
|
computedProperties.readonly = !axObject->canSetValueAttribute();
|
|
|
|
if (axObject->supportsRequiredAttribute())
|
|
computedProperties.required = axObject->isRequired();
|
|
|
|
computedProperties.role = axObject->computedRoleString();
|
|
computedProperties.selected = axObject->isSelected();
|
|
|
|
result = computedProperties;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExceptionOr<std::optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getControlledNodes(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
std::optional<Vector<RefPtr<Node>>> result;
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
Vector<RefPtr<Node>> controlledNodes;
|
|
|
|
Vector<Element*> controlledElements;
|
|
axObject->elementsFromAttribute(controlledElements, HTMLNames::aria_controlsAttr);
|
|
for (Element* controlledElement : controlledElements) {
|
|
if (controlledElement)
|
|
controlledNodes.append(controlledElement);
|
|
}
|
|
|
|
result = WTFMove(controlledNodes);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExceptionOr<std::optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getFlowedNodes(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
std::optional<Vector<RefPtr<Node>>> result;
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
Vector<RefPtr<Node>> flowedNodes;
|
|
|
|
Vector<Element*> flowedElements;
|
|
axObject->elementsFromAttribute(flowedElements, HTMLNames::aria_flowtoAttr);
|
|
for (Element* flowedElement : flowedElements) {
|
|
if (flowedElement)
|
|
flowedNodes.append(flowedElement);
|
|
}
|
|
|
|
result = WTFMove(flowedNodes);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExceptionOr<RefPtr<Node>> InspectorAuditAccessibilityObject::getMouseEventNode(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
if (is<AccessibilityNodeObject>(axObject))
|
|
return downcast<AccessibilityNodeObject>(axObject)->mouseButtonListener(MouseButtonListenerResultFilter::IncludeBodyElement);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExceptionOr<std::optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getOwnedNodes(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
std::optional<Vector<RefPtr<Node>>> result;
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
if (axObject->supportsARIAOwns()) {
|
|
Vector<RefPtr<Node>> ownedNodes;
|
|
|
|
Vector<Element*> ownedElements;
|
|
axObject->elementsFromAttribute(ownedElements, HTMLNames::aria_ownsAttr);
|
|
for (Element* ownedElement : ownedElements) {
|
|
if (ownedElement)
|
|
ownedNodes.append(ownedElement);
|
|
}
|
|
|
|
result = WTFMove(ownedNodes);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExceptionOr<RefPtr<Node>> InspectorAuditAccessibilityObject::getParentNode(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
if (AXCoreObject* parentObject = axObject->parentObjectUnignored())
|
|
return parentObject->node();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
ExceptionOr<std::optional<Vector<RefPtr<Node>>>> InspectorAuditAccessibilityObject::getSelectedChildNodes(Node& node)
|
|
{
|
|
ERROR_IF_NO_ACTIVE_AUDIT();
|
|
|
|
std::optional<Vector<RefPtr<Node>>> result;
|
|
|
|
if (AXCoreObject* axObject = accessiblityObjectForNode(node)) {
|
|
Vector<RefPtr<Node>> selectedChildNodes;
|
|
|
|
AXCoreObject::AccessibilityChildrenVector selectedChildren;
|
|
axObject->selectedChildren(selectedChildren);
|
|
for (auto& selectedChildObject : selectedChildren) {
|
|
if (Node* selectedChildNode = selectedChildObject->node())
|
|
selectedChildNodes.append(selectedChildNode);
|
|
}
|
|
|
|
result = WTFMove(selectedChildNodes);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace WebCore
|