blob: f880b8a15c1fa64ef31b9ae75064331588b51252 [file] [log] [blame]
/*
* Copyright (C) 2021 Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "AccessibilityObjectAtspi.h"
#if USE(ATSPI)
#include "AXObjectCache.h"
#include "AccessibilityAtspiEnums.h"
#include "AccessibilityAtspiInterfaces.h"
#include "AccessibilityObjectInterface.h"
#include "AccessibilityRootAtspi.h"
#include "AccessibilityTableCell.h"
#include "ElementInlines.h"
#include "HTMLSpanElement.h"
#include "RenderAncestorIterator.h"
#include "RenderBlock.h"
#include "RenderInline.h"
#include "RenderObject.h"
#include "RenderTableCell.h"
#include "RenderText.h"
#include "TextControlInnerElements.h"
#include "TextIterator.h"
#include <glib/gi18n-lib.h>
#include <wtf/UUID.h>
namespace WebCore {
Ref<AccessibilityObjectAtspi> AccessibilityObjectAtspi::create(AXCoreObject* coreObject, AccessibilityRootAtspi* root)
{
return adoptRef(*new AccessibilityObjectAtspi(coreObject, root));
}
static inline bool roleIsTextType(AccessibilityRole role)
{
return role == AccessibilityRole::Paragraph
|| role == AccessibilityRole::Heading
|| role == AccessibilityRole::Div
|| role == AccessibilityRole::Cell
|| role == AccessibilityRole::Link
|| role == AccessibilityRole::WebCoreLink
|| role == AccessibilityRole::ListItem
|| role == AccessibilityRole::Pre
|| role == AccessibilityRole::GridCell
|| role == AccessibilityRole::TextGroup
|| role == AccessibilityRole::ApplicationTextGroup
|| role == AccessibilityRole::ApplicationGroup;
}
OptionSet<AccessibilityObjectAtspi::Interface> AccessibilityObjectAtspi::interfacesForObject(AXCoreObject& coreObject)
{
OptionSet<Interface> interfaces = { Interface::Accessible, Interface::Component, Interface::Action };
RenderObject* renderer = coreObject.isAccessibilityRenderObject() ? coreObject.renderer() : nullptr;
if (coreObject.roleValue() == AccessibilityRole::StaticText || coreObject.roleValue() == AccessibilityRole::ColorWell)
interfaces.add(Interface::Text);
else if (coreObject.isTextControl() || coreObject.isNonNativeTextControl())
interfaces.add(Interface::Text);
else if (!coreObject.isWebArea()) {
if (coreObject.roleValue() != AccessibilityRole::Table) {
interfaces.add(Interface::Hypertext);
if ((renderer && renderer->childrenInline()) || roleIsTextType(coreObject.roleValue()) || coreObject.isMathToken())
interfaces.add(Interface::Text);
}
}
if (coreObject.supportsRangeValue())
interfaces.add(Interface::Value);
if (coreObject.isLink() || (isRendererReplacedElement(renderer)))
interfaces.add(Interface::Hyperlink);
if (coreObject.roleValue() == AccessibilityRole::WebArea)
interfaces.add(Interface::Document);
if (coreObject.isImage())
interfaces.add(Interface::Image);
if (coreObject.canHaveSelectedChildren())
interfaces.add(Interface::Selection);
if (coreObject.isTable())
interfaces.add(Interface::Table);
if (coreObject.roleValue() == AccessibilityRole::Cell
|| coreObject.roleValue() == AccessibilityRole::GridCell
|| coreObject.roleValue() == AccessibilityRole::ColumnHeader
|| coreObject.roleValue() == AccessibilityRole::RowHeader)
interfaces.add(Interface::TableCell);
if (coreObject.roleValue() == AccessibilityRole::ListMarker && renderer) {
if (renderer->isImage())
interfaces.add(Interface::Image);
else
interfaces.add(Interface::Text);
interfaces.add(Interface::Hyperlink);
}
return interfaces;
}
AccessibilityObjectAtspi::AccessibilityObjectAtspi(AXCoreObject* coreObject, AccessibilityRootAtspi* root)
: m_coreObject(coreObject)
, m_interfaces(interfacesForObject(*m_coreObject))
, m_root(root)
{
}
void AccessibilityObjectAtspi::cacheDestroyed()
{
m_coreObject = nullptr;
if (!m_isRegistered)
return;
if (m_parent && !*m_parent && m_root)
m_root->childRemoved(*this);
AccessibilityAtspi::singleton().unregisterObject(*this);
}
void AccessibilityObjectAtspi::elementDestroyed()
{
m_coreObject = nullptr;
if (!m_isRegistered)
return;
if (m_parent) {
if (*m_parent)
m_parent.value()->childRemoved(*this);
else if (m_root)
m_root->childRemoved(*this);
}
AccessibilityAtspi::singleton().unregisterObject(*this);
}
static unsigned atspiRole(AccessibilityRole role)
{
switch (role) {
case AccessibilityRole::Annotation:
case AccessibilityRole::ApplicationAlert:
return Atspi::Role::Notification;
case AccessibilityRole::ApplicationAlertDialog:
return Atspi::Role::Alert;
case AccessibilityRole::ApplicationDialog:
return Atspi::Role::Dialog;
case AccessibilityRole::ApplicationStatus:
return Atspi::Role::StatusBar;
case AccessibilityRole::Unknown:
return Atspi::Role::Unknown;
case AccessibilityRole::Audio:
return Atspi::Role::Audio;
case AccessibilityRole::Video:
return Atspi::Role::Video;
case AccessibilityRole::Button:
return Atspi::Role::PushButton;
case AccessibilityRole::Switch:
case AccessibilityRole::ToggleButton:
return Atspi::Role::ToggleButton;
case AccessibilityRole::RadioButton:
return Atspi::Role::RadioButton;
case AccessibilityRole::CheckBox:
return Atspi::Role::CheckBox;
case AccessibilityRole::Slider:
return Atspi::Role::Slider;
case AccessibilityRole::TabGroup:
case AccessibilityRole::TabList:
return Atspi::Role::PageTabList;
case AccessibilityRole::TextField:
case AccessibilityRole::TextArea:
case AccessibilityRole::SearchField:
return Atspi::Role::Entry;
case AccessibilityRole::StaticText:
return Atspi::Role::Static;
case AccessibilityRole::Outline:
case AccessibilityRole::Tree:
return Atspi::Role::Tree;
case AccessibilityRole::TreeItem:
return Atspi::Role::TreeItem;
case AccessibilityRole::MenuBar:
return Atspi::Role::MenuBar;
case AccessibilityRole::MenuListPopup:
case AccessibilityRole::Menu:
return Atspi::Role::Menu;
case AccessibilityRole::MenuListOption:
case AccessibilityRole::MenuItem:
case AccessibilityRole::MenuButton:
return Atspi::Role::MenuItem;
case AccessibilityRole::MenuItemCheckbox:
return Atspi::Role::CheckMenuItem;
case AccessibilityRole::MenuItemRadio:
return Atspi::Role::RadioMenuItem;
case AccessibilityRole::Column:
return Atspi::Role::Unknown; // There's no TableColumn in atspi.
case AccessibilityRole::Row:
return Atspi::Role::TableRow;
case AccessibilityRole::Toolbar:
return Atspi::Role::ToolBar;
case AccessibilityRole::Meter:
return Atspi::Role::LevelBar;
case AccessibilityRole::BusyIndicator:
case AccessibilityRole::ProgressIndicator:
return Atspi::Role::ProgressBar;
case AccessibilityRole::Window:
return Atspi::Role::Window;
case AccessibilityRole::PopUpButton:
case AccessibilityRole::ComboBox:
return Atspi::Role::ComboBox;
case AccessibilityRole::SplitGroup:
return Atspi::Role::SplitPane;
case AccessibilityRole::Splitter:
return Atspi::Role::Separator;
case AccessibilityRole::ColorWell:
return Atspi::Role::PushButton;
case AccessibilityRole::List:
return Atspi::Role::List;
case AccessibilityRole::ScrollBar:
return Atspi::Role::ScrollBar;
case AccessibilityRole::ScrollArea:
case AccessibilityRole::TabPanel:
return Atspi::Role::ScrollPane;
case AccessibilityRole::Grid:
case AccessibilityRole::Table:
return Atspi::Role::Table;
case AccessibilityRole::TreeGrid:
return Atspi::Role::TreeTable;
case AccessibilityRole::Application:
return Atspi::Role::Application;
case AccessibilityRole::ApplicationGroup:
case AccessibilityRole::Feed:
case AccessibilityRole::Figure:
case AccessibilityRole::GraphicsObject:
case AccessibilityRole::Group:
case AccessibilityRole::RadioGroup:
case AccessibilityRole::SVGRoot:
return Atspi::Role::Panel;
case AccessibilityRole::RowHeader:
return Atspi::Role::RowHeader;
case AccessibilityRole::ColumnHeader:
return Atspi::Role::ColumnHeader;
case AccessibilityRole::Caption:
return Atspi::Role::Caption;
case AccessibilityRole::Cell:
case AccessibilityRole::GridCell:
return Atspi::Role::TableCell;
case AccessibilityRole::Link:
case AccessibilityRole::WebCoreLink:
case AccessibilityRole::ImageMapLink:
return Atspi::Role::Link;
case AccessibilityRole::ImageMap:
return Atspi::Role::ImageMap;
case AccessibilityRole::GraphicsSymbol:
case AccessibilityRole::Image:
return Atspi::Role::Image;
case AccessibilityRole::DocumentArticle:
return Atspi::Role::Article;
case AccessibilityRole::Document:
case AccessibilityRole::GraphicsDocument:
return Atspi::Role::DocumentFrame;
case AccessibilityRole::DocumentNote:
return Atspi::Role::Comment;
case AccessibilityRole::Heading:
return Atspi::Role::Heading;
case AccessibilityRole::ListBox:
return Atspi::Role::ListBox;
case AccessibilityRole::ListItem:
case AccessibilityRole::ListBoxOption:
return Atspi::Role::ListItem;
case AccessibilityRole::Paragraph:
return Atspi::Role::Paragraph;
case AccessibilityRole::Label:
case AccessibilityRole::Legend:
return Atspi::Role::Label;
case AccessibilityRole::Blockquote:
return Atspi::Role::BlockQuote;
case AccessibilityRole::Footnote:
return Atspi::Role::Footnote;
case AccessibilityRole::ApplicationTextGroup:
case AccessibilityRole::Div:
case AccessibilityRole::Pre:
case AccessibilityRole::SVGText:
case AccessibilityRole::TextGroup:
return Atspi::Role::Section;
case AccessibilityRole::Footer:
return Atspi::Role::Footer;
case AccessibilityRole::Form:
return Atspi::Role::Form;
case AccessibilityRole::Canvas:
return Atspi::Role::Canvas;
case AccessibilityRole::HorizontalRule:
return Atspi::Role::Separator;
case AccessibilityRole::SpinButton:
return Atspi::Role::SpinButton;
case AccessibilityRole::Tab:
return Atspi::Role::PageTab;
case AccessibilityRole::UserInterfaceTooltip:
return Atspi::Role::ToolTip;
case AccessibilityRole::WebArea:
return Atspi::Role::DocumentWeb;
case AccessibilityRole::WebApplication:
return Atspi::Role::Embedded;
case AccessibilityRole::ApplicationLog:
return Atspi::Role::Log;
case AccessibilityRole::ApplicationMarquee:
return Atspi::Role::Marquee;
case AccessibilityRole::ApplicationTimer:
return Atspi::Role::Timer;
case AccessibilityRole::Definition:
return Atspi::Role::Definition;
case AccessibilityRole::DocumentMath:
return Atspi::Role::Math;
case AccessibilityRole::LandmarkBanner:
case AccessibilityRole::LandmarkComplementary:
case AccessibilityRole::LandmarkContentInfo:
case AccessibilityRole::LandmarkDocRegion:
case AccessibilityRole::LandmarkMain:
case AccessibilityRole::LandmarkNavigation:
case AccessibilityRole::LandmarkRegion:
case AccessibilityRole::LandmarkSearch:
return Atspi::Role::Landmark;
case AccessibilityRole::DescriptionList:
return Atspi::Role::DescriptionList;
case AccessibilityRole::Term:
case AccessibilityRole::DescriptionListTerm:
return Atspi::Role::DescriptionTerm;
case AccessibilityRole::DescriptionListDetail:
return Atspi::Role::DescriptionValue;
case AccessibilityRole::Deletion:
return Atspi::Role::ContentDeletion;
case AccessibilityRole::Insertion:
return Atspi::Role::ContentInsertion;
case AccessibilityRole::Subscript:
return Atspi::Role::Subscript;
case AccessibilityRole::Superscript:
return Atspi::Role::Superscript;
case AccessibilityRole::Inline:
case AccessibilityRole::SVGTextPath:
case AccessibilityRole::SVGTSpan:
case AccessibilityRole::Time:
return Atspi::Role::Static;
case AccessibilityRole::Directory:
return Atspi::Role::DirectoryPane;
case AccessibilityRole::Mark:
return Atspi::Role::Mark;
case AccessibilityRole::Browser:
case AccessibilityRole::Details:
case AccessibilityRole::DisclosureTriangle:
case AccessibilityRole::Drawer:
case AccessibilityRole::EditableText:
case AccessibilityRole::GrowArea:
case AccessibilityRole::HelpTag:
case AccessibilityRole::Ignored:
case AccessibilityRole::Incrementor:
case AccessibilityRole::Matte:
case AccessibilityRole::Model:
case AccessibilityRole::Presentational:
case AccessibilityRole::RowGroup:
case AccessibilityRole::RubyBase:
case AccessibilityRole::RubyBlock:
case AccessibilityRole::RubyInline:
case AccessibilityRole::RubyRun:
case AccessibilityRole::RubyText:
case AccessibilityRole::Ruler:
case AccessibilityRole::RulerMarker:
case AccessibilityRole::Sheet:
case AccessibilityRole::SliderThumb:
case AccessibilityRole::SpinButtonPart:
case AccessibilityRole::Summary:
case AccessibilityRole::SystemWide:
case AccessibilityRole::TableHeaderContainer:
case AccessibilityRole::ValueIndicator:
return Atspi::Role::Unknown;
case AccessibilityRole::ListMarker:
case AccessibilityRole::MathElement:
RELEASE_ASSERT_NOT_REACHED();
}
RELEASE_ASSERT_NOT_REACHED();
}
GDBusInterfaceVTable AccessibilityObjectAtspi::s_accessibleFunctions = {
// method_call
[](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* methodName, GVariant* parameters, GDBusMethodInvocation* invocation, gpointer userData) {
auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) };
atspiObject->updateBackingStore();
if (!g_strcmp0(methodName, "GetRole"))
g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", atspiObject->role()));
else if (!g_strcmp0(methodName, "GetRoleName"))
g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", atspiObject->roleName().utf8().data()));
else if (!g_strcmp0(methodName, "GetLocalizedRoleName"))
g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", atspiObject->localizedRoleName()));
else if (!g_strcmp0(methodName, "GetState")) {
GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(au)"));
auto states = atspiObject->state();
g_variant_builder_open(&builder, G_VARIANT_TYPE("au"));
g_variant_builder_add(&builder, "u", static_cast<uint32_t>(states & 0xffffffff));
g_variant_builder_add(&builder, "u", static_cast<uint32_t>(states >> 32));
g_variant_builder_close(&builder);
g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder));
} else if (!g_strcmp0(methodName, "GetAttributes")) {
GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("(a{ss})"));
g_variant_builder_open(&builder, G_VARIANT_TYPE("a{ss}"));
atspiObject->buildAttributes(&builder);
g_variant_builder_close(&builder);
g_dbus_method_invocation_return_value(invocation, g_variant_builder_end(&builder));
} else if (!g_strcmp0(methodName, "GetApplication"))
g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", AccessibilityAtspi::singleton().applicationReference()));
else if (!g_strcmp0(methodName, "GetChildAtIndex")) {
int index;
g_variant_get(parameters, "(i)", &index);
auto* wrapper = index >= 0 ? atspiObject->childAt(index) : nullptr;
g_dbus_method_invocation_return_value(invocation, g_variant_new("(@(so))", wrapper ? wrapper->reference() : AccessibilityAtspi::singleton().nullReference()));
} else if (!g_strcmp0(methodName, "GetChildren")) {
GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)"));
for (const auto& wrapper : atspiObject->children())
g_variant_builder_add(&builder, "@(so)", wrapper->reference());
g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(so))", &builder));
} else if (!g_strcmp0(methodName, "GetIndexInParent")) {
g_dbus_method_invocation_return_value(invocation, g_variant_new("(i)", atspiObject->indexInParent()));
} else if (!g_strcmp0(methodName, "GetRelationSet")) {
GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(ua(so))"));
atspiObject->buildRelationSet(&builder);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(a(ua(so)))", &builder));
} else if (!g_strcmp0(methodName, "GetInterfaces")) {
GVariantBuilder builder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as"));
atspiObject->buildInterfaces(&builder);
g_dbus_method_invocation_return_value(invocation, g_variant_new("(as)", &builder));
}
},
// get_property
[](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* propertyName, GError** error, gpointer userData) -> GVariant* {
auto atspiObject = Ref { *static_cast<AccessibilityObjectAtspi*>(userData) };
atspiObject->updateBackingStore();
if (!g_strcmp0(propertyName, "Name"))
return g_variant_new_string(atspiObject->name().data());
if (!g_strcmp0(propertyName, "Description"))
return g_variant_new_string(atspiObject->description().data());
if (!g_strcmp0(propertyName, "Locale"))
return g_variant_new_string(atspiObject->locale().utf8().data());
if (!g_strcmp0(propertyName, "AccessibleId"))
return g_variant_new_string(atspiObject->m_coreObject ? String::number(atspiObject->m_coreObject->objectID().toUInt64()).utf8().data() : "");
if (!g_strcmp0(propertyName, "Parent"))
return atspiObject->parentReference();
if (!g_strcmp0(propertyName, "ChildCount"))
return g_variant_new_int32(atspiObject->childCount());
g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Unknown property '%s'", propertyName);
return nullptr;
},
// set_property,
nullptr,
// padding
{ nullptr }
};
bool AccessibilityObjectAtspi::registerObject()
{
if (!m_path.isNull())
return false;
m_isRegistered = true;
Vector<std::pair<GDBusInterfaceInfo*, GDBusInterfaceVTable*>> interfaces;
if (m_interfaces.contains(Interface::Accessible))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_accessible_interface), &s_accessibleFunctions });
if (m_interfaces.contains(Interface::Component))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_component_interface), &s_componentFunctions });
if (m_interfaces.contains(Interface::Text))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_text_interface), &s_textFunctions });
if (m_interfaces.contains(Interface::Value))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_value_interface), &s_valueFunctions });
if (m_interfaces.contains(Interface::Hyperlink))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_hyperlink_interface), &s_hyperlinkFunctions });
if (m_interfaces.contains(Interface::Hypertext))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_hypertext_interface), &s_hypertextFunctions });
if (m_interfaces.contains(Interface::Action))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_action_interface), &s_actionFunctions });
if (m_interfaces.contains(Interface::Document))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_document_interface), &s_documentFunctions });
if (m_interfaces.contains(Interface::Image))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_image_interface), &s_imageFunctions });
if (m_interfaces.contains(Interface::Selection))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_selection_interface), &s_selectionFunctions });
if (m_interfaces.contains(Interface::Table))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_table_interface), &s_tableFunctions });
if (m_interfaces.contains(Interface::TableCell))
interfaces.append({ const_cast<GDBusInterfaceInfo*>(&webkit_table_cell_interface), &s_tableCellFunctions });
m_path = AccessibilityAtspi::singleton().registerObject(*this, WTFMove(interfaces));
return true;
}
void AccessibilityObjectAtspi::didUnregisterObject()
{
m_isRegistered = false;
m_path = { };
}
const String& AccessibilityObjectAtspi::path()
{
registerObject();
return m_path;
}
GVariant* AccessibilityObjectAtspi::reference()
{
return g_variant_new("(so)", AccessibilityAtspi::singleton().uniqueName(), path().utf8().data());
}
GVariant* AccessibilityObjectAtspi::hyperlinkReference()
{
if (m_hyperlinkPath.isNull()) {
registerObject();
m_hyperlinkPath = AccessibilityAtspi::singleton().registerHyperlink(*this, { { const_cast<GDBusInterfaceInfo*>(&webkit_hyperlink_interface), &s_hyperlinkFunctions } });
}
return g_variant_new("(so)", AccessibilityAtspi::singleton().uniqueName(), m_hyperlinkPath.utf8().data());
}
void AccessibilityObjectAtspi::setParent(std::optional<AccessibilityObjectAtspi*> atspiParent)
{
if (m_parent == atspiParent)
return;
m_parent = atspiParent;
if (!m_coreObject || m_coreObject->accessibilityIsIgnored())
return;
AccessibilityAtspi::singleton().parentChanged(*this);
if (m_parent) {
if (*m_parent)
m_parent.value()->childAdded(*this);
else if (m_root)
m_root->childAdded(*this);
}
}
std::optional<AccessibilityObjectAtspi*> AccessibilityObjectAtspi::parent() const
{
if (!m_coreObject)
return std::nullopt;
auto* axParent = m_coreObject->parentObjectUnignored();
if (!axParent)
return nullptr;
if (auto* atspiParent = axParent->wrapper())
return atspiParent;
return std::nullopt;
}
GVariant* AccessibilityObjectAtspi::parentReference() const
{
if (m_parent) {
if (*m_parent)
return m_parent.value()->reference();
if (m_root)
return m_root->reference();
}
return AccessibilityAtspi::singleton().nullReference();
}
unsigned AccessibilityObjectAtspi::childCount() const
{
return m_coreObject ? m_coreObject->children().size() : 0;
}
AccessibilityObjectAtspi* AccessibilityObjectAtspi::childAt(unsigned index) const
{
if (!m_coreObject)
return nullptr;
const auto& children = m_coreObject->children();
if (index >= children.size())
return nullptr;
return children[index]->wrapper();
}
Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::wrapperVector(const Vector<RefPtr<AXCoreObject>>& elements) const
{
Vector<RefPtr<AccessibilityObjectAtspi>> wrappers;
wrappers.reserveInitialCapacity(elements.size());
for (const auto& element : elements) {
if (auto* wrapper = element->wrapper())
wrappers.uncheckedAppend(wrapper);
}
return wrappers;
}
Vector<RefPtr<AccessibilityObjectAtspi>> AccessibilityObjectAtspi::children() const
{
if (!m_coreObject)
return { };
return wrapperVector(m_coreObject->children());
}
int AccessibilityObjectAtspi::indexInParent() const
{
if (!m_coreObject) {
m_indexInParent = -1;
return m_indexInParent;
}
auto* axParent = m_coreObject->parentObjectUnignored();
if (!axParent) {
m_indexInParent = 0;
return m_indexInParent;
}
const auto& children = axParent->children();
unsigned index = 0;
for (const auto& child : children) {
if (child.get() == m_coreObject) {
m_indexInParent = index;
return m_indexInParent;
}
index++;
}
m_indexInParent = -1;
return m_indexInParent;
}
int AccessibilityObjectAtspi::indexInParentForChildrenChanged(AccessibilityAtspi::ChildrenChanged change)
{
if (change == AccessibilityAtspi::ChildrenChanged::Removed)
return m_indexInParent;
updateBackingStore();
return indexInParent();
}
CString AccessibilityObjectAtspi::name() const
{
if (!m_coreObject)
return "";
if (m_coreObject->roleValue() == AccessibilityRole::ListBoxOption || m_coreObject->roleValue() == AccessibilityRole::MenuListOption) {
auto value = m_coreObject->stringValue();
if (!value.isEmpty())
return value.utf8();
}
Vector<AccessibilityText> textOrder;
m_coreObject->accessibilityText(textOrder);
for (const auto& text : textOrder) {
// FIXME: This check is here because AccessibilityNodeObject::titleElementText()
// appends an empty String for the LabelByElementText source when there is a
// titleUIElement(). Removing this check makes some fieldsets lose their name.
if (text.text.isEmpty())
continue;
// WebCore Accessibility should provide us with the text alternative computation
// in the order defined by that spec. So take the first thing that our platform
// does not expose via the AtkObject description.
if (text.textSource != AccessibilityTextSource::Help && text.textSource != AccessibilityTextSource::Summary)
return text.text.utf8();
}
return "";
}
CString AccessibilityObjectAtspi::description() const
{
if (!m_coreObject)
return "";
Vector<AccessibilityText> textOrder;
m_coreObject->accessibilityText(textOrder);
bool nameTextAvailable = false;
for (const auto& text : textOrder) {
// WebCore Accessibility should provide us with the text alternative computation
// in the order defined by that spec. So take the first thing that our platform
// does not expose via the AtkObject name.
if (text.textSource == AccessibilityTextSource::Help || text.textSource == AccessibilityTextSource::Summary)
return text.text.utf8();
// If there is no other text alternative, the title tag contents will have been
// used for the AtkObject name. We don't want to duplicate it here.
if (text.textSource == AccessibilityTextSource::TitleTag && nameTextAvailable)
return text.text.utf8();
nameTextAvailable = true;
}
return "";
}
String AccessibilityObjectAtspi::locale() const
{
return m_coreObject ? m_coreObject->language() : String();
}
static bool shouldIncludeOrientationState(const AXCoreObject& coreObject)
{
return coreObject.isComboBox()
|| coreObject.isRadioGroup()
|| coreObject.isTreeGrid()
|| coreObject.isScrollbar()
|| coreObject.isListBox()
|| coreObject.isMenu()
|| coreObject.isTree()
|| coreObject.isMenuBar()
|| coreObject.isSplitter()
|| coreObject.isTabList()
|| coreObject.isToolbar()
|| coreObject.isSlider();
}
uint64_t AccessibilityObjectAtspi::state() const
{
uint64_t states = 0;
auto addState = [&](Atspi::State atspiState) {
states |= (G_GUINT64_CONSTANT(1) << atspiState);
};
if (!m_coreObject) {
addState(Atspi::State::Defunct);
return states;
}
if (m_coreObject->isEnabled()) {
addState(Atspi::State::Enabled);
addState(Atspi::State::Sensitive);
}
if (m_coreObject->isVisible()) {
addState(Atspi::State::Visible);
if (!m_coreObject->isOffScreen())
addState(Atspi::State::Showing);
}
if (m_coreObject->isSelectedOptionActive() || m_coreObject->currentState() != AccessibilityCurrentState::False)
addState(Atspi::State::Active);
if (m_coreObject->canSetFocusAttribute())
addState(Atspi::State::Focusable);
if (m_coreObject->isFocused() && !m_coreObject->activeDescendant())
addState(Atspi::State::Focused);
else if (m_coreObject->isActiveDescendantOfFocusedContainer()) {
addState(Atspi::State::Focusable);
addState(Atspi::State::Focused);
}
if (m_coreObject->canSetValueAttribute()) {
if (m_coreObject->supportsChecked())
addState(Atspi::State::Checkable);
if (m_coreObject->isTextControl() || m_coreObject->isNonNativeTextControl())
addState(Atspi::State::Editable);
} else if (m_coreObject->supportsReadOnly())
addState(Atspi::State::ReadOnly);
if (m_coreObject->isChecked())
addState(Atspi::State::Checked);
if (m_coreObject->isPressed())
addState(Atspi::State::Pressed);
if (m_coreObject->isRequired())
addState(Atspi::State::Required);
if (m_coreObject->roleValue() == AccessibilityRole::TextArea || m_coreObject->ariaIsMultiline())
addState(Atspi::State::MultiLine);
else if (m_coreObject->roleValue() == AccessibilityRole::TextField || m_coreObject->roleValue() == AccessibilityRole::SearchField)
addState(Atspi::State::SingleLine);
if (m_coreObject->isTextControl())
addState(Atspi::State::SelectableText);
if (m_coreObject->canSetSelectedAttribute())
addState(Atspi::State::Selectable);
if (m_coreObject->isMultiSelectable())
addState(Atspi::State::Multiselectable);
if (m_coreObject->isSelected())
addState(Atspi::State::Selected);
if (m_coreObject->canSetExpandedAttribute())
addState(Atspi::State::Expandable);
if (m_coreObject->isExpanded())
addState(Atspi::State::Expanded);
if (m_coreObject->hasPopup())
addState(Atspi::State::HasPopup);
if (shouldIncludeOrientationState(*m_coreObject)) {
switch (m_coreObject->orientation()) {
case AccessibilityOrientation::Horizontal:
addState(Atspi::State::Horizontal);
break;
case AccessibilityOrientation::Vertical:
addState(Atspi::State::Vertical);
break;
case AccessibilityOrientation::Undefined:
break;
}
}
if (m_coreObject->isIndeterminate())
addState(Atspi::State::Indeterminate);
else if ((m_coreObject->isCheckboxOrRadio() || m_coreObject->isMenuItem() || m_coreObject->isToggleButton()) && m_coreObject->checkboxOrRadioValue() == AccessibilityButtonState::Mixed)
addState(Atspi::State::Indeterminate);
if (m_coreObject->isModalNode())
addState(Atspi::State::Modal);
if (m_coreObject->isBusy())
addState(Atspi::State::Busy);
if (m_coreObject->invalidStatus() != "false"_s)
addState(Atspi::State::InvalidEntry);
if (m_coreObject->supportsAutoComplete() && m_coreObject->autoCompleteValue() != "none"_s)
addState(Atspi::State::SupportsAutocompletion);
return states;
}
bool AccessibilityObjectAtspi::isDefunct() const
{
return !m_coreObject;
}
String AccessibilityObjectAtspi::id() const
{
if (!m_coreObject)
return { };
if (auto* element = m_coreObject->element())
return element->getIdAttribute().string();
return { };
}
HashMap<String, String> AccessibilityObjectAtspi::attributes() const
{
HashMap<String, String> map;
#if PLATFORM(GTK)
map.add("toolkit"_s, "WebKitGTK"_s);
#elif PLATFORM(WPE)
map.add("toolkit"_s, "WPEWebKit"_s);
#endif
if (!m_coreObject)
return map;
String tagName = m_coreObject->tagName();
if (!tagName.isEmpty())
map.add("tag"_s, tagName);
if (auto* element = m_coreObject->element()) {
String id = element->getIdAttribute().string();
if (!id.isEmpty())
map.add("id"_s, id);
}
int level = m_coreObject->isHeading() ? m_coreObject->headingLevel() : m_coreObject->hierarchicalLevel();
if (level)
map.add("level"_s, String::number(level));
int rowCount = m_coreObject->axRowCount();
if (rowCount)
map.add("rowcount"_s, String::number(rowCount));
int columnCount = m_coreObject->axColumnCount();
if (columnCount)
map.add("colcount"_s, String::number(columnCount));
int rowIndex = m_coreObject->axRowIndex();
if (rowIndex != -1)
map.add("rowindex"_s, String::number(rowIndex));
int columnIndex = m_coreObject->axColumnIndex();
if (columnIndex != -1)
map.add("colindex"_s, String::number(columnIndex));
if (is<AccessibilityTableCell>(m_coreObject)) {
auto& cell = downcast<AccessibilityTableCell>(*m_coreObject);
int rowSpan = cell.axRowSpan();
if (rowSpan != -1)
map.add("rowspan"_s, String::number(rowSpan));
int columnSpan = cell.axColumnSpan();
if (columnSpan != -1)
map.add("colspan"_s, String::number(columnSpan));
}
String placeholder = m_coreObject->placeholderValue();
if (!placeholder.isEmpty())
map.add("placeholder-text"_s, placeholder);
if (m_coreObject->supportsAutoComplete())
map.add("autocomplete"_s, m_coreObject->autoCompleteValue());
if (m_coreObject->supportsHasPopup())
map.add("haspopup"_s, m_coreObject->popupValue());
if (m_coreObject->supportsCurrent())
map.add("current"_s, m_coreObject->currentValue());
if (m_coreObject->supportsPosInSet())
map.add("posinset"_s, String::number(m_coreObject->posInSet()));
if (m_coreObject->supportsSetSize())
map.add("setsize"_s, String::number(m_coreObject->setSize()));
// The Core AAM states that an explicitly-set value should be exposed, including "none".
if (static_cast<AccessibilityObject*>(m_coreObject)->hasAttribute(HTMLNames::aria_sortAttr)) {
switch (m_coreObject->sortDirection()) {
case AccessibilitySortDirection::Invalid:
break;
case AccessibilitySortDirection::Ascending:
map.add("sort"_s, "ascending"_s);
break;
case AccessibilitySortDirection::Descending:
map.add("sort"_s, "descending"_s);
break;
case AccessibilitySortDirection::Other:
map.add("sort"_s, "other"_s);
break;
case AccessibilitySortDirection::None:
map.add("sort"_s, "none"_s);
break;
}
}
String isReadOnly = m_coreObject->readOnlyValue();
if (!isReadOnly.isEmpty())
map.add("readonly"_s, isReadOnly);
String valueDescription = m_coreObject->valueDescription();
if (!valueDescription.isEmpty())
map.add("valuetext"_s, valueDescription);
// According to the W3C Core Accessibility API Mappings 1.1, section 5.4.1 General Rules:
// "User agents must expose the WAI-ARIA role string if the API supports a mechanism to do so."
// In the case of ATSPI, the mechanism to do so is an object attribute pair (xml-roles:"string").
// We cannot use the computedRoleString for this purpose because it is not limited to elements
// with ARIA roles, and it might not contain the actual ARIA role value (e.g. DPub ARIA).
String roleString = static_cast<AccessibilityObject*>(m_coreObject)->getAttribute(HTMLNames::roleAttr);
if (!roleString.isEmpty())
map.add("xml-roles"_s, roleString);
String computedRoleString = m_coreObject->computedRoleString();
if (!computedRoleString.isEmpty()) {
map.add("computed-role"_s, computedRoleString);
// The HTML AAM maps several elements to ARIA landmark roles. In order for the type of landmark
// to be obtainable in the same fashion as an ARIA landmark, fall back on the computedRoleString.
// We also want to do this for the style-format-group element types so that the type of format
// group it is doesn't get lost to a generic platform role.
if (m_coreObject->ariaRoleAttribute() == AccessibilityRole::Unknown && (m_coreObject->isLandmark() || m_coreObject->isStyleFormatGroup()))
map.set("xml-roles"_s, computedRoleString);
}
String roleDescription = m_coreObject->roleDescription();
if (!roleDescription.isEmpty())
map.add("roledescription"_s, roleDescription);
String dropEffect = static_cast<AccessibilityObject*>(m_coreObject)->getAttribute(HTMLNames::aria_dropeffectAttr);
if (!dropEffect.isEmpty())
map.add("dropeffect"_s, dropEffect);
if (m_coreObject->supportsDragging())
map.add("grabbed"_s, m_coreObject->isGrabbed() ? "true"_s : "false"_s);
String keyShortcuts = m_coreObject->keyShortcutsValue();
if (!keyShortcuts.isEmpty())
map.add("keyshortcuts"_s, keyShortcuts);
if (m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript) || m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript))
map.add("multiscript-type"_s, "pre"_s);
else if (m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript) || m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript))
map.add("multiscript-type"_s, "post"_s);
if (auto* liveContainer = m_coreObject->liveRegionAncestor(false)) {
auto liveStatus = liveContainer->liveRegionStatus();
map.add("container-live"_s, liveStatus);
auto relevant = liveContainer->liveRegionRelevant();
map.add("container-relevant"_s, relevant);
bool isAtomic = liveContainer->liveRegionAtomic();
if (isAtomic)
map.add("container-atomic"_s, "true"_s);
const String& liveRole = roleString.isEmpty() ? computedRoleString : roleString;
if (!liveRole.isEmpty())
map.add("container-live-role"_s, liveRole);
if (liveContainer == m_coreObject) {
map.add("live"_s, liveStatus);
map.add("relevant"_s, relevant);
if (isAtomic)
map.add("atomic"_s, "true"_s);
} else if (!isAtomic && m_coreObject->liveRegionAtomic())
map.add("atomic"_s, "true"_s);
}
return map;
}
void AccessibilityObjectAtspi::buildAttributes(GVariantBuilder* builder) const
{
for (const auto& it : attributes())
g_variant_builder_add(builder, "{ss}", it.key.utf8().data(), it.value.utf8().data());
}
HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>> AccessibilityObjectAtspi::relationMap() const
{
HashMap<uint32_t, Vector<RefPtr<AccessibilityObjectAtspi>>> map;
if (!m_coreObject)
return map;
auto addRelation = [&](Atspi::Relation relation, const AccessibilityObject::AccessibilityChildrenVector& children) {
Vector<RefPtr<AccessibilityObjectAtspi>> wrappers;
for (const auto& child : children) {
if (auto* wrapper = child->wrapper())
wrappers.append(wrapper);
}
if (!wrappers.isEmpty())
map.add(relation, WTFMove(wrappers));
};
AccessibilityObject::AccessibilityChildrenVector ariaLabelledByElements;
if (m_coreObject->isControl()) {
if (auto* label = m_coreObject->correspondingLabelForControlElement())
ariaLabelledByElements.append(label);
} else if (m_coreObject->isFieldset()) {
if (auto* label = m_coreObject->titleUIElement())
ariaLabelledByElements.append(label);
} else if (m_coreObject->roleValue() == AccessibilityRole::Legend) {
if (auto* renderFieldset = ancestorsOfType<RenderBlock>(*m_coreObject->renderer()).first()) {
if (renderFieldset->isFieldset())
ariaLabelledByElements.append(m_coreObject->axObjectCache()->getOrCreate(renderFieldset));
}
} else if (!m_coreObject->correspondingControlForLabelElement())
ariaLabelledByElements = m_coreObject->labelledByObjects();
addRelation(Atspi::LabelledBy, ariaLabelledByElements);
AccessibilityObject::AccessibilityChildrenVector labelForObjects;
if (auto* control = m_coreObject->correspondingControlForLabelElement())
labelForObjects.append(control);
else
labelForObjects = m_coreObject->labelForObjects();
addRelation(Atspi::LabelFor, labelForObjects);
addRelation(Atspi::FlowsTo, m_coreObject->flowToObjects());
addRelation(Atspi::FlowsFrom, m_coreObject->flowFromObjects());
addRelation(Atspi::DescribedBy, m_coreObject->describedByObjects());
addRelation(Atspi::DescriptionFor, m_coreObject->descriptionForObjects());
addRelation(Atspi::ControllerFor, m_coreObject->controlledObjects());
addRelation(Atspi::ControlledBy, m_coreObject->controllers());
addRelation(Atspi::NodeParentOf, m_coreObject->ownedObjects());
addRelation(Atspi::NodeChildOf, m_coreObject->owners());
addRelation(Atspi::Details, m_coreObject->detailedByObjects());
addRelation(Atspi::DetailsFor, m_coreObject->detailsForObjects());
addRelation(Atspi::ErrorMessage, m_coreObject->errorMessageObjects());
addRelation(Atspi::ErrorFor, m_coreObject->errorMessageForObjects());
return map;
}
void AccessibilityObjectAtspi::buildRelationSet(GVariantBuilder* builder) const
{
for (const auto& it : relationMap()) {
GVariantBuilder arrayBuilder = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("a(so)"));
for (const auto& atspiObject : it.value)
g_variant_builder_add(&arrayBuilder, "@(so)", atspiObject->reference());
g_variant_builder_add(builder, "(ua(so))", it.key, &arrayBuilder);
}
}
void AccessibilityObjectAtspi::buildInterfaces(GVariantBuilder* builder) const
{
if (m_interfaces.contains(Interface::Accessible))
g_variant_builder_add(builder, "s", webkit_accessible_interface.name);
if (m_interfaces.contains(Interface::Component))
g_variant_builder_add(builder, "s", webkit_component_interface.name);
if (m_interfaces.contains(Interface::Text))
g_variant_builder_add(builder, "s", webkit_text_interface.name);
if (m_interfaces.contains(Interface::Value))
g_variant_builder_add(builder, "s", webkit_value_interface.name);
if (m_interfaces.contains(Interface::Hyperlink))
g_variant_builder_add(builder, "s", webkit_hyperlink_interface.name);
if (m_interfaces.contains(Interface::Hypertext))
g_variant_builder_add(builder, "s", webkit_hypertext_interface.name);
if (m_interfaces.contains(Interface::Action))
g_variant_builder_add(builder, "s", webkit_action_interface.name);
if (m_interfaces.contains(Interface::Document))
g_variant_builder_add(builder, "s", webkit_document_interface.name);
if (m_interfaces.contains(Interface::Image))
g_variant_builder_add(builder, "s", webkit_image_interface.name);
if (m_interfaces.contains(Interface::Selection))
g_variant_builder_add(builder, "s", webkit_selection_interface.name);
if (m_interfaces.contains(Interface::Table))
g_variant_builder_add(builder, "s", webkit_table_interface.name);
if (m_interfaces.contains(Interface::TableCell))
g_variant_builder_add(builder, "s", webkit_table_cell_interface.name);
}
void AccessibilityObjectAtspi::serialize(GVariantBuilder* builder) const
{
g_variant_builder_add(builder, "(so)", AccessibilityAtspi::singleton().uniqueName(), m_path.utf8().data());
g_variant_builder_add(builder, "@(so)", AccessibilityAtspi::singleton().applicationReference());
g_variant_builder_add(builder, "@(so)", parentReference());
g_variant_builder_add(builder, "i", indexInParent());
// Do not set the children count in cache, because children are handled by children-changed signals.
g_variant_builder_add(builder, "i", -1);
GVariantBuilder interfaces = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("as"));
buildInterfaces(&interfaces);
g_variant_builder_add(builder, "@as", g_variant_new("as", &interfaces));
g_variant_builder_add(builder, "s", name().data());
g_variant_builder_add(builder, "u", role());
g_variant_builder_add(builder, "s", description().data());
GVariantBuilder states = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE("au"));
auto atspiStates = state();
g_variant_builder_add(&states, "u", static_cast<uint32_t>(atspiStates & 0xffffffff));
g_variant_builder_add(&states, "u", static_cast<uint32_t>(atspiStates >> 32));
g_variant_builder_add(builder, "@au", g_variant_builder_end(&states));
}
void AccessibilityObjectAtspi::childAdded(AccessibilityObjectAtspi& child)
{
if (!m_isRegistered)
return;
if (!m_coreObject || m_coreObject->accessibilityIsIgnored())
return;
AccessibilityAtspi::singleton().childrenChanged(*this, child, AccessibilityAtspi::ChildrenChanged::Added);
}
void AccessibilityObjectAtspi::childRemoved(AccessibilityObjectAtspi& child)
{
if (!m_isRegistered)
return;
AccessibilityAtspi::singleton().childrenChanged(*this, child, AccessibilityAtspi::ChildrenChanged::Removed);
}
void AccessibilityObjectAtspi::stateChanged(const char* name, bool value)
{
AccessibilityAtspi::singleton().stateChanged(*this, name, value);
}
void AccessibilityObjectAtspi::loadEvent(const char* event)
{
AccessibilityAtspi::singleton().loadEvent(*this, event);
}
std::optional<unsigned> AccessibilityObjectAtspi::effectiveRole() const
{
if (m_coreObject->isPasswordField())
return Atspi::Role::PasswordText;
switch (m_coreObject->roleValue()) {
case AccessibilityRole::Form:
if (m_coreObject->ariaRoleAttribute() != AccessibilityRole::Unknown)
return Atspi::Role::Landmark;
break;
case AccessibilityRole::ListMarker: {
auto* renderer = m_coreObject->renderer();
return renderer && renderer->isImage() ? Atspi::Role::Image : Atspi::Role::Text;
}
case AccessibilityRole::MathElement:
if (m_coreObject->isMathRow())
return Atspi::Role::Panel;
if (m_coreObject->isMathTable())
return Atspi::Role::Table;
if (m_coreObject->isMathTableRow())
return Atspi::Role::TableRow;
if (m_coreObject->isMathTableCell())
return Atspi::Role::TableCell;
if (m_coreObject->isMathSubscriptSuperscript() || m_coreObject->isMathMultiscript())
return Atspi::Role::Section;
if (m_coreObject->isMathFraction())
return Atspi::Role::MathFraction;
if (m_coreObject->isMathSquareRoot() || m_coreObject->isMathRoot())
return Atspi::Role::MathRoot;
if (m_coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Subscript)
|| m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSubscript)
|| m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSubscript))
return Atspi::Role::Subscript;
if (m_coreObject->isMathScriptObject(AccessibilityMathScriptObjectType::Superscript)
|| m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PreSuperscript)
|| m_coreObject->isMathMultiscriptObject(AccessibilityMathMultiscriptObjectType::PostSuperscript))
return Atspi::Role::Superscript;
if (m_coreObject->isMathToken())
return Atspi::Role::Static;
break;
case AccessibilityRole::ListItem: {
if (m_coreObject->inheritsPresentationalRole())
return Atspi::Role::Section;
break;
}
default:
break;
}
return std::nullopt;
}
unsigned AccessibilityObjectAtspi::role() const
{
if (!m_coreObject)
return Atspi::Role::InvalidRole;
if (auto effective = effectiveRole())
return *effective;
return atspiRole(m_coreObject->roleValue());
}
String AccessibilityObjectAtspi::effectiveRoleName() const
{
auto effective = effectiveRole();
if (!effective)
return { };
switch (*effective) {
case Atspi::Role::Image:
return "image"_s;
case Atspi::Role::Text:
case Atspi::Role::Static:
return "text"_s;
case Atspi::Role::InvalidRole:
return "invalid"_s;
case Atspi::Role::Panel:
return "panel"_s;
case Atspi::Role::PasswordText:
return "password text"_s;
case Atspi::Role::Table:
return "table"_s;
case Atspi::Role::TableRow:
return "table row"_s;
case Atspi::Role::TableCell:
return "table cell"_s;
case Atspi::Role::Section:
return "section"_s;
case Atspi::Role::MathFraction:
return "math fraction"_s;
case Atspi::Role::MathRoot:
return "math root"_s;
case Atspi::Role::Subscript:
return "subscript"_s;
case Atspi::Role::Superscript:
return "superscript"_s;
case Atspi::Role::Landmark:
return "landmark"_s;
default:
break;
}
RELEASE_ASSERT_NOT_REACHED();
}
String AccessibilityObjectAtspi::roleName() const
{
if (!m_coreObject)
return "invalid"_s;
auto effective = effectiveRoleName();
if (!effective.isEmpty())
return effective;
return m_coreObject->rolePlatformString();
}
const char* AccessibilityObjectAtspi::effectiveLocalizedRoleName() const
{
auto effective = effectiveRole();
if (!effective)
return nullptr;
switch (*effective) {
case Atspi::Role::Image:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Image);
case Atspi::Role::Text:
case Atspi::Role::Static:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::StaticText);
case Atspi::Role::InvalidRole:
return _("invalid");
case Atspi::Role::Panel:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Group);
case Atspi::Role::PasswordText:
return _("password text");
case Atspi::Role::Table:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Table);
case Atspi::Role::TableRow:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Row);
case Atspi::Role::TableCell:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Cell);
case Atspi::Role::Section:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Div);
case Atspi::Role::MathFraction:
return _("math fraction");
case Atspi::Role::MathRoot:
return _("math root");
case Atspi::Role::Subscript:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Subscript);
case Atspi::Role::Superscript:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::Superscript);
case Atspi::Role::Landmark:
return AccessibilityAtspi::localizedRoleName(AccessibilityRole::LandmarkMain);
default:
break;
}
RELEASE_ASSERT_NOT_REACHED();
}
const char* AccessibilityObjectAtspi::localizedRoleName() const
{
if (!m_coreObject)
return _("invalid");
if (const auto* effective = effectiveLocalizedRoleName())
return effective;
return AccessibilityAtspi::localizedRoleName(m_coreObject->roleValue());
}
void AccessibilityObjectAtspi::updateBackingStore()
{
if (m_coreObject)
m_coreObject->updateBackingStore();
}
bool AccessibilityObjectAtspi::isIgnored() const
{
return m_coreObject ? m_coreObject->accessibilityIsIgnored() : true;
}
void AccessibilityObject::detachPlatformWrapper(AccessibilityDetachmentType detachmentType)
{
switch (detachmentType) {
case AccessibilityDetachmentType::CacheDestroyed:
wrapper()->cacheDestroyed();
break;
case AccessibilityDetachmentType::ElementDestroyed:
wrapper()->elementDestroyed();
break;
case AccessibilityDetachmentType::ElementChanged:
RELEASE_ASSERT_NOT_REACHED();
break;
}
}
bool AccessibilityObject::accessibilityIgnoreAttachment() const
{
return false;
}
AccessibilityObjectInclusion AccessibilityObject::accessibilityPlatformIncludesObject() const
{
auto* parent = parentObject();
if (!parent)
return AccessibilityObjectInclusion::DefaultBehavior;
// If the author has provided a role, platform-specific inclusion likely doesn't apply.
if (ariaRoleAttribute() != AccessibilityRole::Unknown)
return AccessibilityObjectInclusion::DefaultBehavior;
// Never expose an unknown object, since AT's won't know what to
// do with them. This is what is done on the Mac as well.
if (roleValue() == AccessibilityRole::Unknown)
return AccessibilityObjectInclusion::IgnoreObject;
// The object containing the text should implement org.a11y.atspi.Text itself.
if (roleValue() == AccessibilityRole::StaticText)
return AccessibilityObjectInclusion::IgnoreObject;
// Entries and password fields have extraneous children which we want to ignore.
if (parent->isPasswordField() || parent->isTextControl())
return AccessibilityObjectInclusion::IgnoreObject;
// We expose the slider as a whole but not its value indicator.
if (roleValue() == AccessibilityRole::SliderThumb)
return AccessibilityObjectInclusion::IgnoreObject;
// List items inheriting presentational are ignored, but their content exposed.
// Since we expose text in the parent, we need to expose presentational list items
// with a different role (section).
if (roleValue() == AccessibilityRole::ListItem && inheritsPresentationalRole())
return AccessibilityObjectInclusion::IncludeObject;
RenderObject* renderObject = renderer();
if (!renderObject)
return AccessibilityObjectInclusion::DefaultBehavior;
// We always want to include paragraphs that have rendered content.
// WebCore Accessibility does so unless there is a RenderBlock child.
if (roleValue() == AccessibilityRole::Paragraph) {
auto child = childrenOfType<RenderBlock>(downcast<RenderElement>(*renderObject)).first();
return child ? AccessibilityObjectInclusion::IncludeObject : AccessibilityObjectInclusion::DefaultBehavior;
}
// We always want to include table cells (layout and CSS) that have rendered text content.
if (is<RenderTableCell>(renderObject)) {
for (const auto& child : childrenOfType<RenderObject>(downcast<RenderElement>(*renderObject))) {
if (is<RenderInline>(child) || is<RenderText>(child) || is<HTMLSpanElement>(child.node()))
return AccessibilityObjectInclusion::IncludeObject;
}
return AccessibilityObjectInclusion::DefaultBehavior;
}
if (renderObject->isAnonymousBlock()) {
// The text displayed by an ARIA menu item is exposed through the accessible name.
if (parent->isMenuItem())
return AccessibilityObjectInclusion::IgnoreObject;
// The text displayed in headings is typically exposed in the heading itself.
if (parent->isHeading())
return AccessibilityObjectInclusion::IgnoreObject;
// The text displayed in list items is typically exposed in the list item itself.
if (parent->isListItem())
return AccessibilityObjectInclusion::IgnoreObject;
// The text displayed in links is typically exposed in the link itself.
if (parent->isLink())
return AccessibilityObjectInclusion::IgnoreObject;
// FIXME: This next one needs some further consideration. But paragraphs are not
// typically huge (like divs). And ignoring anonymous block children of paragraphs
// will preserve existing behavior.
if (parent->roleValue() == AccessibilityRole::Paragraph)
return AccessibilityObjectInclusion::IgnoreObject;
return AccessibilityObjectInclusion::DefaultBehavior;
}
Node* node = renderObject->node();
if (!node)
return AccessibilityObjectInclusion::DefaultBehavior;
// We don't want <span> elements to show up in the accessibility hierarchy unless
// we have good reasons for that (e.g. focusable or visible because of containing
// a meaningful accessible name, maybe set through ARIA).
if (is<HTMLSpanElement>(node) && !canSetFocusAttribute() && !hasAttributesRequiredForInclusion() && !supportsARIAAttributes())
return AccessibilityObjectInclusion::IgnoreObject;
if (is<TextControlInnerTextElement>(node))
return AccessibilityObjectInclusion::IgnoreObject;
return AccessibilityObjectInclusion::DefaultBehavior;
}
} // namespace WebCore
#endif // USE(ATSPI)