diff options
Diffstat (limited to 'Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp')
-rw-r--r-- | Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp | 1397 |
1 files changed, 1155 insertions, 242 deletions
diff --git a/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp b/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp index a72f48612..ded3a329d 100644 --- a/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp +++ b/Tools/WebKitTestRunner/InjectedBundle/atk/AccessibilityUIElementAtk.cpp @@ -1,6 +1,7 @@ /* * 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 @@ -33,62 +34,182 @@ #include "InjectedBundlePage.h" #include "NotImplemented.h" #include <JavaScriptCore/JSStringRef.h> +#include <JavaScriptCore/OpaqueJSString.h> +#if ATK_CHECK_VERSION(2,11,90) +#include <WebKit/WKBundleFrame.h> +#endif #include <atk/atk.h> #include <wtf/Assertions.h> -#include <wtf/gobject/GOwnPtr.h> -#include <wtf/gobject/GRefPtr.h> +#include <wtf/glib/GRefPtr.h> +#include <wtf/glib/GUniquePtr.h> #include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> #include <wtf/text/WTFString.h> #include <wtf/unicode/CharacterNames.h> namespace WTR { -static String coreAttributeToAtkAttribute(JSStringRef attribute) +namespace { + +#if ATK_CHECK_VERSION(2,11,92) +enum RangeLimit { + RangeLimitMinimum, + RangeLimitMaximum +}; +#endif + +enum AtkAttributeType { + ObjectAttributeType, + TextAttributeType +}; + +enum AttributeDomain { + CoreDomain = 0, + AtkDomain +}; + +enum AttributesIndex { + // Attribute names. + InvalidNameIndex = 0, + PosInSetIndex, + SetSizeIndex, + PlaceholderNameIndex, + SortNameIndex, + + // Attribute values. + SortAscendingValueIndex, + SortDescendingValueIndex, + SortUnknownValueIndex, + + NumberOfAttributes +}; + +// Attribute names & Values (keep on sync with enum AttributesIndex). +const String attributesMap[][2] = { + // Attribute names. + { "AXInvalid", "invalid" }, + { "AXARIAPosInSet", "posinset" }, + { "AXARIASetSize", "setsize" }, + { "AXPlaceholderValue", "placeholder-text" } , + { "AXSortDirection", "sort" }, + + // Attribute values. + { "AXAscendingSortDirection", "ascending" }, + { "AXDescendingSortDirection", "descending" }, + { "AXUnknownSortDirection", "unknown" } +}; + +#if ATK_CHECK_VERSION(2, 11, 3) +const char* landmarkStringBanner = "AXLandmarkBanner"; +const char* landmarkStringComplementary = "AXLandmarkComplementary"; +const char* landmarkStringContentinfo = "AXLandmarkContentInfo"; +const char* landmarkStringMain = "AXLandmarkMain"; +const char* landmarkStringNavigation = "AXLandmarkNavigation"; +const char* landmarkStringSearch = "AXLandmarkSearch"; +#endif + +String jsStringToWTFString(JSStringRef attribute) { size_t bufferSize = JSStringGetMaximumUTF8CStringSize(attribute); - GOwnPtr<gchar> buffer(static_cast<gchar*>(g_malloc(bufferSize))); + GUniquePtr<gchar> buffer(static_cast<gchar*>(g_malloc(bufferSize))); JSStringGetUTF8CString(attribute, buffer.get(), bufferSize); - String attributeString = String::fromUTF8(buffer.get()); - return attributeString == "AXPlaceholderValue" ? "placeholder-text" : String(); + 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; } -static String getAttributeSetValueForId(AtkObject* accessible, const char* id) +String atkAttributeValueToCoreAttributeValue(AtkAttributeType type, const String& id, const String& value) { - const char* attributeValue = 0; - AtkAttributeSet* attributeSet = atk_object_get_attributes(accessible); + if (type == ObjectAttributeType) { + // 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 (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 (!strcmp(atkAttribute->name, id)) { - attributeValue = atkAttribute->value; + if (id == atkAttribute->name) { + attributeValue = String::fromUTF8(atkAttribute->value); break; } } - - String atkAttributeValue = String::fromUTF8(attributeValue); atk_attribute_set_free(attributeSet); - return atkAttributeValue; + return atkAttributeValueToCoreAttributeValue(type, id, attributeValue); } -static char* getAtkAttributeSetAsString(AtkObject* accessible) +String attributeSetToString(AtkAttributeSet* attributeSet, String separator=", ") { - GString* str = g_string_new(0); + if (!attributeSet) + return String(); - AtkAttributeSet* attributeSet = atk_object_get_attributes(accessible); + StringBuilder builder; for (AtkAttributeSet* attributes = attributeSet; attributes; attributes = attributes->next) { AtkAttribute* attribute = static_cast<AtkAttribute*>(attributes->data); - GOwnPtr<gchar> attributeData(g_strconcat(attribute->name, ":", attribute->value, NULL)); - g_string_append(str, attributeData.get()); + GUniquePtr<gchar> attributeData(g_strconcat(attribute->name, ":", attribute->value, NULL)); + builder.append(attributeData.get()); if (attributes->next) - g_string_append(str, ", "); + builder.append(separator); } atk_attribute_set_free(attributeSet); - return g_string_free(str, FALSE); + return builder.toString(); } -static bool checkElementState(PlatformUIElement element, AtkStateType stateType) +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; @@ -97,11 +218,14 @@ static bool checkElementState(PlatformUIElement element, AtkStateType stateType) return atk_state_set_contains_state(stateSet.get(), stateType); } -static JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange) +JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange) { - GOwnPtr<gchar> rangeString(g_strdup("{0, 0}")); - - if (!element || !ATK_IS_OBJECT(element.get())) + GUniquePtr<gchar> rangeString(g_strdup("{0, 0}")); +#if ATK_CHECK_VERSION(2,11,90) + if (!ATK_IS_TABLE_CELL(element.get())) + return JSStringCreateWithUTF8CString(rangeString.get()); +#else + if (!ATK_IS_OBJECT(element.get())) return JSStringCreateWithUTF8CString(rangeString.get()); AtkObject* axTable = atk_object_get_parent(ATK_OBJECT(element.get())); @@ -112,11 +236,20 @@ static JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange) gint indexInParent = atk_object_get_index_in_parent(ATK_OBJECT(element.get())); if (indexInParent == -1) return JSStringCreateWithUTF8CString(rangeString.get()); +#endif - int row = -1; - int column = -1; + gint row = -1; + gint column = -1; + gint rowSpan = -1; + gint columnSpan = -1; +#if ATK_CHECK_VERSION(2,11,90) + atk_table_cell_get_row_column_span(ATK_TABLE_CELL(element.get()), &row, &column, &rowSpan, &columnSpan); +#else row = atk_table_get_row_at_index(ATK_TABLE(axTable), indexInParent); column = atk_table_get_column_at_index(ATK_TABLE(axTable), indexInParent); + rowSpan = atk_table_get_row_extent_at(ATK_TABLE(axTable), row, column); + columnSpan = atk_table_get_column_extent_at(ATK_TABLE(axTable), row, column); +#endif // Get the actual values, if row and columns are valid values. if (row != -1 && column != -1) { @@ -124,22 +257,29 @@ static JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange) int length = 0; if (isRowRange) { base = row; - length = atk_table_get_row_extent_at(ATK_TABLE(axTable), row, column); + length = rowSpan; } else { base = column; - length = atk_table_get_column_extent_at(ATK_TABLE(axTable), row, column); + length = columnSpan; } - rangeString.set(g_strdup_printf("{%d, %d}", base, length)); + rangeString.reset(g_strdup_printf("{%d, %d}", base, length)); } return JSStringCreateWithUTF8CString(rangeString.get()); } -static void alterCurrentValue(PlatformUIElement element, int factor) +void alterCurrentValue(PlatformUIElement element, int factor) { - if (!element || !ATK_IS_VALUE(element.get())) + if (!ATK_IS_VALUE(element.get())) return; +#if ATK_CHECK_VERSION(2,11,92) + double currentValue; + atk_value_get_value_and_text(ATK_VALUE(element.get()), ¤tValue, nullptr); + + double increment = atk_value_get_increment(ATK_VALUE(element.get())); + atk_value_set_value(ATK_VALUE(element.get()), currentValue + factor * increment); +#else GValue currentValue = G_VALUE_INIT; atk_value_get_current_value(ATK_VALUE(element.get()), ¤tValue); @@ -147,7 +287,7 @@ static void alterCurrentValue(PlatformUIElement element, int factor) atk_value_get_minimum_increment(ATK_VALUE(element.get()), &increment); GValue newValue = G_VALUE_INIT; - g_value_init(&newValue, G_TYPE_DOUBLE); + g_value_init(&newValue, G_TYPE_FLOAT); g_value_set_float(&newValue, g_value_get_float(¤tValue) + factor * g_value_get_float(&increment)); atk_value_set_current_value(ATK_VALUE(element.get()), &newValue); @@ -155,9 +295,10 @@ static void alterCurrentValue(PlatformUIElement element, int factor) g_value_unset(&newValue); g_value_unset(&increment); g_value_unset(¤tValue); +#endif } -static gchar* replaceCharactersForResults(gchar* str) +gchar* replaceCharactersForResults(gchar* str) { WTF::String uString = WTF::String::fromUTF8(str); @@ -173,114 +314,366 @@ static gchar* replaceCharactersForResults(gchar* str) return g_strdup(uString.utf8().data()); } -static const gchar* roleToString(AtkRole role) -{ +const gchar* roleToString(AtkObject* object) +{ + AtkRole role = atk_object_get_role(object); + +#if ATK_CHECK_VERSION(2, 11, 3) + 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, "main")) + return landmarkStringMain; + if (equalLettersIgnoringASCIICase(xmlRolesValue, "navigation")) + return landmarkStringNavigation; + if (equalLettersIgnoringASCIICase(xmlRolesValue, "search")) + return landmarkStringSearch; + } +#endif + switch (role) { case ATK_ROLE_ALERT: - return "AXRole: AXAlert"; + return "AXAlert"; + case ATK_ROLE_DIALOG: + return "AXDialog"; case ATK_ROLE_CANVAS: - return "AXRole: AXCanvas"; + return "AXCanvas"; + case ATK_ROLE_CAPTION: + return "AXCaption"; case ATK_ROLE_CHECK_BOX: - return "AXRole: AXCheckBox"; + return "AXCheckBox"; + case ATK_ROLE_COLOR_CHOOSER: + return "AXColorWell"; case ATK_ROLE_COLUMN_HEADER: - return "AXRole: AXColumnHeader"; + return "AXColumnHeader"; case ATK_ROLE_COMBO_BOX: - return "AXRole: AXComboBox"; + return "AXComboBox"; + case ATK_ROLE_COMMENT: + return "AXComment"; case ATK_ROLE_DOCUMENT_FRAME: - return "AXRole: AXWebArea"; + return "AXDocument"; + case ATK_ROLE_DOCUMENT_WEB: + return "AXWebArea"; + case ATK_ROLE_EMBEDDED: + return "AXEmbedded"; case ATK_ROLE_ENTRY: - return "AXRole: AXTextField"; + return "AXTextField"; case ATK_ROLE_FOOTER: - return "AXRole: AXFooter"; + return "AXFooter"; case ATK_ROLE_FORM: - return "AXRole: AXForm"; + return "AXForm"; case ATK_ROLE_GROUPING: - return "AXRole: AXGroup"; + return "AXGroup"; case ATK_ROLE_HEADING: - return "AXRole: AXHeading"; + return "AXHeading"; case ATK_ROLE_IMAGE: - return "AXRole: AXImage"; + return "AXImage"; case ATK_ROLE_IMAGE_MAP: - return "AXRole: AXImageMap"; + return "AXImageMap"; + case ATK_ROLE_INVALID: + return "AXInvalid"; case ATK_ROLE_LABEL: - return "AXRole: AXLabel"; + return "AXLabel"; case ATK_ROLE_LINK: - return "AXRole: AXLink"; + return "AXLink"; case ATK_ROLE_LIST: - return "AXRole: AXList"; + return "AXList"; case ATK_ROLE_LIST_BOX: - return "AXRole: AXListBox"; + return "AXListBox"; case ATK_ROLE_LIST_ITEM: - return "AXRole: AXListItem"; + return "AXListItem"; case ATK_ROLE_MENU: - return "AXRole: AXMenu"; + return "AXMenu"; case ATK_ROLE_MENU_BAR: - return "AXRole: AXMenuBar"; + return "AXMenuBar"; case ATK_ROLE_MENU_ITEM: - return "AXRole: AXMenuItem"; + return "AXMenuItem"; case ATK_ROLE_PAGE_TAB: - return "AXRole: AXTab"; + return "AXTab"; case ATK_ROLE_PAGE_TAB_LIST: - return "AXRole: AXTabGroup"; + return "AXTabGroup"; case ATK_ROLE_PANEL: - return "AXRole: AXGroup"; + return "AXGroup"; case ATK_ROLE_PARAGRAPH: - return "AXRole: AXParagraph"; + return "AXParagraph"; case ATK_ROLE_PASSWORD_TEXT: - return "AXRole: AXPasswordField"; + return "AXPasswordField"; + case ATK_ROLE_PROGRESS_BAR: + return "AXProgressIndicator"; case ATK_ROLE_PUSH_BUTTON: - return "AXRole: AXButton"; + return "AXButton"; case ATK_ROLE_RADIO_BUTTON: - return "AXRole: AXRadioButton"; + return "AXRadioButton"; + case ATK_ROLE_RADIO_MENU_ITEM: + return "AXRadioMenuItem"; case ATK_ROLE_ROW_HEADER: - return "AXRole: AXRowHeader"; + return "AXRowHeader"; + case ATK_ROLE_CHECK_MENU_ITEM: + return "AXCheckMenuItem"; case ATK_ROLE_RULER: - return "AXRole: AXRuler"; + return "AXRuler"; case ATK_ROLE_SCROLL_BAR: - return "AXRole: AXScrollBar"; + return "AXScrollBar"; case ATK_ROLE_SCROLL_PANE: - return "AXRole: AXScrollArea"; + return "AXScrollArea"; case ATK_ROLE_SECTION: - return "AXRole: AXDiv"; + return "AXSection"; case ATK_ROLE_SEPARATOR: - return "AXRole: AXHorizontalRule"; + return "AXSeparator"; case ATK_ROLE_SLIDER: - return "AXRole: AXSlider"; + return "AXSlider"; case ATK_ROLE_SPIN_BUTTON: - return "AXRole: AXSpinButton"; + return "AXSpinButton"; + case ATK_ROLE_STATUSBAR: + return "AXStatusBar"; case ATK_ROLE_TABLE: - return "AXRole: AXTable"; + return "AXTable"; case ATK_ROLE_TABLE_CELL: - return "AXRole: AXCell"; + return "AXCell"; case ATK_ROLE_TABLE_COLUMN_HEADER: - return "AXRole: AXColumnHeader"; + return "AXColumnHeader"; case ATK_ROLE_TABLE_ROW: - return "AXRole: AXRow"; + return "AXRow"; case ATK_ROLE_TABLE_ROW_HEADER: - return "AXRole: AXRowHeader"; + return "AXRowHeader"; case ATK_ROLE_TOGGLE_BUTTON: - return "AXRole: AXToggleButton"; + return "AXToggleButton"; case ATK_ROLE_TOOL_BAR: - return "AXRole: AXToolbar"; + return "AXToolbar"; case ATK_ROLE_TOOL_TIP: - return "AXRole: AXUserInterfaceTooltip"; + return "AXUserInterfaceTooltip"; case ATK_ROLE_TREE: - return "AXRole: AXTree"; + return "AXTree"; case ATK_ROLE_TREE_TABLE: - return "AXRole: AXTreeGrid"; + return "AXTreeGrid"; case ATK_ROLE_TREE_ITEM: - return "AXRole: AXTreeItem"; + return "AXTreeItem"; case ATK_ROLE_WINDOW: - return "AXRole: AXWindow"; + return "AXWindow"; case ATK_ROLE_UNKNOWN: - return "AXRole: AXUnknown"; + return "AXUnknown"; +#if ATK_CHECK_VERSION(2, 11, 3) + 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"; +#endif +#if ATK_CHECK_VERSION(2, 11, 4) + case ATK_ROLE_DESCRIPTION_LIST: + return "AXDescriptionList"; + case ATK_ROLE_DESCRIPTION_TERM: + return "AXDescriptionTerm"; + case ATK_ROLE_DESCRIPTION_VALUE: + return "AXDescriptionValue"; +#endif +#if ATK_CHECK_VERSION(2, 15, 2) + case ATK_ROLE_STATIC: + return "AXStatic"; +#endif +#if ATK_CHECK_VERSION(2, 15, 4) + 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"; +#endif default: // We want to distinguish ATK_ROLE_UNKNOWN from a known AtkRole which // our DRT isn't properly handling. - return "AXRole: FIXME not identified"; + 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(String::format("%s\n", element->role()->string().utf8().data())); + + // For the parent we print its role and its name, if available. + builder.append("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 && g_utf8_strlen(parentName, -1)) + builder.append(String::format(": %s", parentName)); + } else + builder.append("(null)"); + builder.append("\n"); + + builder.append(String::format("AXChildren: %d\n", element->childrenCount())); + builder.append(String::format("AXPosition: { %f, %f }\n", element->x(), element->y())); + builder.append(String::format("AXSize: { %f, %f }\n", element->width(), element->height())); + + String title = element->title()->string(); + if (!title.isEmpty()) + builder.append(String::format("%s\n", title.utf8().data())); + + String description = element->description()->string(); + if (!description.isEmpty()) + builder.append(String::format("%s\n", description.utf8().data())); + + String value = element->stringValue()->string(); + if (!value.isEmpty()) + builder.append(String::format("%s\n", value.utf8().data())); + + builder.append(String::format("AXFocusable: %d\n", element->isFocusable())); + builder.append(String::format("AXFocused: %d\n", element->isFocused())); + builder.append(String::format("AXSelectable: %d\n", element->isSelectable())); + builder.append(String::format("AXSelected: %d\n", element->isSelected())); + builder.append(String::format("AXMultiSelectable: %d\n", element->isMultiSelectable())); + builder.append(String::format("AXEnabled: %d\n", element->isEnabled())); + builder.append(String::format("AXExpanded: %d\n", element->isExpanded())); + builder.append(String::format("AXRequired: %d\n", element->isRequired())); + builder.append(String::format("AXChecked: %d\n", element->isChecked())); + + String url = element->url()->string(); + if (!url.isEmpty()) + builder.append(String::format("%s\n", url.utf8().data())); + + // We append the ATK specific attributes as a single line at the end. + builder.append("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.append("\n------------\n"); } + + return JSStringCreateWithUTF8CString(builder.toString().utf8().data()); +} + +static Vector<RefPtr<AccessibilityUIElement> > getRowHeaders(AtkTable* accessible) +{ + Vector<RefPtr<AccessibilityUIElement> > rowHeaders; + + int rowsCount = atk_table_get_n_rows(accessible); + for (int row = 0; row < rowsCount; ++row) + rowHeaders.append(AccessibilityUIElement::create(atk_table_get_row_header(accessible, row))); + + return rowHeaders; +} + +static Vector<RefPtr<AccessibilityUIElement> > getColumnHeaders(AtkTable* accessible) +{ + Vector<RefPtr<AccessibilityUIElement> > columnHeaders; + + int columnsCount = atk_table_get_n_columns(accessible); + for (int column = 0; column < columnsCount; ++column) + columnHeaders.append(AccessibilityUIElement::create(atk_table_get_column_header(accessible, column))); + + 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; +} + +#if ATK_CHECK_VERSION(2,11,90) +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); +} +#endif + +#if ATK_CHECK_VERSION(2,11,92) +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; +} +#endif + +} // namespace + AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element) : m_element(element) { @@ -303,7 +696,7 @@ bool AccessibilityUIElement::isEqual(AccessibilityUIElement* otherElement) void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> >& children) { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return; int count = childrenCount(); @@ -315,7 +708,7 @@ void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> > void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIElement> >& children, unsigned location, unsigned length) { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return; unsigned end = location + length; for (unsigned i = location; i < end; i++) { @@ -326,7 +719,7 @@ void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIE int AccessibilityUIElement::childrenCount() { - if (!m_element) + if (!ATK_IS_OBJECT(m_element.get())) return 0; return atk_object_get_n_accessible_children(ATK_OBJECT(m_element.get())); @@ -334,16 +727,16 @@ int AccessibilityUIElement::childrenCount() PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPoint(int x, int y) { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0; + 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 objectAtPoint ? AccessibilityUIElement::create(objectAtPoint.get()) : 0; + return AccessibilityUIElement::create(objectAtPoint ? objectAtPoint.get() : m_element.get()); } unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element) { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return 0; Vector<RefPtr<AccessibilityUIElement> > children; @@ -359,73 +752,118 @@ unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element) PassRefPtr<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 0; + return nullptr; +} + +static PassRefPtr<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) : nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::linkedUIElementAtIndex(unsigned index) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index) { - // FIXME: implement - return 0; + return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_FLOWS_TO, index); +} + +PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsElementAtIndex(unsigned index) +{ + return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_CONTROLLER_FOR, index); } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedRowAtIndex(unsigned index) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index) { - // FIXME: implement - return 0; + // 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)) : nullptr; + } + } + + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const { - // FIXME: implement - return 0; + 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()) : nullptr; } unsigned AccessibilityUIElement::selectedChildrenCount() const { - // FIXME: implement - return 0; + if (!ATK_IS_SELECTION(m_element.get())) + return 0; + return atk_selection_get_selection_count(ATK_SELECTION(m_element.get())); } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedRowAtIndex(unsigned index) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::titleUIElement() { - if (!m_element) - return 0; + if (!ATK_IS_OBJECT(m_element.get())) + return nullptr; AtkRelationSet* set = atk_object_ref_relation_set(ATK_OBJECT(m_element.get())); if (!set) - return 0; + return nullptr; - AtkObject* target = 0; + 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); @@ -437,22 +875,22 @@ PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::titleUIElement() } g_object_unref(set); - return target ? AccessibilityUIElement::create(target) : 0; + return target ? AccessibilityUIElement::create(target) : nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::parentElement() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0; + 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) : 0; + return parent ? AccessibilityUIElement::create(parent) : nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedByRow() { // FIXME: implement - return 0; + return nullptr; } JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfLinkedUIElements() @@ -469,42 +907,126 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfDocumentLinks() JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren() { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + 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 (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return JSStringCreateWithCharacters(0, 0); - GOwnPtr<char> attributeData(getAtkAttributeSetAsString(ATK_OBJECT(m_element.get()))); - return JSStringCreateWithUTF8CString(attributeData.get()); + return JSStringCreateWithUTF8CString(attributesOfElement(this).utf8().data()); } JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValue(JSStringRef attribute) { - if (!m_element) + if (!ATK_IS_OBJECT(m_element.get())) return JSStringCreateWithCharacters(0, 0); String atkAttributeName = coreAttributeToAtkAttribute(attribute); - if (atkAttributeName.isNull()) - return JSStringCreateWithCharacters(0, 0); - String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), atkAttributeName.utf8().data()); + // 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(); + } + + return 0; +} + +JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attribute) const +{ // FIXME: implement - return 0.0f; + return nullptr; +} + +JSValueRef AccessibilityUIElement::rowHeaders() const +{ +#if ATK_CHECK_VERSION(2,11,90) + if (!ATK_IS_TABLE_CELL(m_element.get())) + return nullptr; + + GRefPtr<GPtrArray> array = adoptGRef(atk_table_cell_get_row_header_cells(ATK_TABLE_CELL(m_element.get()))); + if (!array) + return nullptr; + + Vector<RefPtr<AccessibilityUIElement>> rows = convertGPtrArrayToVector(array.get()); + return convertToJSObjectArray(rows); +#else + return nullptr; +#endif +} + +JSValueRef AccessibilityUIElement::columnHeaders() const +{ +#if ATK_CHECK_VERSION(2,11,90) + if (!ATK_IS_TABLE_CELL(m_element.get()) && !ATK_IS_TABLE(m_element.get())) + return nullptr; + + Vector<RefPtr<AccessibilityUIElement>> columns; + if (ATK_IS_TABLE_CELL(m_element.get())) { + GRefPtr<GPtrArray> array = adoptGRef(atk_table_cell_get_column_header_cells(ATK_TABLE_CELL(m_element.get()))); + if (!array) + return nullptr; + + columns = convertGPtrArrayToVector(array.get()); + } else + columns = getColumnHeaders(ATK_TABLE(m_element.get())); + return convertToJSObjectArray(columns); +#else + return nullptr; +#endif } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const { // FIXME: implement - return 0; + return nullptr; } bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute) @@ -515,14 +1037,74 @@ bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute) bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute) { - // FIXME: implement + 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; + +#if ATK_CHECK_VERSION(2,11,2) + // This state is applicable to checkboxes, radiobuttons, switches, etc. + if (checkElementState(m_element.get(), ATK_STATE_CHECKABLE)) + return true; +#endif + +#if ATK_CHECK_VERSION(2,15,3) + // 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; +#endif + + // 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) { - // FIXME: implement - return false; + if (!ATK_IS_OBJECT(m_element.get())) + return false; + + String atkAttributeName = coreAttributeToAtkAttribute(attribute); + if (atkAttributeName.isEmpty()) + return false; + + // 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); + + return !attributeValue.isEmpty(); } JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames() @@ -533,15 +1115,11 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames() JSRetainPtr<JSStringRef> AccessibilityUIElement::role() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return JSStringCreateWithCharacters(0, 0); - - AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get())); - if (!role) + if (!ATK_IS_OBJECT(m_element.get())) return JSStringCreateWithCharacters(0, 0); - GOwnPtr<gchar> axRole(g_strdup(roleToString(role))); - return JSStringCreateWithUTF8CString(axRole.get()); + GUniquePtr<char> roleStringWithPrefix(g_strdup_printf("AXRole: %s", roleToString(ATK_OBJECT(m_element.get())))); + return JSStringCreateWithUTF8CString(roleStringWithPrefix.get()); } JSRetainPtr<JSStringRef> AccessibilityUIElement::subrole() @@ -556,37 +1134,46 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::roleDescription() return JSStringCreateWithCharacters(0, 0); } +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 (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return JSStringCreateWithCharacters(0, 0); const gchar* name = atk_object_get_name(ATK_OBJECT(m_element.get())); - GOwnPtr<gchar> axTitle(g_strdup_printf("AXTitle: %s", name ? name : "")); + GUniquePtr<gchar> axTitle(g_strdup_printf("AXTitle: %s", name ? name : "")); return JSStringCreateWithUTF8CString(axTitle.get()); } JSRetainPtr<JSStringRef> AccessibilityUIElement::description() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + 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); - GOwnPtr<gchar> axDesc(g_strdup_printf("AXDescription: %s", description)); + GUniquePtr<gchar> axDesc(g_strdup_printf("AXDescription: %s", description)); return JSStringCreateWithUTF8CString(axDesc.get()); } JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return JSStringCreateWithCharacters(0, 0); - const gchar* axOrientation = 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)) @@ -600,126 +1187,215 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const JSRetainPtr<JSStringRef> AccessibilityUIElement::stringValue() { - if (!m_element || !ATK_IS_TEXT(m_element.get())) + if (!ATK_IS_TEXT(m_element.get())) return JSStringCreateWithCharacters(0, 0); - GOwnPtr<gchar> text(atk_text_get_text(ATK_TEXT(m_element.get()), 0, -1)); - GOwnPtr<gchar> textWithReplacedCharacters(replaceCharactersForResults(text.get())); - GOwnPtr<gchar> axValue(g_strdup_printf("AXValue: %s", textWithReplacedCharacters.get())); + 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 (!m_element || !ATK_IS_OBJECT(m_element.get())) + 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); - GOwnPtr<char> axValue(g_strdup_printf("AXLanguage: %s", locale)); + GUniquePtr<char> axValue(g_strdup_printf("AXLanguage: %s", locale)); return JSStringCreateWithUTF8CString(axValue.get()); } JSRetainPtr<JSStringRef> AccessibilityUIElement::helpText() const { - // FIXME: implement - // We need a way to call WebCore::AccessibilityObject::helpText() - // from here, probably a new helper class in WebProcess/WebCoreSupport. - return JSStringCreateWithCharacters(0, 0); + 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 nullptr; + + AtkRelation* relation = atk_relation_set_get_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY); + if (!relation) + return nullptr; + + GPtrArray* targetList = atk_relation_get_target(relation); + if (!targetList || !targetList->len) + return nullptr; + + StringBuilder builder; + builder.append("AXHelp: "); + + for (int 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 (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; + if (!ATK_IS_COMPONENT(m_element.get())) + return 0; - int x, y; - atk_component_get_position(ATK_COMPONENT(m_element.get()), &x, &y, ATK_XY_SCREEN); + int x; +#if ATK_CHECK_VERSION(2,11,90) + atk_component_get_extents(ATK_COMPONENT(m_element.get()), &x, nullptr, nullptr, nullptr, ATK_XY_SCREEN); +#else + atk_component_get_position(ATK_COMPONENT(m_element.get()), &x, nullptr, ATK_XY_SCREEN); +#endif return x; } double AccessibilityUIElement::y() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; + if (!ATK_IS_COMPONENT(m_element.get())) + return 0; - int x, y; - atk_component_get_position(ATK_COMPONENT(m_element.get()), &x, &y, ATK_XY_SCREEN); + int y; +#if ATK_CHECK_VERSION(2,11,90) + atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, &y, nullptr, nullptr, ATK_XY_SCREEN); +#else + atk_component_get_position(ATK_COMPONENT(m_element.get()), nullptr, &y, ATK_XY_SCREEN); +#endif return y; } double AccessibilityUIElement::width() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; + if (!ATK_IS_COMPONENT(m_element.get())) + return 0; - int width, height; - atk_component_get_size(ATK_COMPONENT(m_element.get()), &width, &height); + int width; +#if ATK_CHECK_VERSION(2,11,90) + atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, nullptr, &width, nullptr, ATK_XY_WINDOW); +#else + atk_component_get_size(ATK_COMPONENT(m_element.get()), &width, nullptr); +#endif return width; } double AccessibilityUIElement::height() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; + if (!ATK_IS_COMPONENT(m_element.get())) + return 0; - int width, height; - atk_component_get_size(ATK_COMPONENT(m_element.get()), &width, &height); + int height; +#if ATK_CHECK_VERSION(2,11,90) + atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, nullptr, nullptr, &height, ATK_XY_WINDOW); +#else + atk_component_get_size(ATK_COMPONENT(m_element.get()), nullptr, &height); +#endif return height; } double AccessibilityUIElement::clickPointX() { - // FIXME: implement - return 0.0f; + if (!ATK_IS_COMPONENT(m_element.get())) + return 0; + + int x, width; +#if ATK_CHECK_VERSION(2,11,90) + atk_component_get_extents(ATK_COMPONENT(m_element.get()), &x, nullptr, &width, nullptr, ATK_XY_WINDOW); +#else + atk_component_get_position(ATK_COMPONENT(m_element.get()), &x, nullptr, ATK_XY_WINDOW); + atk_component_get_size(ATK_COMPONENT(m_element.get()), &width, nullptr); +#endif + + return x + width / 2.0; } double AccessibilityUIElement::clickPointY() { - // FIXME: implement - return 0.0f; + if (!ATK_IS_COMPONENT(m_element.get())) + return 0; + + int y, height; +#if ATK_CHECK_VERSION(2,11,90) + atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, &y, nullptr, &height, ATK_XY_WINDOW); +#else + atk_component_get_position(ATK_COMPONENT(m_element.get()), nullptr, &y, ATK_XY_WINDOW); + atk_component_get_size(ATK_COMPONENT(m_element.get()), nullptr, &height); +#endif + + return y + height / 2.0; } double AccessibilityUIElement::intValue() const { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; + if (!ATK_IS_OBJECT(m_element.get())) + return 0; - GValue value = G_VALUE_INIT; - atk_value_get_current_value(ATK_VALUE(m_element.get()), &value); - if (!G_VALUE_HOLDS_FLOAT(&value)) - return 0.0f; + if (ATK_IS_VALUE(m_element.get())) { +#if ATK_CHECK_VERSION(2,11,92) + double value; + atk_value_get_value_and_text(ATK_VALUE(m_element.get()), &value, nullptr); + return value; +#else + GValue value = G_VALUE_INIT; + atk_value_get_current_value(ATK_VALUE(m_element.get()), &value); + if (!G_VALUE_HOLDS_FLOAT(&value)) + return 0; + return g_value_get_float(&value); +#endif + } - return g_value_get_float(&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 (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; - + if (!ATK_IS_VALUE(m_element.get())) + return 0; +#if ATK_CHECK_VERSION(2,11,92) + return rangeMinMaxValue(ATK_VALUE(m_element.get()), RangeLimitMinimum); +#else GValue value = G_VALUE_INIT; atk_value_get_minimum_value(ATK_VALUE(m_element.get()), &value); if (!G_VALUE_HOLDS_FLOAT(&value)) - return 0.0f; + return 0; return g_value_get_float(&value); +#endif } double AccessibilityUIElement::maxValue() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return 0.0f; + if (!ATK_IS_VALUE(m_element.get())) + return 0; +#if ATK_CHECK_VERSION(2,11,92) + return rangeMinMaxValue(ATK_VALUE(m_element.get()), RangeLimitMaximum); +#else GValue value = G_VALUE_INIT; atk_value_get_maximum_value(ATK_VALUE(m_element.get()), &value); if (!G_VALUE_HOLDS_FLOAT(&value)) - return 0.0f; + return 0; return g_value_get_float(&value); +#endif } JSRetainPtr<JSStringRef> AccessibilityUIElement::valueDescription() @@ -736,8 +1412,11 @@ int AccessibilityUIElement::insertionPointLineNumber() bool AccessibilityUIElement::isPressActionSupported() { - // FIXME: implement - return false; + 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() @@ -772,6 +1451,11 @@ 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); @@ -782,6 +1466,11 @@ 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 { // FIXME: implement @@ -809,14 +1498,34 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::ariaDropEffects() const // parameterized attributes int AccessibilityUIElement::lineForIndex(int index) { - // FIXME: implement - return 0; + 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) { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + 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) @@ -827,20 +1536,50 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForPosition(int x, int y) JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRange(unsigned location, unsigned length) { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + 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) { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + 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) { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + if (!ATK_IS_TEXT(m_element.get())) + return JSStringCreateWithCharacters(0, 0); + + StringBuilder builder; + + // The default text attributes apply to the entire element. + builder.append("\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. + AtkAttributeSet* attributeSet; + AtkText* text = ATK_TEXT(m_element.get()); + gint start = 0, end = 0; + for (int 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.append(String::format("\n\tRange attributes for '%s':\n\t\t", substring.get())); + builder.append(attributeSetToString(attributeSet, "\n\t\t")); + } + + atk_attribute_set_free(attributeSet); + + return JSStringCreateWithUTF8CString(builder.toString().utf8().data()); } bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length) @@ -849,22 +1588,40 @@ bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location return false; } -PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly) +unsigned AccessibilityUIElement::uiElementCountForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly) { // FIXME: implement return 0; } -JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders() +PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly) { // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + return nullptr; } -JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders() +JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContextRef context, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity) { // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + return nullptr; +} + +JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders() +{ + if (!ATK_IS_TABLE(m_element.get())) + return JSStringCreateWithCharacters(0, 0); + + Vector<RefPtr<AccessibilityUIElement> > columnHeaders = getColumnHeaders(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 = getRowHeaders(ATK_TABLE(m_element.get())); + return createStringWithAttributes(rowHeaders); } JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns() @@ -881,8 +1638,11 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows() JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells() { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + if (!ATK_IS_TABLE(m_element.get())) + return JSStringCreateWithCharacters(0, 0); + + Vector<RefPtr<AccessibilityUIElement> > visibleCells = getVisibleCells(this); + return createStringWithAttributes(visibleCells); } JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader() @@ -893,7 +1653,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader() int AccessibilityUIElement::rowCount() { - if (!m_element || !ATK_IS_TABLE(m_element.get())) + if (!ATK_IS_TABLE(m_element.get())) return 0; return atk_table_get_n_rows(ATK_TABLE(m_element.get())); @@ -901,7 +1661,7 @@ int AccessibilityUIElement::rowCount() int AccessibilityUIElement::columnCount() { - if (!m_element || !ATK_IS_TABLE(m_element.get())) + if (!ATK_IS_TABLE(m_element.get())) return 0; return atk_table_get_n_columns(ATK_TABLE(m_element.get())); @@ -927,36 +1687,48 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange() PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row) { - if (!m_element || !ATK_IS_TABLE(m_element.get())) - return 0; + 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()) : 0; + return foundCell ? AccessibilityUIElement::create(foundCell.get()) : nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::horizontalScrollbar() const { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::verticalScrollbar() const { // FIXME: implement - return 0; + return nullptr; } JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange() { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + 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()); } -void AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length) +bool AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length) { - // FIXME: implement + 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() @@ -976,9 +1748,6 @@ void AccessibilityUIElement::showMenu() void AccessibilityUIElement::press() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) - return; - if (!ATK_IS_ACTION(m_element.get())) return; @@ -991,6 +1760,22 @@ void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) c // 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); +} + JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const { // FIXME: implement @@ -999,7 +1784,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const JSRetainPtr<JSStringRef> AccessibilityUIElement::documentEncoding() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_DOCUMENT(m_element.get())) return JSStringCreateWithCharacters(0, 0); AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get())); @@ -1011,7 +1796,7 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::documentEncoding() JSRetainPtr<JSStringRef> AccessibilityUIElement::documentURI() { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_DOCUMENT(m_element.get())) return JSStringCreateWithCharacters(0, 0); AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get())); @@ -1023,19 +1808,40 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::documentURI() JSRetainPtr<JSStringRef> AccessibilityUIElement::url() { - // FIXME: implement - return JSStringCreateWithCharacters(0, 0); + 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)); + + // Build the result string, stripping the absolute URL paths if present. + char* localURI = g_strstr_len(hyperlinkURI.get(), -1, "LayoutTests"); + String axURL = String::format("AXURL: %s", localURI ? localURI : hyperlinkURI.get()); + return JSStringCreateWithUTF8CString(axURL.utf8().data()); } bool AccessibilityUIElement::addNotificationListener(JSValueRef functionCallback) { - // FIXME: implement + 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() { - // FIXME: implement + // Programmers should not be trying to remove a listener that's already removed. + ASSERT(m_notificationHandler); + m_notificationHandler = nullptr; + return true; } @@ -1079,10 +1885,11 @@ bool AccessibilityUIElement::isIgnored() const bool AccessibilityUIElement::hasPopup() const { - if (!m_element || !ATK_IS_OBJECT(m_element.get())) + if (!ATK_IS_OBJECT(m_element.get())) return false; - return equalIgnoringCase(getAttributeSetValueForId(ATK_OBJECT(m_element.get()), "aria-haspopup"), "true"); + String hasPopupValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "haspopup"); + return equalLettersIgnoringASCIICase(hasPopupValue, "true"); } void AccessibilityUIElement::takeFocus() @@ -1106,10 +1913,16 @@ void AccessibilityUIElement::removeSelection() } // Text markers +PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::lineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker) +{ + // FIXME: implement + return nullptr; +} + PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForElement(AccessibilityUIElement* element) { // FIXME: implement - return 0; + return nullptr; } int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* range) @@ -1121,13 +1934,13 @@ int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousTextMarker(AccessibilityTextMarker* textMarker) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextTextMarker(AccessibilityTextMarker* textMarker) { // FIXME: implement - return 0; + return nullptr; } JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForTextMarkerRange(AccessibilityTextMarkerRange* markerRange) @@ -1139,31 +1952,43 @@ JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForTextMarkerRange(Access PassRefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range) { // FIXME: implement - return 0; + return nullptr; +} + +PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForBounds(int x, int y, int width, int height) +{ + // FIXME: implement + return nullptr; +} + +PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForBounds(int x, int y, int width, int height) +{ + // FIXME: implement + return nullptr; } PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForPoint(int x, int y) { // FIXME: implement - return 0; + return nullptr; } PassRefPtr<AccessibilityUIElement> AccessibilityUIElement::accessibilityElementForTextMarker(AccessibilityTextMarker* marker) { // FIXME: implement - return 0; + return nullptr; } bool AccessibilityUIElement::attributedStringForTextMarkerRangeContainsAttribute(JSStringRef attribute, AccessibilityTextMarkerRange* range) @@ -1187,38 +2012,126 @@ bool AccessibilityUIElement::isTextMarkerValid(AccessibilityTextMarker* textMark PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForIndex(int textIndex) { // FIXME: implement - return 0; + return nullptr; +} + +PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarker() +{ + // FIXME: implement + return nullptr; +} + +PassRefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarker() +{ + // FIXME: implement + return nullptr; +} + +bool AccessibilityUIElement::setSelectedVisibleTextRange(AccessibilityTextMarkerRange*) +{ + return false; } void AccessibilityUIElement::scrollToMakeVisible() { // FIXME: implement } + +void AccessibilityUIElement::scrollToGlobalPoint(int x, int y) +{ + // FIXME: implement +} + +void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height) +{ + // FIXME: implement +} JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const { // FIXME: implement - return 0; + return nullptr; } JSRetainPtr<JSStringRef> AccessibilityUIElement::pathDescription() const { notImplemented(); - return 0; + return nullptr; } JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const { notImplemented(); - return 0; + return nullptr; } JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPrescriptsDescription() const { notImplemented(); - return 0; + return nullptr; } -} // namespace WTR +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; + +#if ATK_CHECK_VERSION(2, 10, 0) + 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)); +#else + builder.append(atk_text_get_text_at_offset(ATK_TEXT(element.get()), offset, boundary, &startOffset, &endOffset)); #endif + builder.append(String::format(", %i, %i", startOffset, 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); +} + +} // namespace WTR + +#endif // HAVE(ACCESSIBILITY) |