blob: dad05d5e2d84eac68123b13c3ce528642a56eecd [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 USE(ATSPI)
#include "AccessibilityNotificationHandler.h"
#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/HashSet.h>
#include <wtf/NeverDestroyed.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();
}
void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> >& children)
{
}
void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIElement> >& children, unsigned location, unsigned length)
{
}
int AccessibilityUIElement::childrenCount()
{
m_element->updateBackingStore();
return m_element->childCount();
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPoint(int x, int y)
{
m_element->updateBackingStore();
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();
if (auto* child = m_element->childAt(index))
return AccessibilityUIElement::create(child);
return nullptr;
}
static RefPtr<AccessibilityUIElement> elementForRelationAtIndex(WebCore::AccessibilityObjectAtspi* element, WebCore::Atspi::Relation relation, unsigned index)
{
element->updateBackingStore();
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::ariaOwnsReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::NodeChildOf, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::FlowsTo, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::FlowsFrom, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::ControllerFor, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::ControlledBy, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::LabelledBy, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::LabelFor, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::DescribedBy, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::DescriptionFor, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::Details, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::DetailsFor, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::ErrorMessage, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageReferencingElementAtIndex(unsigned index)
{
return elementForRelationAtIndex(m_element.get(), WebCore::Atspi::Relation::ErrorFor, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedRowAtIndex(unsigned index)
{
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return nullptr;
m_element->updateBackingStore();
auto rows = m_element->rows();
if (index >= rows.size())
return nullptr;
return AccessibilityUIElement::create(rows[index].get());
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Selection))
return nullptr;
m_element->updateBackingStore();
if (auto* selectedChild = m_element->selectedChild(index))
return AccessibilityUIElement::create(selectedChild);
return nullptr;
}
unsigned AccessibilityUIElement::selectedChildrenCount() const
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Selection))
return 0;
m_element->updateBackingStore();
return m_element->selectionCount();
}
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();
if (auto* parent = m_element->parent().value_or(nullptr))
return AccessibilityUIElement::create(parent);
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedByRow()
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfLinkedUIElements()
{
return JSStringCreateWithCharacters(nullptr, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfDocumentLinks()
{
return JSStringCreateWithCharacters(nullptr, 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();
auto keys = copyToVector(attributes.keys());
std::sort(keys.begin(), keys.end(), WTF::codePointCompareLessThan);
bool isFirst = true;
for (const auto& key : keys) {
if (key == "id"_s || key == "toolkit"_s)
continue;
if (!isFirst)
builder.append(", ");
isFirst = false;
builder.append(key, ':', attributes.get(key));
}
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();
}
static Vector<RefPtr<AccessibilityUIElement>> elementsVector(const Vector<RefPtr<WebCore::AccessibilityObjectAtspi>>& wrappers)
{
Vector<RefPtr<AccessibilityUIElement>> elements;
elements.reserveInitialCapacity(wrappers.size());
for (auto& wrapper : wrappers)
elements.uncheckedAppend(AccessibilityUIElement::create(wrapper.get()));
return elements;
}
static String attributesOfElements(const Vector<RefPtr<WebCore::AccessibilityObjectAtspi>>& wrappers)
{
auto elements = elementsVector(wrappers);
return attributesOfElements(elements);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren()
{
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(attributesOfElements(m_element->children())).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::allAttributes()
{
return OpaqueJSString::tryCreate(attributesOfElement(*this)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringDescriptionOfAttributeValue(JSStringRef attribute)
{
return JSStringCreateWithCharacters(nullptr, 0);
}
static bool checkElementState(WebCore::AccessibilityObjectAtspi* element, WebCore::Atspi::State state)
{
return element->state() & (G_GUINT64_CONSTANT(1) << state);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValue(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
if (attributeName == "AXSelectedText"_s) {
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(nullptr, 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();
}
m_element->updateBackingStore();
auto attributes = m_element->attributes();
if (attributeName == "AXPlaceholderValue"_s)
return OpaqueJSString::tryCreate(attributes.get("placeholder-text"_s)).leakRef();
if (attributeName == "AXInvalid"_s) {
auto textAttributes = m_element->textAttributes();
auto value = textAttributes.attributes.get("invalid"_s);
if (value.isEmpty())
value = checkElementState(m_element.get(), WebCore::Atspi::State::InvalidEntry) ? "true"_s : "false"_s;
return OpaqueJSString::tryCreate(value).leakRef();
}
if (attributeName == "AXARIALive"_s)
return OpaqueJSString::tryCreate(attributes.get("live"_s)).leakRef();
if (attributeName == "AXARIARelevant"_s)
return OpaqueJSString::tryCreate(attributes.get("relevant"_s)).leakRef();
return JSStringCreateWithCharacters(nullptr, 0);
}
double AccessibilityUIElement::numberAttributeValue(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
m_element->updateBackingStore();
auto attributes = m_element->attributes();
if (attributeName == "AXARIASetSize"_s)
return attributes.get("setsize"_s).toDouble();
if (attributeName == "AXARIAPosInSet"_s)
return attributes.get("posinset"_s).toDouble();
if (attributeName == "AXARIAColumnCount"_s)
return attributes.get("colcount"_s).toDouble();
if (attributeName == "AXARIARowCount"_s)
return attributes.get("rowcount"_s).toDouble();
if (attributeName == "AXARIAColumnIndex"_s)
return attributes.get("colindex"_s).toDouble();
if (attributeName == "AXARIARowIndex"_s)
return attributes.get("rowindex"_s).toDouble();
if (attributeName == "AXARIAColumnSpan"_s)
return attributes.get("colspan"_s).toDouble();
if (attributeName == "AXARIARowSpan"_s)
return attributes.get("rowspan"_s).toDouble();
return 0;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::currentStateValue() const
{
m_element->updateBackingStore();
auto value = m_element->attributes().get("current"_s);
return OpaqueJSString::tryCreate(!value.isNull() ? value : "false"_s).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::domIdentifier() const
{
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(m_element->attributes().get("id"_s)).leakRef();
}
JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attribute) const
{
return nullptr;
}
static JSValueRef makeJSArray(const Vector<RefPtr<AccessibilityUIElement>>& elements)
{
WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::singleton().page()->page());
JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
size_t elementCount = elements.size();
auto valueElements = makeUniqueArray<JSValueRef>(elementCount);
for (size_t i = 0; i < elementCount; i++)
valueElements[i] = JSObjectMake(context, elements[i]->wrapperClass(), elements[i].get());
return JSObjectMakeArray(context, elementCount, valueElements.get(), nullptr);
}
JSValueRef AccessibilityUIElement::rowHeaders() const
{
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table)) {
m_element->updateBackingStore();
return makeJSArray(elementsVector(m_element->rowHeaders()));
}
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell)) {
m_element->updateBackingStore();
return makeJSArray(elementsVector(m_element->cellRowHeaders()));
}
return makeJSArray({ });
}
JSValueRef AccessibilityUIElement::columnHeaders() const
{
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table)) {
m_element->updateBackingStore();
return makeJSArray(elementsVector(m_element->columnHeaders()));
}
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell)) {
m_element->updateBackingStore();
return makeJSArray(elementsVector(m_element->cellColumnHeaders()));
}
return makeJSArray({ });
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
{
return nullptr;
}
bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
m_element->updateBackingStore();
if (attributeName == "AXElementBusy"_s)
return checkElementState(m_element.get(), WebCore::Atspi::State::Busy);
if (attributeName == "AXModal"_s)
return checkElementState(m_element.get(), WebCore::Atspi::State::Modal);
if (attributeName == "AXSupportsAutoCompletion"_s)
return checkElementState(m_element.get(), WebCore::Atspi::State::SupportsAutocompletion);
if (attributeName == "AXInterfaceTable"_s)
return m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table);
if (attributeName == "AXInterfaceTableCell"_s)
return m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell);
if (attributeName == "AXARIAAtomic"_s)
return m_element->attributes().get("atomic"_s) == "true"_s;
return false;
}
bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
if (attributeName != "AXValue"_s)
return false;
m_element->updateBackingStore();
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;
auto attributes = m_element->attributes();
String isReadOnly = attributes.get("readonly"_s);
if (!isReadOnly.isEmpty())
return isReadOnly == "true"_s ? false : true;
// If we have a listbox or combobox and the value can be set, the options should be selectable.
auto elementRole = m_element->role();
switch (elementRole) {
case WebCore::Atspi::Role::ComboBox:
case WebCore::Atspi::Role::ListBox:
if (auto child = childAtIndex(0)) {
if (elementRole == WebCore::Atspi::Role::ComboBox) {
// First child is the menu.
child = child->childAtIndex(0);
}
if (child)
return checkElementState(child->m_element.get(), WebCore::Atspi::State::Selectable);
}
break;
default:
break;
}
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value) && checkElementState(m_element.get(), WebCore::Atspi::State::Focusable)) {
if (m_element->minimumValue() != m_element->maximumValue())
return true;
}
return false;
}
bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute)
{
String attributeName = toWTFString(attribute);
m_element->updateBackingStore();
auto attributes = m_element->attributes();
if (attributeName == "AXARIASetSize"_s)
return attributes.contains("setsize"_s);
if (attributeName == "AXARIAPosInSet"_s)
return attributes.contains("posinset"_s);
if (attributeName == "AXARIALive"_s) {
auto value = attributes.get("live"_s);
return !value.isEmpty() && value != "off"_s;
}
if (attributeName == "AXARIARelevant"_s)
return attributes.contains("relevant"_s);
if (attributeName == "AXARIAAtomic"_s)
return attributes.contains("atomic"_s);
if (attributeName == "AXElementBusy"_s)
return true;
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames()
{
return JSStringCreateWithCharacters(nullptr, 0);
}
static String xmlRoleValueString(const String& xmlRoles)
{
static NeverDestroyed<HashSet<String, ASCIICaseInsensitiveHash>> regionRoles = HashSet<String, ASCIICaseInsensitiveHash>({
"doc-acknowledgments"_s,
"doc-afterword"_s,
"doc-appendix"_s,
"doc-bibliography"_s,
"doc-chapter"_s,
"doc-conclusion"_s,
"doc-credits"_s,
"doc-endnotes"_s,
"doc-epilogue"_s,
"doc-errata"_s,
"doc-foreword"_s,
"doc-glossary"_s,
"doc-glossref"_s,
"doc-index"_s,
"doc-introduction"_s,
"doc-pagelist"_s,
"doc-part"_s,
"doc-preface"_s,
"doc-prologue"_s,
"doc-toc"_s,
"region"_s
});
if (regionRoles->contains(xmlRoles))
return "AXLandmarkRegion"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "banner"_s))
return "AXLandmarkBanner"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "complementary"_s))
return "AXLandmarkComplementary"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "contentinfo"_s))
return "AXLandmarkContentInfo"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "form"_s))
return "AXLandmarkForm"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "main"_s))
return "AXLandmarkMain"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "navigation"_s))
return "AXLandmarkNavigation"_s;
if (equalLettersIgnoringASCIICase(xmlRoles, "search"_s))
return "AXLandmarkSearch"_s;
return { };
}
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"_s;
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:
case WebCore::Atspi::Role::Text:
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()
{
m_element->updateBackingStore();
auto roleValue = m_element->role();
auto roleValueString = roleValue == WebCore::Atspi::Role::Landmark ? xmlRoleValueString(m_element->attributes().get("xml-roles"_s)) : roleValueToString(roleValue);
if (roleValueString.isEmpty())
return JSStringCreateWithCharacters(nullptr, 0);
return OpaqueJSString::tryCreate(makeString("AXRole: ", roleValueString)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::subrole()
{
return JSStringCreateWithCharacters(nullptr, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::roleDescription()
{
m_element->updateBackingStore();
auto roleDescription = m_element->attributes().get("roledescription"_s);
return OpaqueJSString::tryCreate(makeString("AXRoleDescription: ", roleDescription)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::computedRoleString()
{
m_element->updateBackingStore();
auto computedRole = m_element->attributes().get("computed-role"_s);
if (computedRole.isEmpty())
return JSStringCreateWithCharacters(nullptr, 0);
return OpaqueJSString::tryCreate(computedRole).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::title()
{
m_element->updateBackingStore();
auto titleValue = makeString("AXTitle: ", String::fromUTF8(m_element->name()));
return OpaqueJSString::tryCreate(titleValue).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::description()
{
m_element->updateBackingStore();
auto descriptionValue = makeString("AXDescription: ", String::fromUTF8(m_element->description()));
return OpaqueJSString::tryCreate(descriptionValue).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const
{
m_element->updateBackingStore();
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();
}
bool AccessibilityUIElement::isAtomicLiveRegion() const
{
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::liveRegionRelevant() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::liveRegionStatus() const
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringValue()
{
m_element->updateBackingStore();
if (m_element->role() == WebCore::Atspi::Role::ComboBox) {
// Tests expect the combo box to expose the selected element name as the string value.
if (auto menu = childAtIndex(0)) {
if (auto* selectedChild = menu->m_element->selectedChild(0))
return OpaqueJSString::tryCreate(makeString("AXValue: ", String::fromUTF8(selectedChild->name()))).leakRef();
}
}
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(nullptr, 0);
auto value = makeString("AXValue: ", makeStringByReplacingAll(makeStringByReplacingAll(m_element->text(), '\n', "<\\n>"_s), objectReplacementCharacter, "<obj>"_s));
return OpaqueJSString::tryCreate(value).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::language()
{
m_element->updateBackingStore();
auto locale = m_element->locale();
if (locale.isEmpty())
return JSStringCreateWithCharacters(nullptr, 0);
return OpaqueJSString::tryCreate(makeString("AXLanguage: ", locale)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::helpText() const
{
m_element->updateBackingStore();
auto relationMap = m_element->relationMap();
auto targets = relationMap.get(WebCore::Atspi::Relation::DescribedBy);
if (targets.isEmpty())
return JSStringCreateWithCharacters(nullptr, 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()
{
m_element->updateBackingStore();
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).x();
}
double AccessibilityUIElement::y()
{
m_element->updateBackingStore();
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).y();
}
double AccessibilityUIElement::width()
{
m_element->updateBackingStore();
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).width();
}
double AccessibilityUIElement::height()
{
m_element->updateBackingStore();
return m_element->elementRect(WebCore::Atspi::CoordinateType::ScreenCoordinates).height();
}
double AccessibilityUIElement::clickPointX()
{
m_element->updateBackingStore();
auto rect = m_element->elementRect(WebCore::Atspi::CoordinateType::WindowCoordinates);
return rect.center().x();
}
double AccessibilityUIElement::clickPointY()
{
m_element->updateBackingStore();
auto rect = m_element->elementRect(WebCore::Atspi::CoordinateType::WindowCoordinates);
return rect.center().y();
}
double AccessibilityUIElement::intValue() const
{
m_element->updateBackingStore();
if (m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return m_element->currentValue();
// Consider headings as an special case when returning the int value.
if (m_element->role() == WebCore::Atspi::Role::Heading)
return m_element->attributes().get("level"_s).toDouble();
return 0;
}
double AccessibilityUIElement::minValue()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return 0;
m_element->updateBackingStore();
return m_element->minimumValue();
}
double AccessibilityUIElement::maxValue()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return 0;
m_element->updateBackingStore();
return m_element->maximumValue();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::valueDescription()
{
m_element->updateBackingStore();
auto attributes = m_element->attributes();
auto value = makeString("AXValueDescription: ", attributes.get("valuetext"_s));
return OpaqueJSString::tryCreate(value).leakRef();
}
int AccessibilityUIElement::insertionPointLineNumber()
{
return -1;
}
bool AccessibilityUIElement::isPressActionSupported()
{
m_element->updateBackingStore();
auto name = m_element->actionName();
return name == "press"_s || name == "jump"_s;
}
bool AccessibilityUIElement::isIncrementActionSupported()
{
return false;
}
bool AccessibilityUIElement::isDecrementActionSupported()
{
return false;
}
bool AccessibilityUIElement::isBusy() const
{
// FIXME: Implement.
return false;
}
bool AccessibilityUIElement::isEnabled()
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Enabled);
}
bool AccessibilityUIElement::isRequired() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Required);
}
bool AccessibilityUIElement::isFocused() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Focused);
}
bool AccessibilityUIElement::isSelected() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Selected);
}
bool AccessibilityUIElement::isSelectedOptionActive() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Active);
}
bool AccessibilityUIElement::isExpanded() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Expanded);
}
bool AccessibilityUIElement::isChecked() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Checked);
}
bool AccessibilityUIElement::isIndeterminate() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Indeterminate);
}
int AccessibilityUIElement::hierarchicalLevel() const
{
m_element->updateBackingStore();
auto level = m_element->attributes().get("level"_s);
if (level.isEmpty())
return 0;
return parseIntegerAllowingTrailingJunk<int>(level).value_or(0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::speakAs()
{
return JSStringCreateWithCharacters(nullptr, 0);
}
bool AccessibilityUIElement::ariaIsGrabbed() const
{
m_element->updateBackingStore();
return m_element->attributes().get("grabbed"_s) == "true"_s;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::ariaDropEffects() const
{
m_element->updateBackingStore();
auto dropEffects = m_element->attributes().get("dropeffect"_s);
if (dropEffects.isEmpty())
return JSStringCreateWithCharacters(nullptr, 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(nullptr, 0);
m_element->updateBackingStore();
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(nullptr, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRange(unsigned location, unsigned length)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Text))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
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(nullptr, 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(nullptr, 0);
m_element->updateBackingStore();
auto text = m_element->text();
auto limit = location + length;
if (limit > text.length())
return JSStringCreateWithCharacters(nullptr, 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);
}
};
m_element->updateBackingStore();
builder.append("\n\tDefault text attributes:");
buildAttributes(m_element->textAttributes());
int endOffset = 0;
for (unsigned i = location; i < limit; i = endOffset) {
auto attributes = m_element->textAttributes(i);
auto rangeStart = std::max<int>(location, attributes.startOffset);
auto rangeEnd = std::min<int>(limit, attributes.endOffset);
builder.append("\n\tRange attributes for '", makeStringByReplacingAll(makeStringByReplacingAll(text.substring(rangeStart, rangeEnd - rangeStart), '\n', "<\\n>"_s), objectReplacementCharacter, "<obj>"_s), "':");
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()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(attributesOfElements(m_element->columnHeaders())).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(attributesOfElements(m_element->rowHeaders())).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
{
return JSStringCreateWithCharacters(nullptr, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(attributesOfElements(m_element->rows())).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(attributesOfElements(m_element->cells())).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader()
{
return JSStringCreateWithCharacters(nullptr, 0);
}
int AccessibilityUIElement::rowCount()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return 0;
m_element->updateBackingStore();
return m_element->rowCount();
}
int AccessibilityUIElement::columnCount()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return 0;
m_element->updateBackingStore();
return m_element->columnCount();
}
int AccessibilityUIElement::indexInTable()
{
return -1;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rowIndexRange()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
auto position = m_element->cellPosition().first;
auto span = m_element->rowSpan();
if (!position || !span)
return JSStringCreateWithCharacters(nullptr, 0);
return OpaqueJSString::tryCreate(makeString('{', *position, ", ", span, '}')).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::TableCell))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
auto position = m_element->cellPosition().second;
auto span = m_element->columnSpan();
if (!position || !span)
return JSStringCreateWithCharacters(nullptr, 0);
return OpaqueJSString::tryCreate(makeString('{', *position, ", ", span, '}')).leakRef();
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned column, unsigned row)
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Table))
return nullptr;
m_element->updateBackingStore();
if (auto* cell = m_element->cell(row, column))
return AccessibilityUIElement::create(cell);
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(nullptr, 0);
m_element->updateBackingStore();
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->updateBackingStore();
m_element->setCurrentValue(intValue() + m_element->minimumIncrement());
}
void AccessibilityUIElement::decrement()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Value))
return;
m_element->updateBackingStore();
m_element->setCurrentValue(intValue() - m_element->minimumIncrement());
}
void AccessibilityUIElement::showMenu()
{
}
void AccessibilityUIElement::press()
{
m_element->updateBackingStore();
m_element->doAction();
}
void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) const
{
}
void AccessibilityUIElement::setSelectedChildAtIndex(unsigned index) const
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Selection))
return;
m_element->updateBackingStore();
m_element->setChildSelected(index, true);
}
void AccessibilityUIElement::removeSelectionAtIndex(unsigned index) const
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Selection))
return;
m_element->updateBackingStore();
m_element->setChildSelected(index, false);
}
void AccessibilityUIElement::clearSelectedChildren() const
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Selection))
return;
m_element->updateBackingStore();
m_element->clearSelection();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const
{
return JSStringCreateWithCharacters(nullptr, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::documentEncoding()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Document))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(m_element->documentAttribute("Encoding"_s)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::documentURI()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Document))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
return OpaqueJSString::tryCreate(m_element->documentAttribute("URI"_s)).leakRef();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::url()
{
if (!m_element->interfaces().contains(WebCore::AccessibilityObjectAtspi::Interface::Hyperlink))
return JSStringCreateWithCharacters(nullptr, 0);
m_element->updateBackingStore();
auto axURL = m_element->url();
if (axURL.isNull())
return JSStringCreateWithUTF8CString("AXURL: (null)");
auto stringURL = axURL.string();
if (axURL.isLocalFile()) {
// Do not expose absolute paths.
auto index = stringURL.find("LayoutTests"_s);
if (index != notFound)
stringURL = stringURL.substring(index);
}
return OpaqueJSString::tryCreate(makeString("AXURL: ", stringURL)).leakRef();
}
bool AccessibilityUIElement::addNotificationListener(JSValueRef functionCallback)
{
if (!functionCallback)
return false;
if (m_notificationHandler)
return false;
m_notificationHandler = makeUnique<AccessibilityNotificationHandler>(functionCallback, m_element.get());
return true;
}
bool AccessibilityUIElement::removeNotificationListener()
{
ASSERT(m_notificationHandler);
m_notificationHandler = nullptr;
return true;
}
bool AccessibilityUIElement::isFocusable() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Focusable);
}
bool AccessibilityUIElement::isSelectable() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Selectable);
}
bool AccessibilityUIElement::isMultiSelectable() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Multiselectable);
}
bool AccessibilityUIElement::isVisible() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Visible);
}
bool AccessibilityUIElement::isOffScreen() const
{
m_element->updateBackingStore();
return !checkElementState(m_element.get(), WebCore::Atspi::State::Showing);
}
bool AccessibilityUIElement::isCollapsed() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::Collapsed);
}
bool AccessibilityUIElement::isIgnored() const
{
m_element->updateBackingStore();
return m_element->isIgnored();
}
bool AccessibilityUIElement::isSingleLine() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::SingleLine);
}
bool AccessibilityUIElement::isMultiLine() const
{
m_element->updateBackingStore();
return checkElementState(m_element.get(), WebCore::Atspi::State::MultiLine);
}
bool AccessibilityUIElement::hasPopup() const
{
m_element->updateBackingStore();
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(nullptr, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rectsForTextMarkerRange(AccessibilityTextMarkerRange* markerRange, JSStringRef searchText)
{
return JSStringCreateWithCharacters(nullptr, 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->updateBackingStore();
m_element->scrollToMakeVisible(WebCore::Atspi::ScrollType::Anywhere);
}
void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
{
m_element->updateBackingStore();
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(nullptr, 0);
m_element->updateBackingStore();
auto text = m_element->text();
if (offset < 0 || offset > static_cast<int>(text.length()))
return JSStringCreateWithCharacters(nullptr, 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 // USE(ATSPI)