blob: 40e6b333c5d95d2fb6c44ce3323673e5058dafbf [file] [log] [blame]
/*
* Copyright (C) 2011 Apple Inc. All Rights Reserved.
* Copyright (C) 2012 Igalia S.L.
* Copyright (C) 2013 Samsung Electronics. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "AccessibilityUIElement.h"
#if HAVE(ACCESSIBILITY)
#include "InjectedBundle.h"
#include "InjectedBundlePage.h"
#include <JavaScriptCore/JSStringRef.h>
#include <JavaScriptCore/OpaqueJSString.h>
#include <WebCore/NotImplemented.h>
#include <WebKit/WKBundleFrame.h>
#include <atk/atk.h>
#include <wtf/Assertions.h>
#include <wtf/glib/GRefPtr.h>
#include <wtf/glib/GUniquePtr.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/unicode/CharacterNames.h>
namespace WTR {
namespace {
enum RangeLimit {
RangeLimitMinimum,
RangeLimitMaximum
};
enum AtkAttributeType {
ObjectAttributeType,
TextAttributeType
};
enum AttributesIndex {
// Attribute names.
InvalidNameIndex = 0,
ColumnCount,
ColumnIndex,
ColumnSpan,
RowCount,
RowIndex,
RowSpan,
PosInSetIndex,
SetSizeIndex,
PlaceholderNameIndex,
SortNameIndex,
CurrentNameIndex,
AriaLiveNameIndex,
AriaAtomicNameIndex,
AriaRelevantNameIndex,
BusyNameIndex,
// Attribute values.
SortAscendingValueIndex,
SortDescendingValueIndex,
SortUnknownValueIndex,
NumberOfAttributes
};
// Attribute names & Values (keep on sync with enum AttributesIndex).
struct Attribute {
String coreDomain;
String atkDomain;
};
using Attributes = std::array<Attribute, NumberOfAttributes>;
static const Attributes& attributesMap()
{
static NeverDestroyed<Attributes> attributes = Attributes({
// Attribute names.
Attribute { "AXInvalid", "invalid" },
Attribute { "AXARIAColumnCount", "colcount" },
Attribute { "AXARIAColumnIndex", "colindex" },
Attribute { "AXARIAColumnSpan", "colspan" },
Attribute { "AXARIARowCount", "rowcount" },
Attribute { "AXARIARowIndex", "rowindex" },
Attribute { "AXARIARowSpan", "rowspan" },
Attribute { "AXARIAPosInSet", "posinset" },
Attribute { "AXARIASetSize", "setsize" },
Attribute { "AXPlaceholderValue", "placeholder-text" } ,
Attribute { "AXSortDirection", "sort" },
Attribute { "AXARIACurrent", "current" },
Attribute { "AXARIALive", "live" },
Attribute { "AXARIAAtomic", "atomic" },
Attribute { "AXARIARelevant", "relevant" },
Attribute { "AXElementBusy", "busy" },
// Attribute values.
Attribute { "AXAscendingSortDirection", "ascending" },
Attribute { "AXDescendingSortDirection", "descending" },
Attribute { "AXUnknownSortDirection", "unknown" },
});
return attributes.get();
}
const char* landmarkStringBanner = "AXLandmarkBanner";
const char* landmarkStringComplementary = "AXLandmarkComplementary";
const char* landmarkStringContentinfo = "AXLandmarkContentInfo";
const char* landmarkStringForm = "AXLandmarkForm";
const char* landmarkStringMain = "AXLandmarkMain";
const char* landmarkStringNavigation = "AXLandmarkNavigation";
const char* landmarkStringRegion = "AXLandmarkRegion";
const char* landmarkStringSearch = "AXLandmarkSearch";
String jsStringToWTFString(JSStringRef attribute)
{
size_t bufferSize = JSStringGetMaximumUTF8CStringSize(attribute);
GUniquePtr<gchar> buffer(static_cast<gchar*>(g_malloc(bufferSize)));
JSStringGetUTF8CString(attribute, buffer.get(), bufferSize);
return String::fromUTF8(buffer.get());
}
String coreAttributeToAtkAttribute(JSStringRef attribute)
{
String attributeString = jsStringToWTFString(attribute);
for (int i = 0; i < NumberOfAttributes; ++i) {
if (attributesMap()[i].coreDomain == attributeString)
return attributesMap()[i].atkDomain;
}
return attributeString;
}
String atkAttributeValueToCoreAttributeValue(AtkAttributeType type, const String& id, const String& value)
{
if (type == ObjectAttributeType) {
// We don't expose the "current" attribute if there is no author-provided value.
if (id == attributesMap()[CurrentNameIndex].atkDomain && value.isEmpty())
return "false";
// We need to translate ATK values exposed for 'aria-sort' (e.g. 'ascending')
// into those expected by the layout tests (e.g. 'AXAscendingSortDirection').
if (id == attributesMap()[SortNameIndex].atkDomain && !value.isEmpty()) {
if (value == attributesMap()[SortAscendingValueIndex].atkDomain)
return attributesMap()[SortAscendingValueIndex].coreDomain;
if (value == attributesMap()[SortDescendingValueIndex].atkDomain)
return attributesMap()[SortDescendingValueIndex].coreDomain;
return attributesMap()[SortUnknownValueIndex].coreDomain;
}
} else if (type == TextAttributeType) {
// In case of 'aria-invalid' when the attribute empty or has "false" for ATK
// it should not be mapped at all, but layout tests will expect 'false'.
if (id == attributesMap()[InvalidNameIndex].atkDomain && value.isEmpty())
return "false";
}
return value;
}
AtkAttributeSet* getAttributeSet(AtkObject* accessible, AtkAttributeType type)
{
if (!accessible)
return nullptr;
if (type == ObjectAttributeType)
return atk_object_get_attributes(accessible);
if (type == TextAttributeType) {
if (!ATK_IS_TEXT(accessible))
return nullptr;
return atk_text_get_default_attributes(ATK_TEXT(accessible));
}
ASSERT_NOT_REACHED();
return nullptr;
}
String getAttributeSetValueForId(AtkObject* accessible, AtkAttributeType type, String id)
{
AtkAttributeSet* attributeSet = getAttributeSet(accessible, type);
if (!attributeSet)
return String();
String attributeValue;
for (AtkAttributeSet* attributes = attributeSet; attributes; attributes = attributes->next) {
AtkAttribute* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
if (id == atkAttribute->name) {
attributeValue = String::fromUTF8(atkAttribute->value);
break;
}
}
atk_attribute_set_free(attributeSet);
return atkAttributeValueToCoreAttributeValue(type, id, attributeValue);
}
String attributeSetToString(AtkAttributeSet* attributeSet, String separator=", ")
{
if (!attributeSet)
return String();
StringBuilder builder;
for (AtkAttributeSet* attributes = attributeSet; attributes; attributes = attributes->next) {
AtkAttribute* attribute = static_cast<AtkAttribute*>(attributes->data);
builder.append(attribute->name);
builder.append(':');
builder.append(attribute->value);
if (attributes->next)
builder.append(separator);
}
atk_attribute_set_free(attributeSet);
return builder.toString();
}
String getAtkAttributeSetAsString(AtkObject* accessible, AtkAttributeType type, String separator=", ")
{
return attributeSetToString(getAttributeSet(accessible, type), separator);
}
bool checkElementState(PlatformUIElement element, AtkStateType stateType)
{
if (!ATK_IS_OBJECT(element.get()))
return false;
GRefPtr<AtkStateSet> stateSet = adoptGRef(atk_object_ref_state_set(ATK_OBJECT(element.get())));
return atk_state_set_contains_state(stateSet.get(), stateType);
}
JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange)
{
GUniquePtr<gchar> rangeString(g_strdup("{0, 0}"));
if (!ATK_IS_TABLE_CELL(element.get()))
return JSStringCreateWithUTF8CString(rangeString.get());
gint row = -1;
gint column = -1;
gint rowSpan = -1;
gint columnSpan = -1;
atk_table_cell_get_row_column_span(ATK_TABLE_CELL(element.get()), &row, &column, &rowSpan, &columnSpan);
// Get the actual values, if row and columns are valid values.
if (row != -1 && column != -1) {
int base = 0;
int length = 0;
if (isRowRange) {
base = row;
length = rowSpan;
} else {
base = column;
length = columnSpan;
}
rangeString.reset(g_strdup_printf("{%d, %d}", base, length));
}
return JSStringCreateWithUTF8CString(rangeString.get());
}
void alterCurrentValue(PlatformUIElement element, int factor)
{
if (!ATK_IS_VALUE(element.get()))
return;
double currentValue;
atk_value_get_value_and_text(ATK_VALUE(element.get()), &currentValue, nullptr);
double increment = atk_value_get_increment(ATK_VALUE(element.get()));
atk_value_set_value(ATK_VALUE(element.get()), currentValue + factor * increment);
}
gchar* replaceCharactersForResults(gchar* str)
{
WTF::String uString = WTF::String::fromUTF8(str);
// The object replacement character is passed along to ATs so we need to be
// able to test for their presence and do so without causing test failures.
uString.replace(objectReplacementCharacter, "<obj>");
// The presence of newline characters in accessible text of a single object
// is appropriate, but it makes test results (especially the accessible tree)
// harder to read.
uString.replace("\n", "<\\n>");
return g_strdup(uString.utf8().data());
}
const gchar* roleToString(AtkObject* object)
{
AtkRole role = atk_object_get_role(object);
if (role == ATK_ROLE_LANDMARK) {
String xmlRolesValue = getAttributeSetValueForId(object, ObjectAttributeType, "xml-roles");
if (equalLettersIgnoringASCIICase(xmlRolesValue, "banner"))
return landmarkStringBanner;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "complementary"))
return landmarkStringComplementary;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "contentinfo"))
return landmarkStringContentinfo;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-acknowledgments"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-afterword"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-appendix"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-bibliography"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-chapter"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-conclusion"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-credits"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-endnotes"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-epilogue"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-errata"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-foreword"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-glossary"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-glossref"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-index"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-introduction"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-pagelist"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-part"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-preface"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-prologue"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-toc"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "form"))
return landmarkStringForm;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "main"))
return landmarkStringMain;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "navigation"))
return landmarkStringNavigation;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "region"))
return landmarkStringRegion;
if (equalLettersIgnoringASCIICase(xmlRolesValue, "search"))
return landmarkStringSearch;
}
switch (role) {
case ATK_ROLE_ALERT:
return "AXAlert";
case ATK_ROLE_DIALOG:
return "AXDialog";
case ATK_ROLE_CANVAS:
return "AXCanvas";
case ATK_ROLE_CAPTION:
return "AXCaption";
case ATK_ROLE_CHECK_BOX:
return "AXCheckBox";
case ATK_ROLE_COLOR_CHOOSER:
return "AXColorWell";
case ATK_ROLE_COLUMN_HEADER:
return "AXColumnHeader";
case ATK_ROLE_COMBO_BOX:
return "AXComboBox";
case ATK_ROLE_COMMENT:
return "AXComment";
case ATK_ROLE_DOCUMENT_FRAME:
return "AXDocument";
case ATK_ROLE_DOCUMENT_WEB:
return "AXWebArea";
case ATK_ROLE_EMBEDDED:
return "AXEmbedded";
case ATK_ROLE_ENTRY:
return "AXTextField";
case ATK_ROLE_FOOTER:
return "AXFooter";
case ATK_ROLE_FORM:
return "AXForm";
case ATK_ROLE_GROUPING:
return "AXGroup";
case ATK_ROLE_HEADING:
return "AXHeading";
case ATK_ROLE_IMAGE:
return "AXImage";
case ATK_ROLE_IMAGE_MAP:
return "AXImageMap";
case ATK_ROLE_INVALID:
return "AXInvalid";
case ATK_ROLE_LABEL:
return "AXLabel";
case ATK_ROLE_LEVEL_BAR:
return "AXLevelIndicator";
case ATK_ROLE_LINK:
return "AXLink";
case ATK_ROLE_LIST:
return "AXList";
case ATK_ROLE_LIST_BOX:
return "AXListBox";
case ATK_ROLE_LIST_ITEM:
return "AXListItem";
case ATK_ROLE_MENU:
return "AXMenu";
case ATK_ROLE_MENU_BAR:
return "AXMenuBar";
case ATK_ROLE_MENU_ITEM:
return "AXMenuItem";
case ATK_ROLE_PAGE_TAB:
return "AXTab";
case ATK_ROLE_PAGE_TAB_LIST:
return "AXTabGroup";
case ATK_ROLE_PANEL:
return "AXGroup";
case ATK_ROLE_PARAGRAPH:
return "AXParagraph";
case ATK_ROLE_PASSWORD_TEXT:
return "AXPasswordField";
case ATK_ROLE_PROGRESS_BAR:
return "AXProgressIndicator";
case ATK_ROLE_PUSH_BUTTON:
return "AXButton";
case ATK_ROLE_RADIO_BUTTON:
return "AXRadioButton";
case ATK_ROLE_RADIO_MENU_ITEM:
return "AXRadioMenuItem";
case ATK_ROLE_ROW_HEADER:
return "AXRowHeader";
case ATK_ROLE_CHECK_MENU_ITEM:
return "AXCheckMenuItem";
case ATK_ROLE_RULER:
return "AXRuler";
case ATK_ROLE_SCROLL_BAR:
return "AXScrollBar";
case ATK_ROLE_SCROLL_PANE:
return "AXScrollArea";
case ATK_ROLE_SECTION:
return "AXSection";
case ATK_ROLE_SEPARATOR:
return "AXSeparator";
case ATK_ROLE_SLIDER:
return "AXSlider";
case ATK_ROLE_SPIN_BUTTON:
return "AXSpinButton";
case ATK_ROLE_STATUSBAR:
return "AXStatusBar";
case ATK_ROLE_TABLE:
return "AXTable";
case ATK_ROLE_TABLE_CELL:
return "AXCell";
case ATK_ROLE_TABLE_COLUMN_HEADER:
return "AXColumnHeader";
case ATK_ROLE_TABLE_ROW:
return "AXRow";
case ATK_ROLE_TABLE_ROW_HEADER:
return "AXRowHeader";
case ATK_ROLE_TOGGLE_BUTTON:
return "AXToggleButton";
case ATK_ROLE_TOOL_BAR:
return "AXToolbar";
case ATK_ROLE_TOOL_TIP:
return "AXUserInterfaceTooltip";
case ATK_ROLE_TREE:
return "AXTree";
case ATK_ROLE_TREE_TABLE:
return "AXTreeGrid";
case ATK_ROLE_TREE_ITEM:
return "AXTreeItem";
case ATK_ROLE_WINDOW:
return "AXWindow";
case ATK_ROLE_UNKNOWN:
return "AXUnknown";
case ATK_ROLE_ARTICLE:
return "AXArticle";
case ATK_ROLE_AUDIO:
return "AXAudio";
case ATK_ROLE_BLOCK_QUOTE:
return "AXBlockquote";
case ATK_ROLE_DEFINITION:
return "AXDefinition";
case ATK_ROLE_LOG:
return "AXLog";
case ATK_ROLE_MARQUEE:
return "AXMarquee";
case ATK_ROLE_MATH:
return "AXMath";
case ATK_ROLE_TIMER:
return "AXTimer";
case ATK_ROLE_VIDEO:
return "AXVideo";
case ATK_ROLE_DESCRIPTION_LIST:
return "AXDescriptionList";
case ATK_ROLE_DESCRIPTION_TERM:
return "AXDescriptionTerm";
case ATK_ROLE_DESCRIPTION_VALUE:
return "AXDescriptionValue";
case ATK_ROLE_STATIC:
return "AXStatic";
case ATK_ROLE_MATH_FRACTION:
return "AXMathFraction";
case ATK_ROLE_MATH_ROOT:
return "AXMathRoot";
case ATK_ROLE_SUBSCRIPT:
return "AXSubscript";
case ATK_ROLE_SUPERSCRIPT:
return "AXSuperscript";
#if ATK_CHECK_VERSION(2, 25, 2)
case ATK_ROLE_FOOTNOTE:
return "AXFootnote";
#endif
#if ATK_CHECK_VERSION(2, 33, 3)
case ATK_ROLE_CONTENT_DELETION:
return "AXDeletion";
case ATK_ROLE_CONTENT_INSERTION:
return "AXInsertion";
#endif
default:
// We want to distinguish ATK_ROLE_UNKNOWN from a known AtkRole which
// our DRT isn't properly handling.
return "FIXME not identified";
}
}
String selectedText(AtkObject* accessible)
{
if (!ATK_IS_TEXT(accessible))
return String();
AtkText* text = ATK_TEXT(accessible);
gint start, end;
g_free(atk_text_get_selection(text, 0, &start, &end));
return atk_text_get_text(text, start, end);
}
String attributesOfElement(AccessibilityUIElement* element)
{
StringBuilder builder;
builder.append(element->role()->string());
builder.append('\n');
// For the parent we print its role and its name, if available.
builder.appendLiteral("AXParent: ");
RefPtr<AccessibilityUIElement> parent = element->parentElement();
AtkObject* atkParent = parent ? parent->platformUIElement().get() : nullptr;
if (atkParent) {
builder.append(roleToString(atkParent));
const char* parentName = atk_object_get_name(atkParent);
if (parentName && parentName[0]) {
builder.appendLiteral(": ");
builder.append(parentName);
}
} else
builder.appendLiteral("(null)");
builder.append('\n');
builder.appendLiteral("AXChildren: ");
builder.appendNumber(element->childrenCount());
builder.append('\n');
builder.appendLiteral("AXPosition: { ");
builder.appendFixedPrecisionNumber(element->x(), 6, KeepTrailingZeros);
builder.appendLiteral(", ");
builder.appendFixedPrecisionNumber(element->y(), 6, KeepTrailingZeros);
builder.appendLiteral(" }\n");
builder.appendLiteral("AXSize: { ");
builder.appendFixedPrecisionNumber(element->width(), 6, KeepTrailingZeros);
builder.appendLiteral(", ");
builder.appendFixedPrecisionNumber(element->height(), 6, KeepTrailingZeros);
builder.appendLiteral(" }\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());
builder.append('\n');
}
String value = element->stringValue()->string();
if (!value.isEmpty()) {
builder.append(value);
builder.append('\n');
}
builder.appendLiteral("AXFocusable: ");
builder.appendNumber(element->isFocusable());
builder.append('\n');
builder.appendLiteral("AXFocused: ");
builder.appendNumber(element->isFocused());
builder.append('\n');
builder.appendLiteral("AXSelectable: ");
builder.appendNumber(element->isSelectable());
builder.append('\n');
builder.appendLiteral("AXSelected: ");
builder.appendNumber(element->isSelected());
builder.append('\n');
builder.appendLiteral("AXMultiSelectable: ");
builder.appendNumber(element->isMultiSelectable());
builder.append('\n');
builder.appendLiteral("AXEnabled: ");
builder.appendNumber(element->isEnabled());
builder.append('\n');
builder.appendLiteral("AXExpanded: ");
builder.appendNumber(element->isExpanded());
builder.append('\n');
builder.appendLiteral("AXRequired: ");
builder.appendNumber(element->isRequired());
builder.append('\n');
builder.appendLiteral("AXChecked: ");
builder.appendNumber(element->isChecked());
builder.append('\n');
String url = element->url()->string();
if (!url.isEmpty()) {
builder.append(url);
builder.append('\n');
}
// We append the ATK specific attributes as a single line at the end.
builder.appendLiteral("AXPlatformAttributes: ");
builder.append(getAtkAttributeSetAsString(element->platformUIElement().get(), ObjectAttributeType));
return builder.toString();
}
static JSRetainPtr<JSStringRef> createStringWithAttributes(const Vector<RefPtr<AccessibilityUIElement> >& elements)
{
StringBuilder builder;
for (Vector<RefPtr<AccessibilityUIElement> >::const_iterator it = elements.begin(); it != elements.end(); ++it) {
builder.append(attributesOfElement(const_cast<AccessibilityUIElement*>(it->get())));
builder.appendLiteral("\n------------\n");
}
return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
}
static Vector<RefPtr<AccessibilityUIElement> > getTableRowHeaders(AtkTable* accessible)
{
Vector<RefPtr<AccessibilityUIElement> > rowHeaders;
int rowsCount = atk_table_get_n_rows(accessible);
for (int row = 0; row < rowsCount; ++row) {
if (AtkObject* header = atk_table_get_row_header(accessible, row))
rowHeaders.append(AccessibilityUIElement::create(header));
}
return rowHeaders;
}
static Vector<RefPtr<AccessibilityUIElement> > getTableColumnHeaders(AtkTable* accessible)
{
Vector<RefPtr<AccessibilityUIElement> > columnHeaders;
int columnsCount = atk_table_get_n_columns(accessible);
for (int column = 0; column < columnsCount; ++column) {
if (AtkObject* header = atk_table_get_column_header(accessible, column))
columnHeaders.append(AccessibilityUIElement::create(header));
}
return columnHeaders;
}
static Vector<RefPtr<AccessibilityUIElement> > getVisibleCells(AccessibilityUIElement* element)
{
Vector<RefPtr<AccessibilityUIElement> > visibleCells;
AtkTable* accessible = ATK_TABLE(element->platformUIElement().get());
int rowsCount = atk_table_get_n_rows(accessible);
int columnsCount = atk_table_get_n_columns(accessible);
for (int row = 0; row < rowsCount; ++row) {
for (int column = 0; column < columnsCount; ++column)
visibleCells.append(element->cellForColumnAndRow(column, row));
}
return visibleCells;
}
static Vector<RefPtr<AccessibilityUIElement>> convertGPtrArrayToVector(const GPtrArray* array)
{
Vector<RefPtr<AccessibilityUIElement>> cells;
for (guint i = 0; i < array->len; i++) {
if (AtkObject* atkObject = static_cast<AtkObject*>(g_ptr_array_index(array, i)))
cells.append(AccessibilityUIElement::create(atkObject));
}
return cells;
}
static JSValueRef convertToJSObjectArray(const Vector<RefPtr<AccessibilityUIElement>>& children)
{
WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::singleton().page()->page());
JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
size_t elementCount = children.size();
auto valueElements = std::make_unique<JSValueRef[]>(elementCount);
for (size_t i = 0; i < elementCount; i++)
valueElements[i] = JSObjectMake(context, children[i]->wrapperClass(), children[i].get());
return JSObjectMakeArray(context, elementCount, valueElements.get(), nullptr);
}
static double rangeMinMaxValue(AtkValue* atkValue, RangeLimit rangeLimit)
{
AtkRange* range = atk_value_get_range(atkValue);
if (!range)
return 0;
double rangeValue = 0;
switch (rangeLimit) {
case RangeLimitMinimum:
rangeValue = atk_range_get_lower_limit(range);
break;
case RangeLimitMaximum:
rangeValue = atk_range_get_upper_limit(range);
break;
};
atk_range_free(range);
return rangeValue;
}
} // namespace
AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element)
: m_element(element)
{
}
AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other)
: JSWrappable()
, m_element(other.m_element)
{
}
AccessibilityUIElement::~AccessibilityUIElement()
{
}
bool AccessibilityUIElement::isEqual(AccessibilityUIElement* otherElement)
{
return m_element == otherElement->platformUIElement();
}
void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> >& children)
{
if (!ATK_IS_OBJECT(m_element.get()))
return;
int count = childrenCount();
for (int i = 0; i < count; i++) {
GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(m_element.get()), i));
children.append(AccessibilityUIElement::create(child.get()));
}
}
void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIElement> >& children, unsigned location, unsigned length)
{
if (!ATK_IS_OBJECT(m_element.get()))
return;
unsigned end = location + length;
for (unsigned i = location; i < end; i++) {
GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(m_element.get()), i));
children.append(AccessibilityUIElement::create(child.get()));
}
}
int AccessibilityUIElement::childrenCount()
{
if (!ATK_IS_OBJECT(m_element.get()))
return 0;
return atk_object_get_n_accessible_children(ATK_OBJECT(m_element.get()));
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPoint(int x, int y)
{
if (!ATK_IS_COMPONENT(m_element.get()))
return nullptr;
GRefPtr<AtkObject> objectAtPoint = adoptGRef(atk_component_ref_accessible_at_point(ATK_COMPONENT(m_element.get()), x, y, ATK_XY_WINDOW));
return AccessibilityUIElement::create(objectAtPoint ? objectAtPoint.get() : m_element.get());
}
unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element)
{
if (!ATK_IS_OBJECT(m_element.get()))
return 0;
Vector<RefPtr<AccessibilityUIElement> > children;
getChildren(children);
unsigned count = children.size();
for (unsigned i = 0; i < count; i++)
if (children[i]->isEqual(element))
return i;
return 0;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::childAtIndex(unsigned index)
{
if (!ATK_IS_OBJECT(m_element.get()))
return nullptr;
Vector<RefPtr<AccessibilityUIElement> > children;
getChildrenWithRange(children, index, 1);
if (children.size() == 1)
return children[0];
return nullptr;
}
static RefPtr<AccessibilityUIElement> accessibilityElementAtIndex(AtkObject* element, AtkRelationType relationType, unsigned index)
{
if (!ATK_IS_OBJECT(element))
return nullptr;
AtkRelationSet* relationSet = atk_object_ref_relation_set(element);
if (!relationSet)
return nullptr;
AtkRelation* relation = atk_relation_set_get_relation_by_type(relationSet, relationType);
if (!relation)
return nullptr;
GPtrArray* targetList = atk_relation_get_target(relation);
if (!targetList || !targetList->len || index >= targetList->len)
return nullptr;
AtkObject* target = static_cast<AtkObject*>(g_ptr_array_index(targetList, index));
g_object_unref(relationSet);
return target ? AccessibilityUIElement::create(target).ptr() : nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::linkedUIElementAtIndex(unsigned index)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_NODE_PARENT_OF, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsReferencingElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_NODE_CHILD_OF, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_FLOWS_TO, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToReferencingElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_FLOWS_FROM, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_CONTROLLER_FOR, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsReferencingElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_CONTROLLED_BY, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_LABELLED_BY, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByReferencingElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_LABEL_FOR, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DESCRIBED_BY, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByReferencingElementAtIndex(unsigned index)
{
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DESCRIPTION_FOR, index);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsElementAtIndex(unsigned index)
{
#if ATK_CHECK_VERSION(2, 25, 2)
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DETAILS, index);
#endif
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsReferencingElementAtIndex(unsigned index)
{
#if ATK_CHECK_VERSION(2, 25, 2)
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DETAILS_FOR, index);
#endif
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageElementAtIndex(unsigned index)
{
#if ATK_CHECK_VERSION(2, 25, 2)
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_ERROR_MESSAGE, index);
#endif
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageReferencingElementAtIndex(unsigned index)
{
#if ATK_CHECK_VERSION(2, 25, 2)
return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_ERROR_FOR, index);
#endif
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedRowAtIndex(unsigned index)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index)
{
// ATK doesn't have API to get an accessible row by index directly. It does, however, have
// API to get cells in the row specified by index. The parent of a cell should be the row.
AtkTable* axTable = ATK_TABLE(m_element.get());
unsigned nColumns = columnCount();
for (unsigned col = 0; col < nColumns; col++) {
// Find the first cell in this row that only spans one row.
if (atk_table_get_row_extent_at(axTable, index, col) == 1) {
AtkObject* cell = atk_table_ref_at(axTable, index, col);
return cell ? AccessibilityUIElement::create(atk_object_get_parent(cell)).ptr() : nullptr;
}
}
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const
{
if (!ATK_SELECTION(m_element.get()))
return nullptr;
GRefPtr<AtkObject> child = adoptGRef(atk_selection_ref_selection(ATK_SELECTION(m_element.get()), index));
return child ? AccessibilityUIElement::create(child.get()).ptr() : nullptr;
}
unsigned AccessibilityUIElement::selectedChildrenCount() const
{
if (!ATK_IS_SELECTION(m_element.get()))
return 0;
return atk_selection_get_selection_count(ATK_SELECTION(m_element.get()));
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedRowAtIndex(unsigned index)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::titleUIElement()
{
if (!ATK_IS_OBJECT(m_element.get()))
return nullptr;
AtkRelationSet* set = atk_object_ref_relation_set(ATK_OBJECT(m_element.get()));
if (!set)
return nullptr;
AtkObject* target = nullptr;
int count = atk_relation_set_get_n_relations(set);
for (int i = 0; i < count; i++) {
AtkRelation* relation = atk_relation_set_get_relation(set, i);
if (atk_relation_get_relation_type(relation) == ATK_RELATION_LABELLED_BY) {
GPtrArray* targetList = atk_relation_get_target(relation);
if (targetList->len)
target = static_cast<AtkObject*>(g_ptr_array_index(targetList, 0));
}
}
g_object_unref(set);
return target ? AccessibilityUIElement::create(target).ptr() : nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::parentElement()
{
if (!ATK_IS_OBJECT(m_element.get()))
return nullptr;
AtkObject* parent = atk_object_get_parent(ATK_OBJECT(m_element.get()));
return parent ? AccessibilityUIElement::create(parent).ptr() : nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedByRow()
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfLinkedUIElements()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfDocumentLinks()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren()
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
Vector<RefPtr<AccessibilityUIElement> > children;
getChildren(children);
return createStringWithAttributes(children);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::allAttributes()
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
return JSStringCreateWithUTF8CString(attributesOfElement(this).utf8().data());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringDescriptionOfAttributeValue(JSStringRef attribute)
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValue(JSStringRef attribute)
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
String atkAttributeName = coreAttributeToAtkAttribute(attribute);
// The value of AXSelectedText is not exposed through any AtkAttribute.
if (atkAttributeName == "AXSelectedText") {
String string = selectedText(m_element.get());
return JSStringCreateWithUTF8CString(string.utf8().data());
}
// Try object attributes before text attributes.
String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
// Try text attributes if the requested one was not found and we have an AtkText object.
if (attributeValue.isEmpty() && ATK_IS_TEXT(m_element.get()))
attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), TextAttributeType, atkAttributeName);
// Additional check to make sure that the exposure of the state ATK_STATE_INVALID_ENTRY
// is consistent with the exposure of aria-invalid as a text attribute, if present.
if (atkAttributeName == attributesMap()[InvalidNameIndex].atkDomain) {
bool isInvalidState = checkElementState(m_element.get(), ATK_STATE_INVALID_ENTRY);
if (attributeValue.isEmpty())
return JSStringCreateWithUTF8CString(isInvalidState ? "true" : "false");
// If the text attribute was there, check that it's consistent with
// what the state says or force the test to fail otherwise.
bool isAriaInvalid = attributeValue != "false";
if (isInvalidState != isAriaInvalid)
return JSStringCreateWithCharacters(0, 0);
}
return JSStringCreateWithUTF8CString(attributeValue.utf8().data());
}
double AccessibilityUIElement::numberAttributeValue(JSStringRef attribute)
{
if (!ATK_IS_OBJECT(m_element.get()))
return 0;
String atkAttributeName = coreAttributeToAtkAttribute(attribute);
if (atkAttributeName.isEmpty())
return 0;
if (atkAttributeName == "setsize" || atkAttributeName == "posinset") {
String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
if (!attributeValue.isEmpty())
return attributeValue.toDouble();
}
if (atkAttributeName.startsWith("row") || atkAttributeName.startsWith("col")) {
String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
if (!attributeValue.isEmpty())
return attributeValue.toInt();
}
return 0;
}
JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attribute) const
{
// FIXME: implement
return nullptr;
}
JSValueRef AccessibilityUIElement::rowHeaders() const
{
if (ATK_IS_TABLE(m_element.get()))
return convertToJSObjectArray(getTableRowHeaders(ATK_TABLE(m_element.get())));
Vector<RefPtr<AccessibilityUIElement>> headers;
if (!ATK_IS_TABLE_CELL(m_element.get()))
return convertToJSObjectArray(headers);
if (GRefPtr<GPtrArray> array = adoptGRef(atk_table_cell_get_row_header_cells(ATK_TABLE_CELL(m_element.get()))))
headers = convertGPtrArrayToVector(array.get());
return convertToJSObjectArray(headers);
}
JSValueRef AccessibilityUIElement::columnHeaders() const
{
if (ATK_IS_TABLE(m_element.get()))
return convertToJSObjectArray(getTableColumnHeaders(ATK_TABLE(m_element.get())));
Vector<RefPtr<AccessibilityUIElement>> headers;
if (!ATK_IS_TABLE_CELL(m_element.get()))
return convertToJSObjectArray(headers);
if (GRefPtr<GPtrArray> array = adoptGRef(atk_table_cell_get_column_header_cells(ATK_TABLE_CELL(m_element.get()))))
headers = convertGPtrArrayToVector(array.get());
return convertToJSObjectArray(headers);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
{
if (!ATK_IS_OBJECT(m_element.get()))
return nullptr;
// ATK does not have this API. So we're "faking it" here on a case-by-case basis.
String attributeString = jsStringToWTFString(attribute);
AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
if (role == ATK_ROLE_SPIN_BUTTON && const_cast<AccessibilityUIElement*>(this)->childrenCount() == 2) {
if (attributeString == "AXDecrementButton")
return const_cast<AccessibilityUIElement*>(this)->childAtIndex(0);
if (attributeString == "AXIncrementButton")
return const_cast<AccessibilityUIElement*>(this)->childAtIndex(1);
}
return nullptr;
}
bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute)
{
if (!ATK_IS_OBJECT(m_element.get()))
return false;
String attributeString = jsStringToWTFString(attribute);
if (attributeString == "AXElementBusy")
return checkElementState(m_element.get(), ATK_STATE_BUSY);
if (attributeString == "AXChecked")
return checkElementState(m_element.get(), ATK_STATE_CHECKED);
if (attributeString == "AXEnabled")
return checkElementState(m_element.get(), ATK_STATE_ENABLED);
if (attributeString == "AXExpanded")
return checkElementState(m_element.get(), ATK_STATE_EXPANDED);
if (attributeString == "AXFocused")
return checkElementState(m_element.get(), ATK_STATE_FOCUSED);
if (attributeString == "AXInvalid")
return checkElementState(m_element.get(), ATK_STATE_INVALID);
if (attributeString == "AXModal")
return checkElementState(m_element.get(), ATK_STATE_MODAL);
if (attributeString == "AXMultiSelectable")
return checkElementState(m_element.get(), ATK_STATE_MULTISELECTABLE);
if (attributeString == "AXRequired")
return checkElementState(m_element.get(), ATK_STATE_REQUIRED);
if (attributeString == "AXSelected")
return checkElementState(m_element.get(), ATK_STATE_SELECTED);
if (attributeString == "AXSupportsAutoCompletion")
return checkElementState(m_element.get(), ATK_STATE_SUPPORTS_AUTOCOMPLETION);
if (attributeString == "AXVisited")
return checkElementState(m_element.get(), ATK_STATE_VISITED);
if (attributeString == "AXInterfaceTable")
return ATK_IS_TABLE(m_element.get());
if (attributeString == "AXInterfaceTableCell")
return ATK_IS_TABLE_CELL(m_element.get());
if (attributeString == "AXARIAAtomic") {
String atkAttribute = coreAttributeToAtkAttribute(attribute);
return getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttribute) == "true";
}
return false;
}
bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute)
{
if (!ATK_IS_OBJECT(m_element.get()))
return false;
String attributeString = jsStringToWTFString(attribute);
if (attributeString != "AXValue")
return false;
// ATK does not have a single state or property to indicate whether or not the value
// of an accessible object can be set. ATs look at several states and properties based
// on the type of object. If nothing explicitly indicates the value can or cannot be
// set, ATs make role- and interface-based decisions. We'll do something similar here.
// This state is expected to be present only for text widgets and contenteditable elements.
if (checkElementState(m_element.get(), ATK_STATE_EDITABLE))
return true;
// This state is applicable to checkboxes, radiobuttons, switches, etc.
if (checkElementState(m_element.get(), ATK_STATE_CHECKABLE))
return true;
// This state is expected to be present only for controls and only if explicitly set.
if (checkElementState(m_element.get(), ATK_STATE_READ_ONLY))
return false;
// We expose an object attribute to ATs when there is an author-provided ARIA property
// and also when there is a supported ARIA role but no author-provided value.
String isReadOnly = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "readonly");
if (!isReadOnly.isEmpty())
return isReadOnly == "true" ? false : true;
// If we have a native listbox or combobox and the value can be set, the options should
// have ATK_STATE_SELECTABLE.
AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
if (role == ATK_ROLE_LIST_BOX || role == ATK_ROLE_COMBO_BOX) {
if (GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(m_element.get()), 0))) {
if (atk_object_get_role(ATK_OBJECT(child.get())) == ATK_ROLE_MENU)
child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(child.get()), 0));
return child && checkElementState(child.get(), ATK_STATE_SELECTABLE);
}
}
// If we have a native element which exposes a range whose value can be set, it should
// be focusable and have a true range.
if (ATK_IS_VALUE(m_element.get()) && checkElementState(m_element.get(), ATK_STATE_FOCUSABLE))
return minValue() != maxValue();
return false;
}
bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute)
{
if (!ATK_IS_OBJECT(m_element.get()))
return false;
String atkAttributeName = coreAttributeToAtkAttribute(attribute);
if (atkAttributeName.isEmpty())
return false;
// In ATK, "busy" is a state and is supported on all AtkObject instances.
if (atkAttributeName == "busy")
return true;
// For now, an attribute is supported whether it's exposed as a object or a text attribute.
String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
if (attributeValue.isEmpty())
attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), TextAttributeType, atkAttributeName);
// When the aria-live value is "off", we expose that value via the "live" object attribute.
if (atkAttributeName == "live" && attributeValue == "off")
return false;
return !attributeValue.isEmpty();
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::role()
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
GUniquePtr<char> roleStringWithPrefix(g_strdup_printf("AXRole: %s", roleToString(ATK_OBJECT(m_element.get()))));
return JSStringCreateWithUTF8CString(roleStringWithPrefix.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::subrole()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::roleDescription()
{
String roleDescription = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "roledescription");
GUniquePtr<gchar> axRoleDescription(g_strdup_printf("AXRoleDescription: %s", roleDescription.utf8().data()));
return JSStringCreateWithUTF8CString(axRoleDescription.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::computedRoleString()
{
String role = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "computed-role");
if (!role.isEmpty())
return JSStringCreateWithUTF8CString(role.utf8().data());
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::title()
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
const gchar* name = atk_object_get_name(ATK_OBJECT(m_element.get()));
GUniquePtr<gchar> axTitle(g_strdup_printf("AXTitle: %s", name ? name : ""));
return JSStringCreateWithUTF8CString(axTitle.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::description()
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
const gchar* description = atk_object_get_description(ATK_OBJECT(m_element.get()));
if (!description)
return JSStringCreateWithCharacters(0, 0);
GUniquePtr<gchar> axDesc(g_strdup_printf("AXDescription: %s", description));
return JSStringCreateWithUTF8CString(axDesc.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
const gchar* axOrientation = nullptr;
if (checkElementState(m_element.get(), ATK_STATE_HORIZONTAL))
axOrientation = "AXOrientation: AXHorizontalOrientation";
else if (checkElementState(m_element.get(), ATK_STATE_VERTICAL))
axOrientation = "AXOrientation: AXVerticalOrientation";
else
axOrientation = "AXOrientation: AXUnknownOrientation";
return JSStringCreateWithUTF8CString(axOrientation);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringValue()
{
if (!ATK_IS_TEXT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
GUniquePtr<gchar> text(atk_text_get_text(ATK_TEXT(m_element.get()), 0, -1));
GUniquePtr<gchar> textWithReplacedCharacters(replaceCharactersForResults(text.get()));
GUniquePtr<gchar> axValue(g_strdup_printf("AXValue: %s", textWithReplacedCharacters.get()));
return JSStringCreateWithUTF8CString(axValue.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::language()
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
const gchar* locale = atk_object_get_object_locale(ATK_OBJECT(m_element.get()));
if (!locale)
return JSStringCreateWithCharacters(0, 0);
GUniquePtr<char> axValue(g_strdup_printf("AXLanguage: %s", locale));
return JSStringCreateWithUTF8CString(axValue.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::helpText() const
{
if (!ATK_IS_OBJECT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
AtkRelationSet* relationSet = atk_object_ref_relation_set(ATK_OBJECT(m_element.get()));
if (!relationSet)
return JSStringCreateWithCharacters(0, 0);
AtkRelation* relation = atk_relation_set_get_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY);
if (!relation)
return JSStringCreateWithCharacters(0, 0);
GPtrArray* targetList = atk_relation_get_target(relation);
if (!targetList || !targetList->len)
return JSStringCreateWithCharacters(0, 0);
StringBuilder builder;
builder.appendLiteral("AXHelp: ");
for (guint targetCount = 0; targetCount < targetList->len; targetCount++) {
if (AtkObject* target = static_cast<AtkObject*>(g_ptr_array_index(targetList, targetCount))) {
GUniquePtr<gchar> text(atk_text_get_text(ATK_TEXT(target), 0, -1));
if (targetCount)
builder.append(' ');
builder.append(text.get());
}
}
g_object_unref(relationSet);
return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
}
double AccessibilityUIElement::x()
{
if (!ATK_IS_COMPONENT(m_element.get()))
return 0;
int x;
atk_component_get_extents(ATK_COMPONENT(m_element.get()), &x, nullptr, nullptr, nullptr, ATK_XY_SCREEN);
return x;
}
double AccessibilityUIElement::y()
{
if (!ATK_IS_COMPONENT(m_element.get()))
return 0;
int y;
atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, &y, nullptr, nullptr, ATK_XY_SCREEN);
return y;
}
double AccessibilityUIElement::width()
{
if (!ATK_IS_COMPONENT(m_element.get()))
return 0;
int width;
atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, nullptr, &width, nullptr, ATK_XY_WINDOW);
return width;
}
double AccessibilityUIElement::height()
{
if (!ATK_IS_COMPONENT(m_element.get()))
return 0;
int height;
atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, nullptr, nullptr, &height, ATK_XY_WINDOW);
return height;
}
double AccessibilityUIElement::clickPointX()
{
if (!ATK_IS_COMPONENT(m_element.get()))
return 0;
int x, width;
atk_component_get_extents(ATK_COMPONENT(m_element.get()), &x, nullptr, &width, nullptr, ATK_XY_WINDOW);
return x + width / 2.0;
}
double AccessibilityUIElement::clickPointY()
{
if (!ATK_IS_COMPONENT(m_element.get()))
return 0;
int y, height;
atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, &y, nullptr, &height, ATK_XY_WINDOW);
return y + height / 2.0;
}
double AccessibilityUIElement::intValue() const
{
if (!ATK_IS_OBJECT(m_element.get()))
return 0;
if (ATK_IS_VALUE(m_element.get())) {
double value;
atk_value_get_value_and_text(ATK_VALUE(m_element.get()), &value, nullptr);
return value;
}
// Consider headings as an special case when returning the "int value" of
// an AccessibilityUIElement, so we can reuse some tests to check the level
// both for HTML headings and objects with the aria-level attribute.
if (atk_object_get_role(ATK_OBJECT(m_element.get())) == ATK_ROLE_HEADING) {
String headingLevel = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "level");
bool ok;
double headingLevelValue = headingLevel.toDouble(&ok);
if (ok)
return headingLevelValue;
}
return 0;
}
double AccessibilityUIElement::minValue()
{
if (!ATK_IS_VALUE(m_element.get()))
return 0;
return rangeMinMaxValue(ATK_VALUE(m_element.get()), RangeLimitMinimum);
}
double AccessibilityUIElement::maxValue()
{
if (!ATK_IS_VALUE(m_element.get()))
return 0;
return rangeMinMaxValue(ATK_VALUE(m_element.get()), RangeLimitMaximum);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::valueDescription()
{
String valueText = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "valuetext");
GUniquePtr<gchar> valueDescription(g_strdup_printf("AXValueDescription: %s", valueText.utf8().data()));
return JSStringCreateWithUTF8CString(valueDescription.get());
}
int AccessibilityUIElement::insertionPointLineNumber()
{
// FIXME: implement
return -1;
}
bool AccessibilityUIElement::isPressActionSupported()
{
if (!ATK_IS_ACTION(m_element.get()))
return false;
const gchar* actionName = atk_action_get_name(ATK_ACTION(m_element.get()), 0);
return equalLettersIgnoringASCIICase(String(actionName), "press") || equalLettersIgnoringASCIICase(String(actionName), "jump");
}
bool AccessibilityUIElement::isIncrementActionSupported()
{
// FIXME: implement
return false;
}
bool AccessibilityUIElement::isDecrementActionSupported()
{
// FIXME: implement
return false;
}
bool AccessibilityUIElement::isEnabled()
{
return checkElementState(m_element.get(), ATK_STATE_ENABLED);
}
bool AccessibilityUIElement::isRequired() const
{
return checkElementState(m_element.get(), ATK_STATE_REQUIRED);
}
bool AccessibilityUIElement::isFocused() const
{
return checkElementState(m_element.get(), ATK_STATE_FOCUSED);
}
bool AccessibilityUIElement::isSelected() const
{
return checkElementState(m_element.get(), ATK_STATE_SELECTED);
}
bool AccessibilityUIElement::isSelectedOptionActive() const
{
return checkElementState(m_element.get(), ATK_STATE_ACTIVE);
}
bool AccessibilityUIElement::isExpanded() const
{
return checkElementState(m_element.get(), ATK_STATE_EXPANDED);
}
bool AccessibilityUIElement::isChecked() const
{
return checkElementState(m_element.get(), ATK_STATE_CHECKED);
}
bool AccessibilityUIElement::isIndeterminate() const
{
return checkElementState(m_element.get(), ATK_STATE_INDETERMINATE);
}
int AccessibilityUIElement::hierarchicalLevel() const
{
String level = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "level");
if (!level.isEmpty())
return level.toInt();
return 0;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::speakAs()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
bool AccessibilityUIElement::ariaIsGrabbed() const
{
return getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "grabbed") == "true";
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::ariaDropEffects() const
{
String dropEffects = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "dropeffect");
return dropEffects.isEmpty() ? JSStringCreateWithCharacters(0, 0) : JSStringCreateWithUTF8CString(dropEffects.utf8().data());
}
// parameterized attributes
int AccessibilityUIElement::lineForIndex(int index)
{
if (!ATK_IS_TEXT(m_element.get()))
return -1;
if (index < 0 || index > atk_text_get_character_count(ATK_TEXT(m_element.get())))
return -1;
GUniquePtr<gchar> text(atk_text_get_text(ATK_TEXT(m_element.get()), 0, index));
int lineNo = 0;
for (gchar* offset = text.get(); *offset; ++offset) {
if (*offset == '\n')
++lineNo;
}
return lineNo;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForLine(int line)
{
if (!ATK_IS_TEXT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
AtkText* text = ATK_TEXT(m_element.get());
gint startOffset = 0, endOffset = 0;
for (int i = 0; i <= line; ++i)
atk_text_get_string_at_offset(text, endOffset, ATK_TEXT_GRANULARITY_LINE, &startOffset, &endOffset);
GUniquePtr<gchar> range(g_strdup_printf("{%d, %d}", startOffset, endOffset - startOffset));
return JSStringCreateWithUTF8CString(range.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForPosition(int x, int y)
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRange(unsigned location, unsigned length)
{
if (!ATK_IS_TEXT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
AtkTextRectangle rect;
atk_text_get_range_extents(ATK_TEXT(m_element.get()), location, location + length, ATK_XY_WINDOW, &rect);
GUniquePtr<gchar> bounds(g_strdup_printf("{%d, %d, %d, %d}", rect.x, rect.y, rect.width, rect.height));
return JSStringCreateWithUTF8CString(bounds.get());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForRange(unsigned location, unsigned length)
{
if (!ATK_IS_TEXT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
String string = atk_text_get_text(ATK_TEXT(m_element.get()), location, location + length);
return JSStringCreateWithUTF8CString(string.utf8().data());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForRange(unsigned location, unsigned length)
{
if (!ATK_IS_TEXT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
StringBuilder builder;
// The default text attributes apply to the entire element.
builder.appendLiteral("\n\tDefault text attributes:\n\t\t");
builder.append(attributeSetToString(getAttributeSet(m_element.get(), TextAttributeType), "\n\t\t"));
// The attribute run provides attributes specific to the range of text at the specified offset.
AtkText* text = ATK_TEXT(m_element.get());
gint start = 0, end = 0;
for (unsigned i = location; i < location + length; i = end) {
AtkAttributeSet* attributeSet = atk_text_get_run_attributes(text, i, &start, &end);
GUniquePtr<gchar> substring(replaceCharactersForResults(atk_text_get_text(text, start, end)));
builder.appendLiteral("\n\tRange attributes for '");
builder.append(substring.get());
builder.appendLiteral("':\n\t\t");
builder.append(attributeSetToString(attributeSet, "\n\t\t"));
}
return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
}
bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length)
{
// FIXME: implement
return false;
}
unsigned AccessibilityUIElement::uiElementCountForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
// FIXME: implement
return 0;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContextRef context, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity)
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
{
if (!ATK_IS_TABLE(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
Vector<RefPtr<AccessibilityUIElement> > columnHeaders = getTableColumnHeaders(ATK_TABLE(m_element.get()));
return createStringWithAttributes(columnHeaders);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
{
if (!ATK_IS_TABLE(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
Vector<RefPtr<AccessibilityUIElement> > rowHeaders = getTableRowHeaders(ATK_TABLE(m_element.get()));
return createStringWithAttributes(rowHeaders);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
{
if (!ATK_IS_TABLE(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
Vector<RefPtr<AccessibilityUIElement> > visibleCells = getVisibleCells(this);
return createStringWithAttributes(visibleCells);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader()
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
int AccessibilityUIElement::rowCount()
{
if (!ATK_IS_TABLE(m_element.get()))
return 0;
return atk_table_get_n_rows(ATK_TABLE(m_element.get()));
}
int AccessibilityUIElement::columnCount()
{
if (!ATK_IS_TABLE(m_element.get()))
return 0;
return atk_table_get_n_columns(ATK_TABLE(m_element.get()));
}
int AccessibilityUIElement::indexInTable()
{
// FIXME: implement
return -1;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::rowIndexRange()
{
// Range in table for rows.
return indexRangeInTable(m_element.get(), true);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange()
{
// Range in table for columns.
return indexRangeInTable(m_element.get(), false);
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row)
{
if (!ATK_IS_TABLE(m_element.get()))
return nullptr;
// Adopt the AtkObject representing the cell because
// at_table_ref_at() transfers full ownership.
GRefPtr<AtkObject> foundCell = adoptGRef(atk_table_ref_at(ATK_TABLE(m_element.get()), row, col));
return foundCell ? AccessibilityUIElement::create(foundCell.get()).ptr() : nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::horizontalScrollbar() const
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::verticalScrollbar() const
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange()
{
if (!ATK_IS_TEXT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
gint start, end;
g_free(atk_text_get_selection(ATK_TEXT(m_element.get()), 0, &start, &end));
GUniquePtr<gchar> selection(g_strdup_printf("{%d, %d}", start, end - start));
return JSStringCreateWithUTF8CString(selection.get());
}
bool AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length)
{
if (!ATK_IS_TEXT(m_element.get()))
return false;
if (!length)
return atk_text_set_caret_offset(ATK_TEXT(m_element.get()), location);
return atk_text_set_selection(ATK_TEXT(m_element.get()), 0, location, location + length);
}
void AccessibilityUIElement::increment()
{
alterCurrentValue(m_element.get(), 1);
}
void AccessibilityUIElement::decrement()
{
alterCurrentValue(m_element.get(), -1);
}
void AccessibilityUIElement::showMenu()
{
// FIXME: implement
}
void AccessibilityUIElement::press()
{
if (!ATK_IS_ACTION(m_element.get()))
return;
// Only one action per object is supported so far.
atk_action_do_action(ATK_ACTION(m_element.get()), 0);
}
void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) const
{
// FIXME: implement
}
void AccessibilityUIElement::setSelectedChildAtIndex(unsigned index) const
{
if (!ATK_IS_SELECTION(m_element.get()))
return;
atk_selection_add_selection(ATK_SELECTION(m_element.get()), index);
}
void AccessibilityUIElement::removeSelectionAtIndex(unsigned index) const
{
if (!ATK_IS_SELECTION(m_element.get()))
return;
atk_selection_remove_selection(ATK_SELECTION(m_element.get()), index);
}
void AccessibilityUIElement::clearSelectedChildren() const
{
if (!ATK_IS_SELECTION(m_element.get()))
return;
atk_selection_clear_selection(ATK_SELECTION(m_element.get()));
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::documentEncoding()
{
if (!ATK_IS_DOCUMENT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
if (role != ATK_ROLE_DOCUMENT_WEB)
return JSStringCreateWithCharacters(0, 0);
return JSStringCreateWithUTF8CString(atk_document_get_attribute_value(ATK_DOCUMENT(m_element.get()), "Encoding"));
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::documentURI()
{
if (!ATK_IS_DOCUMENT(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
if (role != ATK_ROLE_DOCUMENT_WEB)
return JSStringCreateWithCharacters(0, 0);
return JSStringCreateWithUTF8CString(atk_document_get_attribute_value(ATK_DOCUMENT(m_element.get()), "URI"));
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::url()
{
if (!ATK_IS_HYPERLINK_IMPL(m_element.get()))
return JSStringCreateWithCharacters(0, 0);
AtkHyperlink* hyperlink = atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(m_element.get()));
GUniquePtr<char> hyperlinkURI(atk_hyperlink_get_uri(hyperlink, 0));
if (!hyperlinkURI.get())
return JSStringCreateWithUTF8CString("AXURL: (null)");
// Build the result string, stripping the absolute URL paths if present.
char* localURI = g_strstr_len(hyperlinkURI.get(), -1, "LayoutTests");
String axURL = makeString("AXURL: ", localURI ? localURI : hyperlinkURI.get());
return JSStringCreateWithUTF8CString(axURL.utf8().data());
}
bool AccessibilityUIElement::addNotificationListener(JSValueRef functionCallback)
{
if (!functionCallback)
return false;
// Only one notification listener per element.
if (m_notificationHandler)
return false;
m_notificationHandler = AccessibilityNotificationHandler::create();
m_notificationHandler->setPlatformElement(platformUIElement());
m_notificationHandler->setNotificationFunctionCallback(functionCallback);
return true;
}
bool AccessibilityUIElement::removeNotificationListener()
{
// Programmers should not be trying to remove a listener that's already removed.
ASSERT(m_notificationHandler);
m_notificationHandler = nullptr;
return true;
}
bool AccessibilityUIElement::isFocusable() const
{
return checkElementState(m_element.get(), ATK_STATE_FOCUSABLE);
}
bool AccessibilityUIElement::isSelectable() const
{
return checkElementState(m_element.get(), ATK_STATE_SELECTABLE);
}
bool AccessibilityUIElement::isMultiSelectable() const
{
return checkElementState(m_element.get(), ATK_STATE_MULTISELECTABLE);
}
bool AccessibilityUIElement::isVisible() const
{
return checkElementState(m_element.get(), ATK_STATE_VISIBLE);
}
bool AccessibilityUIElement::isOffScreen() const
{
// FIXME: implement
return false;
}
bool AccessibilityUIElement::isCollapsed() const
{
// FIXME: implement
return false;
}
bool AccessibilityUIElement::isIgnored() const
{
// FIXME: implement
return false;
}
bool AccessibilityUIElement::isSingleLine() const
{
return checkElementState(m_element.get(), ATK_STATE_SINGLE_LINE);
}
bool AccessibilityUIElement::isMultiLine() const
{
return checkElementState(m_element.get(), ATK_STATE_MULTI_LINE);
}
bool AccessibilityUIElement::hasPopup() const
{
return checkElementState(m_element.get(), ATK_STATE_HAS_POPUP);
}
void AccessibilityUIElement::takeFocus()
{
// FIXME: implement
}
void AccessibilityUIElement::takeSelection()
{
// FIXME: implement
}
void AccessibilityUIElement::addSelection()
{
// FIXME: implement
}
void AccessibilityUIElement::removeSelection()
{
// FIXME: implement
}
// Text markers
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::lineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForElement(AccessibilityUIElement* element)
{
// FIXME: implement
return nullptr;
}
int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* range)
{
// FIXME: implement
return 0;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousTextMarker(AccessibilityTextMarker* textMarker)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextTextMarker(AccessibilityTextMarker* textMarker)
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForTextMarkerRange(AccessibilityTextMarkerRange* markerRange)
{
// FIXME: implement
return JSStringCreateWithCharacters(0, 0);
}
RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForBounds(int x, int y, int width, int height)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForBounds(int x, int y, int width, int height)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForPoint(int x, int y)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityUIElement> AccessibilityUIElement::accessibilityElementForTextMarker(AccessibilityTextMarker* marker)
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRange(AccessibilityTextMarkerRange*)
{
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRangeWithOptions(AccessibilityTextMarkerRange*, bool)
{
return nullptr;
}
bool AccessibilityUIElement::attributedStringForTextMarkerRangeContainsAttribute(JSStringRef attribute, AccessibilityTextMarkerRange* range)
{
// FIXME: implement
return false;
}
int AccessibilityUIElement::indexForTextMarker(AccessibilityTextMarker* marker)
{
// FIXME: implement
return -1;
}
bool AccessibilityUIElement::isTextMarkerValid(AccessibilityTextMarker* textMarker)
{
// FIXME: implement
return false;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForIndex(int textIndex)
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarker()
{
// FIXME: implement
return nullptr;
}
RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarker()
{
// FIXME: implement
return nullptr;
}
bool AccessibilityUIElement::setSelectedVisibleTextRange(AccessibilityTextMarkerRange*)
{
return false;
}
void AccessibilityUIElement::scrollToMakeVisible()
{
#if ATK_CHECK_VERSION(2, 30, 0)
if (!ATK_IS_COMPONENT(m_element.get()))
return;
atk_component_scroll_to(ATK_COMPONENT(m_element.get()), ATK_SCROLL_ANYWHERE);
#endif
}
void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
{
#if ATK_CHECK_VERSION(2, 30, 0)
if (!ATK_IS_COMPONENT(m_element.get()))
return;
atk_component_scroll_to_point(ATK_COMPONENT(m_element.get()), ATK_XY_WINDOW, x, y);
#endif
}
void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
{
// FIXME: implement
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const
{
// FIXME: implement
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::pathDescription() const
{
notImplemented();
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const
{
notImplemented();
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPrescriptsDescription() const
{
notImplemented();
return nullptr;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::classList() const
{
notImplemented();
return nullptr;
}
JSRetainPtr<JSStringRef> stringAtOffset(PlatformUIElement element, AtkTextBoundary boundary, int offset)
{
if (!ATK_IS_TEXT(element.get()))
return JSStringCreateWithCharacters(0, 0);
gint startOffset, endOffset;
StringBuilder builder;
AtkTextGranularity granularity;
switch (boundary) {
case ATK_TEXT_BOUNDARY_CHAR:
granularity = ATK_TEXT_GRANULARITY_CHAR;
break;
case ATK_TEXT_BOUNDARY_WORD_START:
granularity = ATK_TEXT_GRANULARITY_WORD;
break;
case ATK_TEXT_BOUNDARY_LINE_START:
granularity = ATK_TEXT_GRANULARITY_LINE;
break;
case ATK_TEXT_BOUNDARY_SENTENCE_START:
granularity = ATK_TEXT_GRANULARITY_SENTENCE;
break;
default:
return JSStringCreateWithCharacters(0, 0);
}
builder.append(atk_text_get_string_at_offset(ATK_TEXT(element.get()), offset, granularity, &startOffset, &endOffset));
builder.appendLiteral(", ");
builder.appendNumber(startOffset);
builder.appendLiteral(", ");
builder.appendNumber(endOffset);
return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::characterAtOffset(int offset)
{
return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_CHAR, offset);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::wordAtOffset(int offset)
{
return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_WORD_START, offset);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::lineAtOffset(int offset)
{
return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_LINE_START, offset);
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::sentenceAtOffset(int offset)
{
return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_SENTENCE_START, offset);
}
bool AccessibilityUIElement::replaceTextInRange(JSStringRef, int, int)
{
notImplemented();
return false;
}
JSRetainPtr<JSStringRef> AccessibilityUIElement::popupValue() const
{
notImplemented();
return nullptr;
}
} // namespace WTR
#endif // HAVE(ACCESSIBILITY)