blob: dcce269ab7a0a9fa37ab37ba9f8974cc2cc8c840 [file] [log] [blame]
/*
* Copyright (C) 2021 Igalia S.L.
*
* 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. ``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
* 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 "AccessibilityUIElement.h"
#if HAVE(ACCESSIBILITY) && USE(ATSPI)
#include "InjectedBundle.h"
#include "InjectedBundlePage.h"
#include <JavaScriptCore/JSStringRef.h>
#include <JavaScriptCore/OpaqueJSString.h>
#include <WebCore/AccessibilityAtspiEnums.h>
#include <WebCore/AccessibilityObjectAtspi.h>
#include <WebKit/WKBundleFrame.h>
#include <wtf/URL.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringToIntegerConversion.h>
#include <wtf/unicode/CharacterNames.h>
namespace WTR {
RefPtr<AccessibilityController> AccessibilityUIElement::s_controller;
AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element)
: m_element(element)
{
if (!s_controller)
s_controller = InjectedBundle::singleton().accessibilityController();
}
AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other)
: JSWrappable()
, m_element(other.m_element)
{
}
AccessibilityUIElement::~AccessibilityUIElement()
{
}
bool AccessibilityUIElement::isEqual(AccessibilityUIElement* otherElement)
{
return otherElement && m_element.get() == otherElement->platformUIElement();
}
bool AccessibilityUIElement::isIsolatedObject() const
{
return true;
}
void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> >& children)
{
}
void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIElement> >& children, unsigned location, unsigned length)
{
}
int AccessibilityUIElement::childrenCount()
{
unsigned childCount;
s_controller->executeOnAXThreadAndWait([this, &childCount] {
m_element->updateBackingStore();
childCount = m_element->childCount();
});
return childCount;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPoint(int x, int y)
{
auto* element = m_element->hitTest({ x, y }, WebCore::Atspi::CoordinateType::WindowCoordinates);
return AccessibilityUIElement::create(element ? element : m_element.get());
}
unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element)
{
return 0;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::childAtIndex(unsigned index)
{
m_element->updateBackingStore();
RefPtr<WebCore::AccessibilityObjectAtspi> child;
s_controller->executeOnAXThreadAndWait([this, &child, index] {
m_element->updateBackingStore();
child = m_element->childAt(index);
});
if (!child)
return nullptr;
return AccessibilityUIElement::create(child.get());
}
static RefPtr<AccessibilityUIElement> elementForRelationAtIndex(WebCore::AccessibilityObjectAtspi* element, WebCore::Atspi::Relation relation, unsigned index)
{
auto relationMap = element->relationMap();
auto targets = relationMap.get(relation);
if (targets.isEmpty() || index >= targets.size())
return nullptr;
auto target = targets[index];
if (!target)
return nullptr;
return AccessibilityUIElement::create(target.get());
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::linkedUIElementAtIndex(unsigned index)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::NodeParentOf, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::FlowsTo, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::ControllerFor, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedRowAtIndex(unsigned index)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const
{
return nullptr;
}
unsigned AccessibilityUIElement::selectedChildrenCount() const
{
return 0;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedRowAtIndex(unsigned index)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::titleUIElement()
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::LabelledBy, 0);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::parentElement()
{
m_element->updateBackingStore();
RefPtr<WebCore::AccessibilityObjectAtspi> parent;
s_controller->executeOnAXThreadAndWait([this, &parent] {
m_element->updateBackingStore();
auto parentWrapper = m_element->parent();
if (parentWrapper)
parent = parentWrapper.value();
});
if (!parent)
return nullptr;
return AccessibilityUIElement::create(parent.get());
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedByRow()
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfLinkedUIElements()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfDocumentLinks()
{
return JSStringCreateWithCharacters(0, 0);
}
static String attributesOfElement(AccessibilityUIElement& element)
{
StringBuilder builder;
builder.append(element.role()->string(), '\n');
builder.append("AXParent: ");
if (auto parent = element.parentElement()) {
builder.append(parent->role()->string().substring(8));
auto parentName = parent->title()->string().substring(9);
if (!parentName.isEmpty())
builder.append(": ", parentName);
} else
builder.append("(null)");
builder.append('\n');
builder.append("AXChildren: ", element.childrenCount(), '\n');
builder.append("AXPosition: { ", FormattedNumber::fixedPrecision(element.x(), 6, KeepTrailingZeros));
builder.append(", ", FormattedNumber::fixedPrecision(element.y(), 6, KeepTrailingZeros));
builder.append(" }\n");
builder.append("AXSize: { ", FormattedNumber::fixedPrecision(element.width(), 6, KeepTrailingZeros));
builder.append(", ", FormattedNumber::fixedPrecision(element.height(), 6, KeepTrailingZeros));
builder.append(" }\n");
String title = element.title()->string();
if (!title.isEmpty()) {
builder.append(title);
builder.append('\n');
}
String description = element.description()->string();
if (!description.isEmpty())
builder.append(description.utf8().data(), '\n');
String value = element.stringValue()->string();
if (!value.isEmpty())
builder.append(value, '\n');
builder.append("AXFocusable: ", element.isFocusable(), '\n');
builder.append("AXFocused: ", element.isFocused(), '\n');
builder.append("AXSelectable: ", element.isSelectable(), '\n');
builder.append("AXSelected: ", element.isSelected(), '\n');
builder.append("AXMultiSelectable: ", element.isMultiSelectable(), '\n');
builder.append("AXEnabled: ", element.isEnabled(), '\n');
builder.append("AXExpanded: ", element.isExpanded(), '\n');
builder.append("AXRequired: ", element.isRequired(), '\n');
builder.append("AXChecked: ", element.isChecked(), '\n');
String url = element.url()->string();
if (!url.isEmpty())
builder.append(url, '\n');
// We append the platform attributes as a single line at the end.
builder.append("AXPlatformAttributes: ");
auto attributes = element.platformUIElement()->attributes();
bool isFirst = true;
for (const auto& it : attributes) {
if (!isFirst)
builder.append(", ");
isFirst = false;
builder.append(it.key, ':', it.value);
}
builder.append('\n');
return builder.toString();
}
static String attributesOfElements(Vector<RefPtr<AccessibilityUIElement>>& elements)
{
StringBuilder builder;
for (auto& element : elements)
builder.append(attributesOfElement(*element), "\n------------\n");
return builder.toString();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren()
{
m_element->updateBackingStore();
Vector<RefPtr<WebCore::AccessibilityObjectAtspi>> children;
s_controller->executeOnAXThreadAndWait([this, &children] {
m_element->updateBackingStore();
children = m_element->children();
});
Vector<RefPtr<AccessibilityUIElement>> elements;
elements.reserveInitialCapacity(children.size());
for (auto& child : children)
elements.uncheckedAppend(AccessibilityUIElement::create(child.get()));
return OpaqueJSString::tryCreate(attributesOfElements(elements)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::allAttributes()
{
return OpaqueJSString::tryCreate(attributesOfElement(*this)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringDescriptionOfAttributeValue(JSStringRef attribute)
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValue(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
if (attributeName == "AXSelectedText") {
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
m_element->updateBackingStore();
auto text = m_element->text();
auto offset = m_element->selectedRange();
return OpaqueJSString::tryCreate(text.substring(offset.x(), offset.y() - offset.x())).leakRef();
}
auto attributes = m_element->attributes();
if (attributeName == "AXPlaceholderValue")
return OpaqueJSString::tryCreate(attributes.get("placeholder-text")).leakRef();
return JSStringCreateWithCharacters(0, 0);
}
double AccessibilityUIElement::numberAttributeValue(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
auto attributes = m_element->attributes();
if (attributeName == "AXARIASetSize")
return attributes.get("setsize").toDouble();
if (attributeName == "AXARIAPosInSet")
return attributes.get("posinset").toDouble();
return 0;
}
JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attribute) const
{
return nullptr;
}
JSValueRef AccessibilityUIElement::rowHeaders() const
{
return nullptr;
}
JSValueRef AccessibilityUIElement::columnHeaders() const
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
{
return nullptr;
}
static bool checkElementState(WebCore::AccessibilityObjectAtspi* element, WebCore::Atspi::State state)
{
return element->state() & (G_GUINT64_CONSTANT(1) << state);
}
bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
if (attributeName == "AXElementBusy")
return checkElementState(m_element.get(), WebCore::Atspi::State::Busy);
return false;
}
bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
if (attributeName != "AXValue")
return false;
if (checkElementState(m_element.get(), WebCore::Atspi::State::ReadOnly))
return false;
if (checkElementState(m_element.get(), WebCore::Atspi::State::Editable))
return true;
if (checkElementState(m_element.get(), WebCore::Atspi::State::Checkable))
return true;
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value)
&& checkElementState(m_element.get(), WebCore::Atspi::State::Focusable)) {
double minimumValue, maximumValue;
s_controller->executeOnAXThreadAndWait([this, &minimumValue, &maximumValue] {
m_element->updateBackingStore();
minimumValue = m_element->minimumValue();
maximumValue = m_element->maximumValue();
});
if (minimumValue != maximumValue)
return true;
}
return false;
}
bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
auto attributes = m_element->attributes();
if (attributeName == "AXARIASetSize")
return attributes.contains("setsize");
if (attributeName == "AXARIAPosInSet")
return attributes.contains("posinset");
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames()
{
return JSStringCreateWithCharacters(0, 0);
}
static String roleValueToString(unsigned roleValue)
{
switch (roleValue) {
case WebCore::Atspi::Role::Alert:
return "AXAlert"_s;
case WebCore::Atspi::Role::Article:
return "AXArticle"_s;
case WebCore::Atspi::Role::Audio:
return "AXAudio"_s;
case WebCore::Atspi::Role::BlockQuote:
return "AXBlockquote"_s;
case WebCore::Atspi::Role::Canvas:
return "AXCanvas"_s;
case WebCore::Atspi::Role::Caption:
return "AXCaption"_s;
case WebCore::Atspi::Role::CheckBox:
return "AXCheckBox"_s;
case WebCore::Atspi::Role::CheckMenuItem:
return "AXCheckMenuItem"_s;
case WebCore::Atspi::Role::ColorChooser:
return "AXColorWell"_s;
case WebCore::Atspi::Role::ColumnHeader:
case WebCore::Atspi::Role::TableColumnHeader:
return "AXColumnHeader"_s;
case WebCore::Atspi::Role::ComboBox:
return "AXComboBox"_s;
case WebCore::Atspi::Role::Comment:
return "AXComment"_s;
case WebCore::Atspi::Role::ContentDeletion:
return "AXDeletion"_s;
case WebCore::Atspi::Role::ContentInsertion:
return "AXInsertion"_s;
case WebCore::Atspi::Role::Definition:
return "AXDefinition"_s;
case WebCore::Atspi::Role::DescriptionList:
return "AXDescriptionList"_s;
case WebCore::Atspi::Role::DescriptionTerm:
return "AXDescriptionTerm"_s;
case WebCore::Atspi::Role::DescriptionValue:
return "AXDescriptionValue"_s;
case WebCore::Atspi::Role::Dialog:
return "AXDialog"_s;
case WebCore::Atspi::Role::DocumentFrame:
return "AXDocument"_s;
case WebCore::Atspi::Role::DocumentWeb:
return "AXWebArea"_s;
case WebCore::Atspi::Role::Embedded:
return "AXEmbedded"_s;
case WebCore::Atspi::Role::Entry:
return "AXTextField"_s;
case WebCore::Atspi::Role::Footer:
return "AXFooter"_s;
case WebCore::Atspi::Role::Footnote:
return "AXFootnote"_s;
case WebCore::Atspi::Role::Form:
return "AXForm"_s;
case WebCore::Atspi::Role::Grouping:
case WebCore::Atspi::Role::Panel:
return "AXGroup"_s;
case WebCore::Atspi::Role::Heading:
return "AXHeading"_s;
case WebCore::Atspi::Role::Image:
return "AXImage"_s;
case WebCore::Atspi::Role::ImageMap:
return "AXImageMap"_s;
case WebCore::Atspi::Role::InvalidRole:
return "AXInvalid"_s;
case WebCore::Atspi::Role::Label:
return "AXLabel"_s;
case WebCore::Atspi::Role::LevelBar:
return "AXLevelIndicator"_s;
case WebCore::Atspi::Role::Link:
return "AXLink"_s;
case WebCore::Atspi::Role::ListBox:
return "AXListBox"_s;
case WebCore::Atspi::Role::List:
return "AXList"_s;
case WebCore::Atspi::Role::ListItem:
return "AXListItem"_s;
case WebCore::Atspi::Role::Log:
return "AXLog"_s;
case WebCore::Atspi::Role::Marquee:
return "AXMarquee"_s;
case WebCore::Atspi::Role::Math:
return "AXMath"_s;
case WebCore::Atspi::Role::MathFraction:
return "AXMathFraction"_s;
case WebCore::Atspi::Role::MathRoot:
return "AXMathRoot"_s;
case WebCore::Atspi::Role::Menu:
return "AXMenu"_s;
case WebCore::Atspi::Role::MenuBar:
return "AXMenuBar"_s;
case WebCore::Atspi::Role::MenuItem:
return "AXMenuItem"_s;
case WebCore::Atspi::Role::Notification:
return "AXNotification"_s;
case WebCore::Atspi::Role::PageTab:
return "AXTab"_s;
case WebCore::Atspi::Role::PageTabList:
return "AXTabGroup"_s;
case WebCore::Atspi::Role::Paragraph:
return "AXParagraph"_s;
case WebCore::Atspi::Role::PasswordText:
return "AXPasswordField"_s;
case WebCore::Atspi::Role::ProgressBar:
return "AXProgressIndicator"_s;
case WebCore::Atspi::Role::PushButton:
return "AXButton"_s;
case WebCore::Atspi::Role::RadioButton:
return "AXRadioButton"_s;
case WebCore::Atspi::Role::RadioMenuItem:
return "AXRadioMenuItem"_s;
case WebCore::Atspi::Role::RowHeader:
case WebCore::Atspi::Role::TableRowHeader:
return "AXRowHeader"_s;
case WebCore::Atspi::Role::Ruler:
return "AXRuler"_s;
case WebCore::Atspi::Role::ScrollBar:
return "AXScrollBar"_s;
case WebCore::Atspi::Role::ScrollPane:
return "AXScrollArea";
case WebCore::Atspi::Role::Section:
return "AXSection"_s;
case WebCore::Atspi::Role::Separator:
return "AXSeparator"_s;
case WebCore::Atspi::Role::Slider:
return "AXSlider"_s;
case WebCore::Atspi::Role::SpinButton:
return "AXSpinButton"_s;
case WebCore::Atspi::Role::Static:
return "AXStatic"_s;
case WebCore::Atspi::Role::StatusBar:
return "AXStatusBar"_s;
case WebCore::Atspi::Role::Subscript:
return "AXSubscript"_s;
case WebCore::Atspi::Role::Superscript:
return "AXSuperscript"_s;
case WebCore::Atspi::Role::Table:
return "AXTable"_s;
case WebCore::Atspi::Role::TableCell:
return "AXCell"_s;
case WebCore::Atspi::Role::TableRow:
return "AXRow"_s;
case WebCore::Atspi::Role::Timer:
return "AXTimer"_s;
case WebCore::Atspi::Role::ToggleButton:
return "AXToggleButton"_s;
case WebCore::Atspi::Role::ToolBar:
return "AXToolbar"_s;
case WebCore::Atspi::Role::ToolTip:
return "AXUserInterfaceTooltip"_s;
case WebCore::Atspi::Role::Tree:
return "AXTree"_s;
case WebCore::Atspi::Role::TreeTable:
return "AXTreeGrid"_s;
case WebCore::Atspi::Role::TreeItem:
return "AXTreeItem"_s;
case WebCore::Atspi::Role::Unknown:
return "AXUnknown"_s;
case WebCore::Atspi::Role::Video:
return "AXVideo"_s;
case WebCore::Atspi::Role::Window:
return "AXWindow"_s;
default:
break;
}
return { };
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::role()
{
unsigned roleValue;
s_controller->executeOnAXThreadAndWait([this, &roleValue] {
m_element->updateBackingStore();
roleValue = m_element->role();
});
auto roleValueString = roleValueToString(roleValue);
if (roleValueString.isEmpty())
return JSStringCreateWithCharacters(0, 0);
return OpaqueJSString::tryCreate(makeString("AXRole: ", roleValueString)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::subrole()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::roleDescription()
{
auto roleDescription = m_element->attributes().get("roledescription");
return OpaqueJSString::tryCreate(makeString("AXRoleDescription: ", roleDescription)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::computedRoleString()
{
auto computedRole = m_element->attributes().get("computed-role");
if (computedRole.isEmpty())
return JSStringCreateWithCharacters(0, 0);
return OpaqueJSString::tryCreate(computedRole).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::title()
{
String name;
s_controller->executeOnAXThreadAndWait([this, &name] {
m_element->updateBackingStore();
name = String::fromUTF8(m_element->name().data());
});
auto titleValue = makeString("AXTitle: ", name);
return OpaqueJSString::tryCreate(titleValue).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::description()
{
String description;
s_controller->executeOnAXThreadAndWait([this, &description] {
m_element->updateBackingStore();
description = String::fromUTF8(m_element->description().data());
});
auto descriptionValue = makeString("AXDescription: ", description);
return OpaqueJSString::tryCreate(descriptionValue).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const
{
const char* orientation = nullptr;
if (checkElementState(m_element.get(), WebCore::Atspi::State::Horizontal))
orientation = "AXHorizontalOrientation";
else if (checkElementState(m_element.get(), WebCore::Atspi::State::Vertical))
orientation = "AXVerticalOrientation";
else
orientation = "AXUnknownOrientation";
auto orientationValue = makeString("AXOrientation: ", orientation);
return OpaqueJSString::tryCreate(orientationValue).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringValue()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
m_element->updateBackingStore();
auto value = makeString("AXValue: ", m_element->text().replace("\n", "<\\n>").replace(objectReplacementCharacter, "<obj>"));
return OpaqueJSString::tryCreate(value).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::language()
{
m_element->updateBackingStore();
auto locale = m_element->locale();
if (locale.isEmpty())
return JSStringCreateWithCharacters(0, 0);
return OpaqueJSString::tryCreate(makeString("AXLanguage: ", locale)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::helpText() const
{
auto relationMap = m_element->relationMap();
auto targets = relationMap.get(WebCore::Atspi::Relation::DescribedBy);
if (targets.isEmpty())
return JSStringCreateWithCharacters(0, 0);
StringBuilder builder;
builder.append("AXHelp: ");
bool isFirst = true;
for (const auto& target : targets) {
if (!isFirst)
builder.append(' ');
isFirst = false;
target->updateBackingStore();
builder.append(target->text());
}
return OpaqueJSString::tryCreate(builder.toString()).leakRef();
}
double AccessibilityUIElement::x()
{
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).x();
}
double AccessibilityUIElement::y()
{
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).y();
}
double AccessibilityUIElement::width()
{
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).width();
}
double AccessibilityUIElement::height()
{
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).height();
}
double AccessibilityUIElement::clickPointX()
{
auto rect = m_element->elementRect(WebCore::Atspi::CoordinateType::WindowCoordinates);
return rect.center().x();
}
double AccessibilityUIElement::clickPointY()
{
auto rect = m_element->elementRect(WebCore::Atspi::CoordinateType::WindowCoordinates);
return rect.center().y();
}
double AccessibilityUIElement::intValue() const
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return 0;
double currentValue;
s_controller->executeOnAXThreadAndWait([this, &currentValue] {
m_element->updateBackingStore();
currentValue = m_element->currentValue();
});
return currentValue;
}
double AccessibilityUIElement::minValue()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return 0;
double minimumValue;
s_controller->executeOnAXThreadAndWait([this, &minimumValue] {
m_element->updateBackingStore();
minimumValue = m_element->minimumValue();
});
return minimumValue;
}
double AccessibilityUIElement::maxValue()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return 0;
double maximumValue;
s_controller->executeOnAXThreadAndWait([this, &maximumValue] {
m_element->updateBackingStore();
maximumValue = m_element->maximumValue();
});
return maximumValue;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::valueDescription()
{
auto attributes = m_element->attributes();
auto value = makeString("AXValueDescription: ", attributes.get("valuetext"));
return OpaqueJSString::tryCreate(value).leakRef();
}
int AccessibilityUIElement::insertionPointLineNumber()
{
return -1;
}
bool AccessibilityUIElement::isPressActionSupported()
{
m_element->updateBackingStore();
auto name = m_element->actionName();
return name == "press" || name == "jump";
}
bool AccessibilityUIElement::isIncrementActionSupported()
{
return false;
}
bool AccessibilityUIElement::isDecrementActionSupported()
{
return false;
}
bool AccessibilityUIElement::isEnabled()
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Enabled);
}
bool AccessibilityUIElement::isRequired() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Required);
}
bool AccessibilityUIElement::isFocused() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Focused);
}
bool AccessibilityUIElement::isSelected() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Selected);
}
bool AccessibilityUIElement::isSelectedOptionActive() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Active);
}
bool AccessibilityUIElement::isExpanded() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Expanded);
}
bool AccessibilityUIElement::isChecked() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Checked);
}
bool AccessibilityUIElement::isIndeterminate() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Indeterminate);
}
int AccessibilityUIElement::hierarchicalLevel() const
{
auto level = m_element->attributes().get("level");
if (level.isEmpty())
return 0;
return parseIntegerAllowingTrailingJunk<int>(level).value_or(0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::speakAs()
{
return JSStringCreateWithCharacters(0, 0);
}
bool AccessibilityUIElement::ariaIsGrabbed() const
{
return m_element->attributes().get("grabbed") == "true";
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::ariaDropEffects() const
{
auto dropEffects = m_element->attributes().get("dropeffect");
if (dropEffects.isEmpty())
return JSStringCreateWithCharacters(0, 0);
return OpaqueJSString::tryCreate(dropEffects).leakRef();
}
int AccessibilityUIElement::lineForIndex(int index)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return -1;
m_element->updateBackingStore();
auto text = m_element->text();
if (index < 0 || index > static_cast<int>(text.length()))
return -1;
int lineNumber = 0;
for (int i = 0; i < index; ++i) {
if (text[i] == '\n')
lineNumber++;
}
return lineNumber;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForLine(int line)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
WebCore::IntPoint offset;
for (int i = 0; i <= line; ++i)
offset = m_element->boundaryOffset(offset.y(), WebCore::AccessibilityObjectAtspi::TextGranularity::LineStart);
auto range = makeString('{', offset.x(), ", ", offset.y() - offset.x(), '}');
return OpaqueJSString::tryCreate(range).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForPosition(int x, int y)
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRange(unsigned location, unsigned length)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
auto rect = m_element->boundsForRange(location, length, WebCore::Atspi::CoordinateType::WindowCoordinates);
auto bounds = makeString('{', rect.x(), ", ", rect.y(), ", ", rect.width(), ", ", rect.height(), '}');
return OpaqueJSString::tryCreate(bounds).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForRange(unsigned location, unsigned length)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(m_element->text().substring(location, length)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForRange(unsigned location, unsigned length)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
StringBuilder builder;
auto buildAttributes = [&](const WebCore::AccessibilityObjectAtspi::TextAttributes& attributes) {
for (const auto& it : attributes.attributes) {
builder.append("\n\t\t");
builder.append(it.key, ':', it.value);
}
};
builder.append("\n\tDefault text attributes:");
buildAttributes(m_element->textAttributes());
auto text = m_element->text();
int endOffset = 0;
for (unsigned i = location; i < location + length; i = endOffset) {
auto attributes = m_element->textAttributes(i);
auto rangeStart = std::max<int>(location, attributes.startOffset);
auto rangeEnd = std::min<int>(location + length, attributes.endOffset);
builder.append("\n\tRange attributes for '", text.substring(rangeStart, rangeEnd - rangeStart).replace("\n", "<\\n>").replace(objectReplacementCharacter, "<obj>"), "':");
buildAttributes(attributes);
endOffset = attributes.endOffset;
}
return OpaqueJSString::tryCreate(builder.toString()).leakRef();
}
bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length)
{
return false;
}
unsigned AccessibilityUIElement::uiElementCountForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
return 0;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContextRef context, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity)
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader()
{
return JSStringCreateWithCharacters(0, 0);
}
int AccessibilityUIElement::rowCount()
{
return 0;
}
int AccessibilityUIElement::columnCount()
{
return 0;
}
int AccessibilityUIElement::indexInTable()
{
return -1;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rowIndexRange()
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange()
{
return JSStringCreateWithCharacters(0, 0);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::horizontalScrollbar() const
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::verticalScrollbar() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
auto offset = m_element->selectedRange();
auto range = makeString('{', offset.x(), ", ", offset.y() - offset.x(), '}');
return OpaqueJSString::tryCreate(range).leakRef();
}
bool AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return false;
m_element->updateBackingStore();
auto textLength = m_element->text().length();
m_element->setSelectedRange(std::min(location, textLength), std::min(length, textLength));
return true;
}
void AccessibilityUIElement::increment()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return;
m_element->setCurrentValue(intValue() + m_element->minimumIncrement());
}
void AccessibilityUIElement::decrement()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return;
m_element->setCurrentValue(intValue() - m_element->minimumIncrement());
}
void AccessibilityUIElement::showMenu()
{
}
void AccessibilityUIElement::press()
{
m_element->doAction();
}
void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) const
{
}
void AccessibilityUIElement::setSelectedChildAtIndex(unsigned index) const
{
}
void AccessibilityUIElement::removeSelectionAtIndex(unsigned index) const
{
}
void AccessibilityUIElement::clearSelectedChildren() const
{
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::documentEncoding()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Document))
return JSStringCreateWithCharacters(0, 0);
return OpaqueJSString::tryCreate(m_element->documentAttribute("Encoding")).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::documentURI()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Document))
return JSStringCreateWithCharacters(0, 0);
return OpaqueJSString::tryCreate(m_element->documentAttribute("URI")).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::url()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Hyperlink))
return JSStringCreateWithCharacters(0, 0);
m_element->updateBackingStore();
auto axURL = m_element->url();
if (axURL.isNull())
return JSStringCreateWithUTF8CString("AXURL: (null)");
if (axURL.isLocalFile()) {
// Do not expose absolute paths.
auto path = axURL.fileSystemPath();
auto index = path.find("LayoutTests");
if (index != notFound)
path = path.substring(index);
return OpaqueJSString::tryCreate(makeString("AXURL: ", path)).leakRef();
}
return OpaqueJSString::tryCreate(makeString("AXURL: ", axURL.string())).leakRef();
}
bool AccessibilityUIElement::addNotificationListener(JSValueRef functionCallback)
{
return false;
}
bool AccessibilityUIElement::removeNotificationListener()
{
return true;
}
bool AccessibilityUIElement::isFocusable() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Focusable);
}
bool AccessibilityUIElement::isSelectable() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Selectable);
}
bool AccessibilityUIElement::isMultiSelectable() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Multiselectable);
}
bool AccessibilityUIElement::isVisible() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Visible);
}
bool AccessibilityUIElement::isOffScreen() const
{
return !checkElementState(m_element.get(), WebCore::Atspi::State::Showing);
}
bool AccessibilityUIElement::isCollapsed() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::Collapsed);
}
bool AccessibilityUIElement::isIgnored() const
{
return false;
}
bool AccessibilityUIElement::isSingleLine() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::SingleLine);
}
bool AccessibilityUIElement::isMultiLine() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::MultiLine);
}
bool AccessibilityUIElement::hasPopup() const
{
return checkElementState(m_element.get(), WebCore::Atspi::State::HasPopup);
}
void AccessibilityUIElement::takeFocus()
{
}
void AccessibilityUIElement::takeSelection()
{
}
void AccessibilityUIElement::addSelection()
{
}
void AccessibilityUIElement::removeSelection()
{
}
// Text markers
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::lineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForElement(AccessibilityUIElement* element)
{
return nullptr;
}
int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* range)
{
return 0;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousTextMarker(AccessibilityTextMarker* textMarker)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextTextMarker(AccessibilityTextMarker* textMarker)
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForTextMarkerRange(AccessibilityTextMarkerRange* markerRange)
{
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rectsForTextMarkerRange(AccessibilityTextMarkerRange* markerRange, JSStringRef searchText)
{
return JSStringCreateWithCharacters(0, 0);
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForBounds(int x, int y, int width, int height)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForBounds(int x, int y, int width, int height)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForPoint(int x, int y)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::accessibilityElementForTextMarker(AccessibilityTextMarker* marker)
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRange(AccessibilityTextMarkerRange*)
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRangeWithOptions(AccessibilityTextMarkerRange*, bool)
{
return nullptr;
}
bool AccessibilityUIElement::attributedStringForTextMarkerRangeContainsAttribute(JSStringRef attribute, AccessibilityTextMarkerRange* range)
{
return false;
}
int AccessibilityUIElement::indexForTextMarker(AccessibilityTextMarker* marker)
{
return -1;
}
bool AccessibilityUIElement::isTextMarkerValid(AccessibilityTextMarker* textMarker)
{
return false;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForIndex(int textIndex)
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarker()
{
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarker()
{
return nullptr;
}
bool AccessibilityUIElement::setSelectedTextMarkerRange(AccessibilityTextMarkerRange*)
{
return false;
}
void AccessibilityUIElement::scrollToMakeVisible()
{
m_element->scrollToMakeVisible(WebCore::Atspi::ScrollType::Anywhere);
}
void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
{
m_element->scrollToPoint({ x, y }, WebCore::Atspi::CoordinateType::WindowCoordinates);
}
void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
{
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::pathDescription() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPrescriptsDescription() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::classList() const
{
return nullptr;
}
static String stringAtOffset(WebCore::AccessibilityObjectAtspi* element, int offset, WebCore::AccessibilityObjectAtspi::TextGranularity granularity)
{
if (!element || !element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return { };
element->updateBackingStore();
auto text = element->text();
if (offset < 0 || offset > static_cast<int>(text.length()))
return { };
auto bounds = element->boundaryOffset(offset, granularity);
unsigned startOffset = std::max<int>(bounds.x(), 0);
unsigned endOffset = std::min<int>(bounds.y(), text.length());
return makeString(text.substring(startOffset, endOffset - startOffset), ", ", startOffset, ", ", endOffset);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::characterAtOffset(int offset)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(0, 0);
m_element->updateBackingStore();
auto text = m_element->text();
if (offset < 0 || offset > static_cast<int>(text.length()))
return JSStringCreateWithCharacters(0, 0);
auto string = makeString(text.substring(offset, 1), ", ", offset, ", ", offset + 1);
return OpaqueJSString::tryCreate(string).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::wordAtOffset(int offset)
{
return OpaqueJSString::tryCreate(stringAtOffset(m_element.get(), offset, WebCore::AccessibilityObjectAtspi::TextGranularity::WordStart)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::lineAtOffset(int offset)
{
return OpaqueJSString::tryCreate(stringAtOffset(m_element.get(), offset, WebCore::AccessibilityObjectAtspi::TextGranularity::LineStart)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::sentenceAtOffset(int offset)
{
return OpaqueJSString::tryCreate(stringAtOffset(m_element.get(), offset, WebCore::AccessibilityObjectAtspi::TextGranularity::SentenceStart)).leakRef();
}
bool AccessibilityUIElement::replaceTextInRange(JSStringRef, int, int)
{
return false;
}
bool AccessibilityUIElement::insertText(JSStringRef)
{
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::popupValue() const
{
return nullptr;
}
} // namespace WTR
#endif // HAVE(ACCESSIBILITY) && USE(ATSPI)