1/*
2 * Copyright (C) 2011 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2012 Igalia S.L.
4 * Copyright (C) 2013 Samsung Electronics. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include "config.h"
29#include "AccessibilityUIElement.h"
30
31#if HAVE(ACCESSIBILITY)
32
33#include "InjectedBundle.h"
34#include "InjectedBundlePage.h"
35#include <JavaScriptCore/JSStringRef.h>
36#include <JavaScriptCore/OpaqueJSString.h>
37#include <WebCore/NotImplemented.h>
38#if ATK_CHECK_VERSION(2,11,90)
39#include <WebKit/WKBundleFrame.h>
40#endif
41#include <atk/atk.h>
42#include <wtf/Assertions.h>
43#include <wtf/glib/GRefPtr.h>
44#include <wtf/glib/GUniquePtr.h>
45#include <wtf/text/CString.h>
46#include <wtf/text/StringBuilder.h>
47#include <wtf/unicode/CharacterNames.h>
48
49namespace WTR {
50
51namespace {
52
53#if ATK_CHECK_VERSION(2,11,92)
54enum RangeLimit {
55 RangeLimitMinimum,
56 RangeLimitMaximum
57};
58#endif
59
60enum AtkAttributeType {
61 ObjectAttributeType,
62 TextAttributeType
63};
64
65enum AttributesIndex {
66 // Attribute names.
67 InvalidNameIndex = 0,
68 ColumnCount,
69 ColumnIndex,
70 ColumnSpan,
71 RowCount,
72 RowIndex,
73 RowSpan,
74 PosInSetIndex,
75 SetSizeIndex,
76 PlaceholderNameIndex,
77 SortNameIndex,
78 CurrentNameIndex,
79 AriaLiveNameIndex,
80 AriaAtomicNameIndex,
81 AriaRelevantNameIndex,
82 BusyNameIndex,
83
84 // Attribute values.
85 SortAscendingValueIndex,
86 SortDescendingValueIndex,
87 SortUnknownValueIndex,
88
89 NumberOfAttributes
90};
91
92// Attribute names & Values (keep on sync with enum AttributesIndex).
93struct Attribute {
94 String coreDomain;
95 String atkDomain;
96};
97using Attributes = std::array<Attribute, NumberOfAttributes>;
98static const Attributes& attributesMap()
99{
100 static NeverDestroyed<Attributes> attributes = Attributes({
101 // Attribute names.
102 Attribute { "AXInvalid", "invalid" },
103 Attribute { "AXARIAColumnCount", "colcount" },
104 Attribute { "AXARIAColumnIndex", "colindex" },
105 Attribute { "AXARIAColumnSpan", "colspan" },
106 Attribute { "AXARIARowCount", "rowcount" },
107 Attribute { "AXARIARowIndex", "rowindex" },
108 Attribute { "AXARIARowSpan", "rowspan" },
109 Attribute { "AXARIAPosInSet", "posinset" },
110 Attribute { "AXARIASetSize", "setsize" },
111 Attribute { "AXPlaceholderValue", "placeholder-text" } ,
112 Attribute { "AXSortDirection", "sort" },
113 Attribute { "AXARIACurrent", "current" },
114 Attribute { "AXARIALive", "live" },
115 Attribute { "AXARIAAtomic", "atomic" },
116 Attribute { "AXARIARelevant", "relevant" },
117 Attribute { "AXElementBusy", "busy" },
118
119 // Attribute values.
120 Attribute { "AXAscendingSortDirection", "ascending" },
121 Attribute { "AXDescendingSortDirection", "descending" },
122 Attribute { "AXUnknownSortDirection", "unknown" },
123 });
124 return attributes.get();
125}
126
127#if ATK_CHECK_VERSION(2, 11, 3)
128const char* landmarkStringBanner = "AXLandmarkBanner";
129const char* landmarkStringComplementary = "AXLandmarkComplementary";
130const char* landmarkStringContentinfo = "AXLandmarkContentInfo";
131const char* landmarkStringForm = "AXLandmarkForm";
132const char* landmarkStringMain = "AXLandmarkMain";
133const char* landmarkStringNavigation = "AXLandmarkNavigation";
134const char* landmarkStringRegion = "AXLandmarkRegion";
135const char* landmarkStringSearch = "AXLandmarkSearch";
136#endif
137
138String jsStringToWTFString(JSStringRef attribute)
139{
140 size_t bufferSize = JSStringGetMaximumUTF8CStringSize(attribute);
141 GUniquePtr<gchar> buffer(static_cast<gchar*>(g_malloc(bufferSize)));
142 JSStringGetUTF8CString(attribute, buffer.get(), bufferSize);
143
144 return String::fromUTF8(buffer.get());
145}
146
147String coreAttributeToAtkAttribute(JSStringRef attribute)
148{
149 String attributeString = jsStringToWTFString(attribute);
150 for (int i = 0; i < NumberOfAttributes; ++i) {
151 if (attributesMap()[i].coreDomain == attributeString)
152 return attributesMap()[i].atkDomain;
153 }
154
155 return attributeString;
156}
157
158String atkAttributeValueToCoreAttributeValue(AtkAttributeType type, const String& id, const String& value)
159{
160 if (type == ObjectAttributeType) {
161 // We don't expose the "current" attribute if there is no author-provided value.
162 if (id == attributesMap()[CurrentNameIndex].atkDomain && value.isEmpty())
163 return "false";
164
165 // We need to translate ATK values exposed for 'aria-sort' (e.g. 'ascending')
166 // into those expected by the layout tests (e.g. 'AXAscendingSortDirection').
167 if (id == attributesMap()[SortNameIndex].atkDomain && !value.isEmpty()) {
168 if (value == attributesMap()[SortAscendingValueIndex].atkDomain)
169 return attributesMap()[SortAscendingValueIndex].coreDomain;
170 if (value == attributesMap()[SortDescendingValueIndex].atkDomain)
171 return attributesMap()[SortDescendingValueIndex].coreDomain;
172
173 return attributesMap()[SortUnknownValueIndex].coreDomain;
174 }
175 } else if (type == TextAttributeType) {
176 // In case of 'aria-invalid' when the attribute empty or has "false" for ATK
177 // it should not be mapped at all, but layout tests will expect 'false'.
178 if (id == attributesMap()[InvalidNameIndex].atkDomain && value.isEmpty())
179 return "false";
180 }
181
182 return value;
183}
184
185AtkAttributeSet* getAttributeSet(AtkObject* accessible, AtkAttributeType type)
186{
187 if (!accessible)
188 return nullptr;
189
190 if (type == ObjectAttributeType)
191 return atk_object_get_attributes(accessible);
192
193 if (type == TextAttributeType) {
194 if (!ATK_IS_TEXT(accessible))
195 return nullptr;
196
197 return atk_text_get_default_attributes(ATK_TEXT(accessible));
198 }
199
200 ASSERT_NOT_REACHED();
201 return nullptr;
202}
203
204String getAttributeSetValueForId(AtkObject* accessible, AtkAttributeType type, String id)
205{
206 AtkAttributeSet* attributeSet = getAttributeSet(accessible, type);
207 if (!attributeSet)
208 return String();
209
210 String attributeValue;
211 for (AtkAttributeSet* attributes = attributeSet; attributes; attributes = attributes->next) {
212 AtkAttribute* atkAttribute = static_cast<AtkAttribute*>(attributes->data);
213 if (id == atkAttribute->name) {
214 attributeValue = String::fromUTF8(atkAttribute->value);
215 break;
216 }
217 }
218 atk_attribute_set_free(attributeSet);
219
220 return atkAttributeValueToCoreAttributeValue(type, id, attributeValue);
221}
222
223String attributeSetToString(AtkAttributeSet* attributeSet, String separator=", ")
224{
225 if (!attributeSet)
226 return String();
227
228 StringBuilder builder;
229 for (AtkAttributeSet* attributes = attributeSet; attributes; attributes = attributes->next) {
230 AtkAttribute* attribute = static_cast<AtkAttribute*>(attributes->data);
231 builder.append(attribute->name);
232 builder.append(':');
233 builder.append(attribute->value);
234 if (attributes->next)
235 builder.append(separator);
236 }
237 atk_attribute_set_free(attributeSet);
238
239 return builder.toString();
240}
241
242String getAtkAttributeSetAsString(AtkObject* accessible, AtkAttributeType type, String separator=", ")
243{
244 return attributeSetToString(getAttributeSet(accessible, type), separator);
245}
246
247bool checkElementState(PlatformUIElement element, AtkStateType stateType)
248{
249 if (!ATK_IS_OBJECT(element.get()))
250 return false;
251
252 GRefPtr<AtkStateSet> stateSet = adoptGRef(atk_object_ref_state_set(ATK_OBJECT(element.get())));
253 return atk_state_set_contains_state(stateSet.get(), stateType);
254}
255
256JSStringRef indexRangeInTable(PlatformUIElement element, bool isRowRange)
257{
258 GUniquePtr<gchar> rangeString(g_strdup("{0, 0}"));
259#if ATK_CHECK_VERSION(2,11,90)
260 if (!ATK_IS_TABLE_CELL(element.get()))
261 return JSStringCreateWithUTF8CString(rangeString.get());
262#else
263 if (!ATK_IS_OBJECT(element.get()))
264 return JSStringCreateWithUTF8CString(rangeString.get());
265
266 AtkObject* axTable = atk_object_get_parent(ATK_OBJECT(element.get()));
267 if (!axTable || !ATK_IS_TABLE(axTable))
268 return JSStringCreateWithUTF8CString(rangeString.get());
269
270 // Look for the cell in the table.
271 gint indexInParent = atk_object_get_index_in_parent(ATK_OBJECT(element.get()));
272 if (indexInParent == -1)
273 return JSStringCreateWithUTF8CString(rangeString.get());
274#endif
275
276 gint row = -1;
277 gint column = -1;
278 gint rowSpan = -1;
279 gint columnSpan = -1;
280#if ATK_CHECK_VERSION(2,11,90)
281 atk_table_cell_get_row_column_span(ATK_TABLE_CELL(element.get()), &row, &column, &rowSpan, &columnSpan);
282#else
283 row = atk_table_get_row_at_index(ATK_TABLE(axTable), indexInParent);
284 column = atk_table_get_column_at_index(ATK_TABLE(axTable), indexInParent);
285 rowSpan = atk_table_get_row_extent_at(ATK_TABLE(axTable), row, column);
286 columnSpan = atk_table_get_column_extent_at(ATK_TABLE(axTable), row, column);
287#endif
288
289 // Get the actual values, if row and columns are valid values.
290 if (row != -1 && column != -1) {
291 int base = 0;
292 int length = 0;
293 if (isRowRange) {
294 base = row;
295 length = rowSpan;
296 } else {
297 base = column;
298 length = columnSpan;
299 }
300 rangeString.reset(g_strdup_printf("{%d, %d}", base, length));
301 }
302
303 return JSStringCreateWithUTF8CString(rangeString.get());
304}
305
306void alterCurrentValue(PlatformUIElement element, int factor)
307{
308 if (!ATK_IS_VALUE(element.get()))
309 return;
310
311#if ATK_CHECK_VERSION(2,11,92)
312 double currentValue;
313 atk_value_get_value_and_text(ATK_VALUE(element.get()), &currentValue, nullptr);
314
315 double increment = atk_value_get_increment(ATK_VALUE(element.get()));
316 atk_value_set_value(ATK_VALUE(element.get()), currentValue + factor * increment);
317#else
318 GValue currentValue = G_VALUE_INIT;
319 atk_value_get_current_value(ATK_VALUE(element.get()), &currentValue);
320
321 GValue increment = G_VALUE_INIT;
322 atk_value_get_minimum_increment(ATK_VALUE(element.get()), &increment);
323
324 GValue newValue = G_VALUE_INIT;
325 g_value_init(&newValue, G_TYPE_FLOAT);
326
327 g_value_set_float(&newValue, g_value_get_float(&currentValue) + factor * g_value_get_float(&increment));
328 atk_value_set_current_value(ATK_VALUE(element.get()), &newValue);
329
330 g_value_unset(&newValue);
331 g_value_unset(&increment);
332 g_value_unset(&currentValue);
333#endif
334}
335
336gchar* replaceCharactersForResults(gchar* str)
337{
338 WTF::String uString = WTF::String::fromUTF8(str);
339
340 // The object replacement character is passed along to ATs so we need to be
341 // able to test for their presence and do so without causing test failures.
342 uString.replace(objectReplacementCharacter, "<obj>");
343
344 // The presence of newline characters in accessible text of a single object
345 // is appropriate, but it makes test results (especially the accessible tree)
346 // harder to read.
347 uString.replace("\n", "<\\n>");
348
349 return g_strdup(uString.utf8().data());
350}
351
352const gchar* roleToString(AtkObject* object)
353{
354 AtkRole role = atk_object_get_role(object);
355
356#if ATK_CHECK_VERSION(2, 11, 3)
357 if (role == ATK_ROLE_LANDMARK) {
358 String xmlRolesValue = getAttributeSetValueForId(object, ObjectAttributeType, "xml-roles");
359 if (equalLettersIgnoringASCIICase(xmlRolesValue, "banner"))
360 return landmarkStringBanner;
361 if (equalLettersIgnoringASCIICase(xmlRolesValue, "complementary"))
362 return landmarkStringComplementary;
363 if (equalLettersIgnoringASCIICase(xmlRolesValue, "contentinfo"))
364 return landmarkStringContentinfo;
365 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-acknowledgments"))
366 return landmarkStringRegion;
367 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-afterword"))
368 return landmarkStringRegion;
369 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-appendix"))
370 return landmarkStringRegion;
371 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-bibliography"))
372 return landmarkStringRegion;
373 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-chapter"))
374 return landmarkStringRegion;
375 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-conclusion"))
376 return landmarkStringRegion;
377 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-credits"))
378 return landmarkStringRegion;
379 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-endnotes"))
380 return landmarkStringRegion;
381 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-epilogue"))
382 return landmarkStringRegion;
383 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-errata"))
384 return landmarkStringRegion;
385 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-foreword"))
386 return landmarkStringRegion;
387 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-glossary"))
388 return landmarkStringRegion;
389 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-glossref"))
390 return landmarkStringRegion;
391 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-index"))
392 return landmarkStringRegion;
393 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-introduction"))
394 return landmarkStringRegion;
395 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-pagelist"))
396 return landmarkStringRegion;
397 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-part"))
398 return landmarkStringRegion;
399 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-preface"))
400 return landmarkStringRegion;
401 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-prologue"))
402 return landmarkStringRegion;
403 if (equalLettersIgnoringASCIICase(xmlRolesValue, "doc-toc"))
404 return landmarkStringRegion;
405 if (equalLettersIgnoringASCIICase(xmlRolesValue, "form"))
406 return landmarkStringForm;
407 if (equalLettersIgnoringASCIICase(xmlRolesValue, "main"))
408 return landmarkStringMain;
409 if (equalLettersIgnoringASCIICase(xmlRolesValue, "navigation"))
410 return landmarkStringNavigation;
411 if (equalLettersIgnoringASCIICase(xmlRolesValue, "region"))
412 return landmarkStringRegion;
413 if (equalLettersIgnoringASCIICase(xmlRolesValue, "search"))
414 return landmarkStringSearch;
415 }
416#endif
417
418 switch (role) {
419 case ATK_ROLE_ALERT:
420 return "AXAlert";
421 case ATK_ROLE_DIALOG:
422 return "AXDialog";
423 case ATK_ROLE_CANVAS:
424 return "AXCanvas";
425 case ATK_ROLE_CAPTION:
426 return "AXCaption";
427 case ATK_ROLE_CHECK_BOX:
428 return "AXCheckBox";
429 case ATK_ROLE_COLOR_CHOOSER:
430 return "AXColorWell";
431 case ATK_ROLE_COLUMN_HEADER:
432 return "AXColumnHeader";
433 case ATK_ROLE_COMBO_BOX:
434 return "AXComboBox";
435 case ATK_ROLE_COMMENT:
436 return "AXComment";
437 case ATK_ROLE_DOCUMENT_FRAME:
438 return "AXDocument";
439 case ATK_ROLE_DOCUMENT_WEB:
440 return "AXWebArea";
441 case ATK_ROLE_EMBEDDED:
442 return "AXEmbedded";
443 case ATK_ROLE_ENTRY:
444 return "AXTextField";
445 case ATK_ROLE_FOOTER:
446 return "AXFooter";
447 case ATK_ROLE_FORM:
448 return "AXForm";
449 case ATK_ROLE_GROUPING:
450 return "AXGroup";
451 case ATK_ROLE_HEADING:
452 return "AXHeading";
453 case ATK_ROLE_IMAGE:
454 return "AXImage";
455 case ATK_ROLE_IMAGE_MAP:
456 return "AXImageMap";
457 case ATK_ROLE_INVALID:
458 return "AXInvalid";
459 case ATK_ROLE_LABEL:
460 return "AXLabel";
461 case ATK_ROLE_LEVEL_BAR:
462 return "AXLevelIndicator";
463 case ATK_ROLE_LINK:
464 return "AXLink";
465 case ATK_ROLE_LIST:
466 return "AXList";
467 case ATK_ROLE_LIST_BOX:
468 return "AXListBox";
469 case ATK_ROLE_LIST_ITEM:
470 return "AXListItem";
471 case ATK_ROLE_MENU:
472 return "AXMenu";
473 case ATK_ROLE_MENU_BAR:
474 return "AXMenuBar";
475 case ATK_ROLE_MENU_ITEM:
476 return "AXMenuItem";
477 case ATK_ROLE_PAGE_TAB:
478 return "AXTab";
479 case ATK_ROLE_PAGE_TAB_LIST:
480 return "AXTabGroup";
481 case ATK_ROLE_PANEL:
482 return "AXGroup";
483 case ATK_ROLE_PARAGRAPH:
484 return "AXParagraph";
485 case ATK_ROLE_PASSWORD_TEXT:
486 return "AXPasswordField";
487 case ATK_ROLE_PROGRESS_BAR:
488 return "AXProgressIndicator";
489 case ATK_ROLE_PUSH_BUTTON:
490 return "AXButton";
491 case ATK_ROLE_RADIO_BUTTON:
492 return "AXRadioButton";
493 case ATK_ROLE_RADIO_MENU_ITEM:
494 return "AXRadioMenuItem";
495 case ATK_ROLE_ROW_HEADER:
496 return "AXRowHeader";
497 case ATK_ROLE_CHECK_MENU_ITEM:
498 return "AXCheckMenuItem";
499 case ATK_ROLE_RULER:
500 return "AXRuler";
501 case ATK_ROLE_SCROLL_BAR:
502 return "AXScrollBar";
503 case ATK_ROLE_SCROLL_PANE:
504 return "AXScrollArea";
505 case ATK_ROLE_SECTION:
506 return "AXSection";
507 case ATK_ROLE_SEPARATOR:
508 return "AXSeparator";
509 case ATK_ROLE_SLIDER:
510 return "AXSlider";
511 case ATK_ROLE_SPIN_BUTTON:
512 return "AXSpinButton";
513 case ATK_ROLE_STATUSBAR:
514 return "AXStatusBar";
515 case ATK_ROLE_TABLE:
516 return "AXTable";
517 case ATK_ROLE_TABLE_CELL:
518 return "AXCell";
519 case ATK_ROLE_TABLE_COLUMN_HEADER:
520 return "AXColumnHeader";
521 case ATK_ROLE_TABLE_ROW:
522 return "AXRow";
523 case ATK_ROLE_TABLE_ROW_HEADER:
524 return "AXRowHeader";
525 case ATK_ROLE_TOGGLE_BUTTON:
526 return "AXToggleButton";
527 case ATK_ROLE_TOOL_BAR:
528 return "AXToolbar";
529 case ATK_ROLE_TOOL_TIP:
530 return "AXUserInterfaceTooltip";
531 case ATK_ROLE_TREE:
532 return "AXTree";
533 case ATK_ROLE_TREE_TABLE:
534 return "AXTreeGrid";
535 case ATK_ROLE_TREE_ITEM:
536 return "AXTreeItem";
537 case ATK_ROLE_WINDOW:
538 return "AXWindow";
539 case ATK_ROLE_UNKNOWN:
540 return "AXUnknown";
541#if ATK_CHECK_VERSION(2, 11, 3)
542 case ATK_ROLE_ARTICLE:
543 return "AXArticle";
544 case ATK_ROLE_AUDIO:
545 return "AXAudio";
546 case ATK_ROLE_BLOCK_QUOTE:
547 return "AXBlockquote";
548 case ATK_ROLE_DEFINITION:
549 return "AXDefinition";
550 case ATK_ROLE_LOG:
551 return "AXLog";
552 case ATK_ROLE_MARQUEE:
553 return "AXMarquee";
554 case ATK_ROLE_MATH:
555 return "AXMath";
556 case ATK_ROLE_TIMER:
557 return "AXTimer";
558 case ATK_ROLE_VIDEO:
559 return "AXVideo";
560#endif
561#if ATK_CHECK_VERSION(2, 11, 4)
562 case ATK_ROLE_DESCRIPTION_LIST:
563 return "AXDescriptionList";
564 case ATK_ROLE_DESCRIPTION_TERM:
565 return "AXDescriptionTerm";
566 case ATK_ROLE_DESCRIPTION_VALUE:
567 return "AXDescriptionValue";
568#endif
569#if ATK_CHECK_VERSION(2, 15, 2)
570 case ATK_ROLE_STATIC:
571 return "AXStatic";
572#endif
573#if ATK_CHECK_VERSION(2, 15, 4)
574 case ATK_ROLE_MATH_FRACTION:
575 return "AXMathFraction";
576 case ATK_ROLE_MATH_ROOT:
577 return "AXMathRoot";
578 case ATK_ROLE_SUBSCRIPT:
579 return "AXSubscript";
580 case ATK_ROLE_SUPERSCRIPT:
581 return "AXSuperscript";
582#endif
583#if ATK_CHECK_VERSION(2, 25, 2)
584 case ATK_ROLE_FOOTNOTE:
585 return "AXFootnote";
586#endif
587 default:
588 // We want to distinguish ATK_ROLE_UNKNOWN from a known AtkRole which
589 // our DRT isn't properly handling.
590 return "FIXME not identified";
591 }
592}
593
594String selectedText(AtkObject* accessible)
595{
596 if (!ATK_IS_TEXT(accessible))
597 return String();
598
599 AtkText* text = ATK_TEXT(accessible);
600
601 gint start, end;
602 g_free(atk_text_get_selection(text, 0, &start, &end));
603
604 return atk_text_get_text(text, start, end);
605}
606
607String attributesOfElement(AccessibilityUIElement* element)
608{
609 StringBuilder builder;
610
611 builder.append(element->role()->string());
612 builder.append('\n');
613
614 // For the parent we print its role and its name, if available.
615 builder.appendLiteral("AXParent: ");
616 RefPtr<AccessibilityUIElement> parent = element->parentElement();
617 AtkObject* atkParent = parent ? parent->platformUIElement().get() : nullptr;
618 if (atkParent) {
619 builder.append(roleToString(atkParent));
620 const char* parentName = atk_object_get_name(atkParent);
621 if (parentName && parentName[0]) {
622 builder.appendLiteral(": ");
623 builder.append(parentName);
624 }
625 } else
626 builder.appendLiteral("(null)");
627 builder.append('\n');
628
629 builder.appendLiteral("AXChildren: ");
630 builder.appendNumber(element->childrenCount());
631 builder.append('\n');
632
633 builder.appendLiteral("AXPosition: { ");
634 builder.appendFixedPrecisionNumber(element->x(), 6, KeepTrailingZeros);
635 builder.appendLiteral(", ");
636 builder.appendFixedPrecisionNumber(element->y(), 6, KeepTrailingZeros);
637 builder.appendLiteral(" }\n");
638
639 builder.appendLiteral("AXSize: { ");
640 builder.appendFixedPrecisionNumber(element->width(), 6, KeepTrailingZeros);
641 builder.appendLiteral(", ");
642 builder.appendFixedPrecisionNumber(element->height(), 6, KeepTrailingZeros);
643 builder.appendLiteral(" }\n");
644
645 String title = element->title()->string();
646 if (!title.isEmpty()) {
647 builder.append(title);
648 builder.append('\n');
649 }
650
651 String description = element->description()->string();
652 if (!description.isEmpty()) {
653 builder.append(description.utf8().data());
654 builder.append('\n');
655 }
656
657 String value = element->stringValue()->string();
658 if (!value.isEmpty()) {
659 builder.append(value);
660 builder.append('\n');
661 }
662
663 builder.appendLiteral("AXFocusable: ");
664 builder.appendNumber(element->isFocusable());
665 builder.append('\n');
666
667 builder.appendLiteral("AXFocused: ");
668 builder.appendNumber(element->isFocused());
669 builder.append('\n');
670
671 builder.appendLiteral("AXSelectable: ");
672 builder.appendNumber(element->isSelectable());
673 builder.append('\n');
674
675 builder.appendLiteral("AXSelected: ");
676 builder.appendNumber(element->isSelected());
677 builder.append('\n');
678
679 builder.appendLiteral("AXMultiSelectable: ");
680 builder.appendNumber(element->isMultiSelectable());
681 builder.append('\n');
682
683 builder.appendLiteral("AXEnabled: ");
684 builder.appendNumber(element->isEnabled());
685 builder.append('\n');
686
687 builder.appendLiteral("AXExpanded: ");
688 builder.appendNumber(element->isExpanded());
689 builder.append('\n');
690
691 builder.appendLiteral("AXRequired: ");
692 builder.appendNumber(element->isRequired());
693 builder.append('\n');
694
695 builder.appendLiteral("AXChecked: ");
696 builder.appendNumber(element->isChecked());
697 builder.append('\n');
698
699 String url = element->url()->string();
700 if (!url.isEmpty()) {
701 builder.append(url);
702 builder.append('\n');
703 }
704
705 // We append the ATK specific attributes as a single line at the end.
706 builder.appendLiteral("AXPlatformAttributes: ");
707 builder.append(getAtkAttributeSetAsString(element->platformUIElement().get(), ObjectAttributeType));
708
709 return builder.toString();
710}
711
712static JSRetainPtr<JSStringRef> createStringWithAttributes(const Vector<RefPtr<AccessibilityUIElement> >& elements)
713{
714 StringBuilder builder;
715
716 for (Vector<RefPtr<AccessibilityUIElement> >::const_iterator it = elements.begin(); it != elements.end(); ++it) {
717 builder.append(attributesOfElement(const_cast<AccessibilityUIElement*>(it->get())));
718 builder.appendLiteral("\n------------\n");
719 }
720
721 return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
722}
723
724static Vector<RefPtr<AccessibilityUIElement> > getTableRowHeaders(AtkTable* accessible)
725{
726 Vector<RefPtr<AccessibilityUIElement> > rowHeaders;
727
728 int rowsCount = atk_table_get_n_rows(accessible);
729 for (int row = 0; row < rowsCount; ++row) {
730 if (AtkObject* header = atk_table_get_row_header(accessible, row))
731 rowHeaders.append(AccessibilityUIElement::create(header));
732 }
733
734 return rowHeaders;
735}
736
737static Vector<RefPtr<AccessibilityUIElement> > getTableColumnHeaders(AtkTable* accessible)
738{
739 Vector<RefPtr<AccessibilityUIElement> > columnHeaders;
740
741 int columnsCount = atk_table_get_n_columns(accessible);
742 for (int column = 0; column < columnsCount; ++column) {
743 if (AtkObject* header = atk_table_get_column_header(accessible, column))
744 columnHeaders.append(AccessibilityUIElement::create(header));
745 }
746
747 return columnHeaders;
748}
749
750static Vector<RefPtr<AccessibilityUIElement> > getVisibleCells(AccessibilityUIElement* element)
751{
752 Vector<RefPtr<AccessibilityUIElement> > visibleCells;
753
754 AtkTable* accessible = ATK_TABLE(element->platformUIElement().get());
755 int rowsCount = atk_table_get_n_rows(accessible);
756 int columnsCount = atk_table_get_n_columns(accessible);
757
758 for (int row = 0; row < rowsCount; ++row) {
759 for (int column = 0; column < columnsCount; ++column)
760 visibleCells.append(element->cellForColumnAndRow(column, row));
761 }
762
763 return visibleCells;
764}
765
766#if ATK_CHECK_VERSION(2,11,90)
767static Vector<RefPtr<AccessibilityUIElement>> convertGPtrArrayToVector(const GPtrArray* array)
768{
769 Vector<RefPtr<AccessibilityUIElement>> cells;
770 for (guint i = 0; i < array->len; i++) {
771 if (AtkObject* atkObject = static_cast<AtkObject*>(g_ptr_array_index(array, i)))
772 cells.append(AccessibilityUIElement::create(atkObject));
773 }
774 return cells;
775}
776
777static JSValueRef convertToJSObjectArray(const Vector<RefPtr<AccessibilityUIElement>>& children)
778{
779 WKBundleFrameRef mainFrame = WKBundlePageGetMainFrame(InjectedBundle::singleton().page()->page());
780 JSContextRef context = WKBundleFrameGetJavaScriptContext(mainFrame);
781
782 size_t elementCount = children.size();
783 auto valueElements = std::make_unique<JSValueRef[]>(elementCount);
784 for (size_t i = 0; i < elementCount; i++)
785 valueElements[i] = JSObjectMake(context, children[i]->wrapperClass(), children[i].get());
786
787 return JSObjectMakeArray(context, elementCount, valueElements.get(), nullptr);
788}
789#endif
790
791#if ATK_CHECK_VERSION(2,11,92)
792static double rangeMinMaxValue(AtkValue* atkValue, RangeLimit rangeLimit)
793{
794 AtkRange* range = atk_value_get_range(atkValue);
795 if (!range)
796 return 0;
797
798 double rangeValue = 0;
799 switch (rangeLimit) {
800 case RangeLimitMinimum:
801 rangeValue = atk_range_get_lower_limit(range);
802 break;
803 case RangeLimitMaximum:
804 rangeValue = atk_range_get_upper_limit(range);
805 break;
806 };
807
808 atk_range_free(range);
809 return rangeValue;
810}
811#endif
812
813} // namespace
814
815AccessibilityUIElement::AccessibilityUIElement(PlatformUIElement element)
816 : m_element(element)
817{
818}
819
820AccessibilityUIElement::AccessibilityUIElement(const AccessibilityUIElement& other)
821 : JSWrappable()
822 , m_element(other.m_element)
823{
824}
825
826AccessibilityUIElement::~AccessibilityUIElement()
827{
828}
829
830bool AccessibilityUIElement::isEqual(AccessibilityUIElement* otherElement)
831{
832 return m_element == otherElement->platformUIElement();
833}
834
835void AccessibilityUIElement::getChildren(Vector<RefPtr<AccessibilityUIElement> >& children)
836{
837 if (!ATK_IS_OBJECT(m_element.get()))
838 return;
839
840 int count = childrenCount();
841 for (int i = 0; i < count; i++) {
842 GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(m_element.get()), i));
843 children.append(AccessibilityUIElement::create(child.get()));
844 }
845}
846
847void AccessibilityUIElement::getChildrenWithRange(Vector<RefPtr<AccessibilityUIElement> >& children, unsigned location, unsigned length)
848{
849 if (!ATK_IS_OBJECT(m_element.get()))
850 return;
851 unsigned end = location + length;
852 for (unsigned i = location; i < end; i++) {
853 GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(m_element.get()), i));
854 children.append(AccessibilityUIElement::create(child.get()));
855 }
856}
857
858int AccessibilityUIElement::childrenCount()
859{
860 if (!ATK_IS_OBJECT(m_element.get()))
861 return 0;
862
863 return atk_object_get_n_accessible_children(ATK_OBJECT(m_element.get()));
864}
865
866RefPtr<AccessibilityUIElement> AccessibilityUIElement::elementAtPoint(int x, int y)
867{
868 if (!ATK_IS_COMPONENT(m_element.get()))
869 return nullptr;
870
871 GRefPtr<AtkObject> objectAtPoint = adoptGRef(atk_component_ref_accessible_at_point(ATK_COMPONENT(m_element.get()), x, y, ATK_XY_WINDOW));
872 return AccessibilityUIElement::create(objectAtPoint ? objectAtPoint.get() : m_element.get());
873}
874
875unsigned AccessibilityUIElement::indexOfChild(AccessibilityUIElement* element)
876{
877 if (!ATK_IS_OBJECT(m_element.get()))
878 return 0;
879
880 Vector<RefPtr<AccessibilityUIElement> > children;
881 getChildren(children);
882
883 unsigned count = children.size();
884 for (unsigned i = 0; i < count; i++)
885 if (children[i]->isEqual(element))
886 return i;
887
888 return 0;
889}
890
891RefPtr<AccessibilityUIElement> AccessibilityUIElement::childAtIndex(unsigned index)
892{
893 if (!ATK_IS_OBJECT(m_element.get()))
894 return nullptr;
895
896 Vector<RefPtr<AccessibilityUIElement> > children;
897 getChildrenWithRange(children, index, 1);
898
899 if (children.size() == 1)
900 return children[0];
901
902 return nullptr;
903}
904
905static RefPtr<AccessibilityUIElement> accessibilityElementAtIndex(AtkObject* element, AtkRelationType relationType, unsigned index)
906{
907 if (!ATK_IS_OBJECT(element))
908 return nullptr;
909
910 AtkRelationSet* relationSet = atk_object_ref_relation_set(element);
911 if (!relationSet)
912 return nullptr;
913
914 AtkRelation* relation = atk_relation_set_get_relation_by_type(relationSet, relationType);
915 if (!relation)
916 return nullptr;
917
918 GPtrArray* targetList = atk_relation_get_target(relation);
919 if (!targetList || !targetList->len || index >= targetList->len)
920 return nullptr;
921
922 AtkObject* target = static_cast<AtkObject*>(g_ptr_array_index(targetList, index));
923 g_object_unref(relationSet);
924
925 return target ? AccessibilityUIElement::create(target).ptr() : nullptr;
926}
927
928RefPtr<AccessibilityUIElement> AccessibilityUIElement::linkedUIElementAtIndex(unsigned index)
929{
930 // FIXME: implement
931 return nullptr;
932}
933
934RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsElementAtIndex(unsigned index)
935{
936 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_NODE_PARENT_OF, index);
937}
938
939RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaOwnsReferencingElementAtIndex(unsigned index)
940{
941 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_NODE_CHILD_OF, index);
942}
943
944RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToElementAtIndex(unsigned index)
945{
946 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_FLOWS_TO, index);
947}
948
949RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaFlowToReferencingElementAtIndex(unsigned index)
950{
951 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_FLOWS_FROM, index);
952}
953
954RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsElementAtIndex(unsigned index)
955{
956 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_CONTROLLER_FOR, index);
957}
958
959RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaControlsReferencingElementAtIndex(unsigned index)
960{
961 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_CONTROLLED_BY, index);
962}
963
964RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByElementAtIndex(unsigned index)
965{
966 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_LABELLED_BY, index);
967}
968
969RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaLabelledByReferencingElementAtIndex(unsigned index)
970{
971 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_LABEL_FOR, index);
972}
973
974RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByElementAtIndex(unsigned index)
975{
976 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DESCRIBED_BY, index);
977}
978
979RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDescribedByReferencingElementAtIndex(unsigned index)
980{
981 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DESCRIPTION_FOR, index);
982}
983
984RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsElementAtIndex(unsigned index)
985{
986#if ATK_CHECK_VERSION(2, 25, 2)
987 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DETAILS, index);
988#endif
989 return nullptr;
990}
991
992RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaDetailsReferencingElementAtIndex(unsigned index)
993{
994#if ATK_CHECK_VERSION(2, 25, 2)
995 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_DETAILS_FOR, index);
996#endif
997 return nullptr;
998}
999
1000RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageElementAtIndex(unsigned index)
1001{
1002#if ATK_CHECK_VERSION(2, 25, 2)
1003 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_ERROR_MESSAGE, index);
1004#endif
1005 return nullptr;
1006}
1007
1008RefPtr<AccessibilityUIElement> AccessibilityUIElement::ariaErrorMessageReferencingElementAtIndex(unsigned index)
1009{
1010#if ATK_CHECK_VERSION(2, 25, 2)
1011 return accessibilityElementAtIndex(m_element.get(), ATK_RELATION_ERROR_FOR, index);
1012#endif
1013 return nullptr;
1014}
1015
1016RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedRowAtIndex(unsigned index)
1017{
1018 // FIXME: implement
1019 return nullptr;
1020}
1021
1022RefPtr<AccessibilityUIElement> AccessibilityUIElement::rowAtIndex(unsigned index)
1023{
1024 // ATK doesn't have API to get an accessible row by index directly. It does, however, have
1025 // API to get cells in the row specified by index. The parent of a cell should be the row.
1026 AtkTable* axTable = ATK_TABLE(m_element.get());
1027 unsigned nColumns = columnCount();
1028 for (unsigned col = 0; col < nColumns; col++) {
1029 // Find the first cell in this row that only spans one row.
1030 if (atk_table_get_row_extent_at(axTable, index, col) == 1) {
1031 AtkObject* cell = atk_table_ref_at(axTable, index, col);
1032 return cell ? AccessibilityUIElement::create(atk_object_get_parent(cell)).ptr() : nullptr;
1033 }
1034 }
1035
1036 return nullptr;
1037}
1038
1039RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedChildAtIndex(unsigned index) const
1040{
1041 if (!ATK_SELECTION(m_element.get()))
1042 return nullptr;
1043
1044 GRefPtr<AtkObject> child = adoptGRef(atk_selection_ref_selection(ATK_SELECTION(m_element.get()), index));
1045 return child ? AccessibilityUIElement::create(child.get()).ptr() : nullptr;
1046}
1047
1048unsigned AccessibilityUIElement::selectedChildrenCount() const
1049{
1050 if (!ATK_IS_SELECTION(m_element.get()))
1051 return 0;
1052 return atk_selection_get_selection_count(ATK_SELECTION(m_element.get()));
1053}
1054
1055RefPtr<AccessibilityUIElement> AccessibilityUIElement::selectedRowAtIndex(unsigned index)
1056{
1057 // FIXME: implement
1058 return nullptr;
1059}
1060
1061RefPtr<AccessibilityUIElement> AccessibilityUIElement::titleUIElement()
1062{
1063 if (!ATK_IS_OBJECT(m_element.get()))
1064 return nullptr;
1065
1066 AtkRelationSet* set = atk_object_ref_relation_set(ATK_OBJECT(m_element.get()));
1067 if (!set)
1068 return nullptr;
1069
1070 AtkObject* target = nullptr;
1071 int count = atk_relation_set_get_n_relations(set);
1072 for (int i = 0; i < count; i++) {
1073 AtkRelation* relation = atk_relation_set_get_relation(set, i);
1074 if (atk_relation_get_relation_type(relation) == ATK_RELATION_LABELLED_BY) {
1075 GPtrArray* targetList = atk_relation_get_target(relation);
1076 if (targetList->len)
1077 target = static_cast<AtkObject*>(g_ptr_array_index(targetList, 0));
1078 }
1079 }
1080
1081 g_object_unref(set);
1082 return target ? AccessibilityUIElement::create(target).ptr() : nullptr;
1083}
1084
1085RefPtr<AccessibilityUIElement> AccessibilityUIElement::parentElement()
1086{
1087 if (!ATK_IS_OBJECT(m_element.get()))
1088 return nullptr;
1089
1090 AtkObject* parent = atk_object_get_parent(ATK_OBJECT(m_element.get()));
1091 return parent ? AccessibilityUIElement::create(parent).ptr() : nullptr;
1092}
1093
1094RefPtr<AccessibilityUIElement> AccessibilityUIElement::disclosedByRow()
1095{
1096 // FIXME: implement
1097 return nullptr;
1098}
1099
1100JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfLinkedUIElements()
1101{
1102 // FIXME: implement
1103 return JSStringCreateWithCharacters(0, 0);
1104}
1105
1106JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfDocumentLinks()
1107{
1108 // FIXME: implement
1109 return JSStringCreateWithCharacters(0, 0);
1110}
1111
1112JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfChildren()
1113{
1114 if (!ATK_IS_OBJECT(m_element.get()))
1115 return JSStringCreateWithCharacters(0, 0);
1116
1117 Vector<RefPtr<AccessibilityUIElement> > children;
1118 getChildren(children);
1119
1120 return createStringWithAttributes(children);
1121}
1122
1123JSRetainPtr<JSStringRef> AccessibilityUIElement::allAttributes()
1124{
1125 if (!ATK_IS_OBJECT(m_element.get()))
1126 return JSStringCreateWithCharacters(0, 0);
1127
1128 return JSStringCreateWithUTF8CString(attributesOfElement(this).utf8().data());
1129}
1130
1131JSRetainPtr<JSStringRef> AccessibilityUIElement::stringDescriptionOfAttributeValue(JSStringRef attribute)
1132{
1133 // FIXME: implement
1134 return JSStringCreateWithCharacters(0, 0);
1135}
1136
1137JSRetainPtr<JSStringRef> AccessibilityUIElement::stringAttributeValue(JSStringRef attribute)
1138{
1139 if (!ATK_IS_OBJECT(m_element.get()))
1140 return JSStringCreateWithCharacters(0, 0);
1141
1142 String atkAttributeName = coreAttributeToAtkAttribute(attribute);
1143
1144 // The value of AXSelectedText is not exposed through any AtkAttribute.
1145 if (atkAttributeName == "AXSelectedText") {
1146 String string = selectedText(m_element.get());
1147 return JSStringCreateWithUTF8CString(string.utf8().data());
1148 }
1149
1150 // Try object attributes before text attributes.
1151 String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
1152
1153 // Try text attributes if the requested one was not found and we have an AtkText object.
1154 if (attributeValue.isEmpty() && ATK_IS_TEXT(m_element.get()))
1155 attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), TextAttributeType, atkAttributeName);
1156
1157 // Additional check to make sure that the exposure of the state ATK_STATE_INVALID_ENTRY
1158 // is consistent with the exposure of aria-invalid as a text attribute, if present.
1159 if (atkAttributeName == attributesMap()[InvalidNameIndex].atkDomain) {
1160 bool isInvalidState = checkElementState(m_element.get(), ATK_STATE_INVALID_ENTRY);
1161 if (attributeValue.isEmpty())
1162 return JSStringCreateWithUTF8CString(isInvalidState ? "true" : "false");
1163
1164 // If the text attribute was there, check that it's consistent with
1165 // what the state says or force the test to fail otherwise.
1166 bool isAriaInvalid = attributeValue != "false";
1167 if (isInvalidState != isAriaInvalid)
1168 return JSStringCreateWithCharacters(0, 0);
1169 }
1170
1171 return JSStringCreateWithUTF8CString(attributeValue.utf8().data());
1172}
1173
1174double AccessibilityUIElement::numberAttributeValue(JSStringRef attribute)
1175{
1176 if (!ATK_IS_OBJECT(m_element.get()))
1177 return 0;
1178
1179 String atkAttributeName = coreAttributeToAtkAttribute(attribute);
1180 if (atkAttributeName.isEmpty())
1181 return 0;
1182
1183 if (atkAttributeName == "setsize" || atkAttributeName == "posinset") {
1184 String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
1185 if (!attributeValue.isEmpty())
1186 return attributeValue.toDouble();
1187 }
1188
1189 if (atkAttributeName.startsWith("row") || atkAttributeName.startsWith("col")) {
1190 String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
1191 if (!attributeValue.isEmpty())
1192 return attributeValue.toInt();
1193 }
1194
1195 return 0;
1196}
1197
1198JSValueRef AccessibilityUIElement::uiElementArrayAttributeValue(JSStringRef attribute) const
1199{
1200 // FIXME: implement
1201 return nullptr;
1202}
1203
1204JSValueRef AccessibilityUIElement::rowHeaders() const
1205{
1206 if (ATK_IS_TABLE(m_element.get()))
1207 return convertToJSObjectArray(getTableRowHeaders(ATK_TABLE(m_element.get())));
1208
1209 Vector<RefPtr<AccessibilityUIElement>> headers;
1210 if (!ATK_IS_TABLE_CELL(m_element.get()))
1211 return convertToJSObjectArray(headers);
1212
1213#if ATK_CHECK_VERSION(2,11,90)
1214 if (GRefPtr<GPtrArray> array = adoptGRef(atk_table_cell_get_row_header_cells(ATK_TABLE_CELL(m_element.get()))))
1215 headers = convertGPtrArrayToVector(array.get());
1216#endif
1217 return convertToJSObjectArray(headers);
1218}
1219
1220JSValueRef AccessibilityUIElement::columnHeaders() const
1221{
1222 if (ATK_IS_TABLE(m_element.get()))
1223 return convertToJSObjectArray(getTableColumnHeaders(ATK_TABLE(m_element.get())));
1224
1225 Vector<RefPtr<AccessibilityUIElement>> headers;
1226 if (!ATK_IS_TABLE_CELL(m_element.get()))
1227 return convertToJSObjectArray(headers);
1228
1229#if ATK_CHECK_VERSION(2,11,90)
1230 if (GRefPtr<GPtrArray> array = adoptGRef(atk_table_cell_get_column_header_cells(ATK_TABLE_CELL(m_element.get()))))
1231 headers = convertGPtrArrayToVector(array.get());
1232#endif
1233 return convertToJSObjectArray(headers);
1234}
1235
1236RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementAttributeValue(JSStringRef attribute) const
1237{
1238 if (!ATK_IS_OBJECT(m_element.get()))
1239 return nullptr;
1240
1241 // ATK does not have this API. So we're "faking it" here on a case-by-case basis.
1242 String attributeString = jsStringToWTFString(attribute);
1243 AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
1244 if (role == ATK_ROLE_SPIN_BUTTON && const_cast<AccessibilityUIElement*>(this)->childrenCount() == 2) {
1245 if (attributeString == "AXDecrementButton")
1246 return const_cast<AccessibilityUIElement*>(this)->childAtIndex(0);
1247 if (attributeString == "AXIncrementButton")
1248 return const_cast<AccessibilityUIElement*>(this)->childAtIndex(1);
1249 }
1250
1251 return nullptr;
1252}
1253
1254bool AccessibilityUIElement::boolAttributeValue(JSStringRef attribute)
1255{
1256 if (!ATK_IS_OBJECT(m_element.get()))
1257 return false;
1258
1259 String attributeString = jsStringToWTFString(attribute);
1260 if (attributeString == "AXElementBusy")
1261 return checkElementState(m_element.get(), ATK_STATE_BUSY);
1262 if (attributeString == "AXChecked")
1263 return checkElementState(m_element.get(), ATK_STATE_CHECKED);
1264 if (attributeString == "AXEnabled")
1265 return checkElementState(m_element.get(), ATK_STATE_ENABLED);
1266 if (attributeString == "AXExpanded")
1267 return checkElementState(m_element.get(), ATK_STATE_EXPANDED);
1268 if (attributeString == "AXFocused")
1269 return checkElementState(m_element.get(), ATK_STATE_FOCUSED);
1270 if (attributeString == "AXInvalid")
1271 return checkElementState(m_element.get(), ATK_STATE_INVALID);
1272 if (attributeString == "AXModal")
1273 return checkElementState(m_element.get(), ATK_STATE_MODAL);
1274 if (attributeString == "AXMultiSelectable")
1275 return checkElementState(m_element.get(), ATK_STATE_MULTISELECTABLE);
1276 if (attributeString == "AXRequired")
1277 return checkElementState(m_element.get(), ATK_STATE_REQUIRED);
1278 if (attributeString == "AXSelected")
1279 return checkElementState(m_element.get(), ATK_STATE_SELECTED);
1280 if (attributeString == "AXSupportsAutoCompletion")
1281 return checkElementState(m_element.get(), ATK_STATE_SUPPORTS_AUTOCOMPLETION);
1282 if (attributeString == "AXVisited")
1283 return checkElementState(m_element.get(), ATK_STATE_VISITED);
1284
1285 if (attributeString == "AXInterfaceTable")
1286 return ATK_IS_TABLE(m_element.get());
1287 if (attributeString == "AXInterfaceTableCell")
1288 return ATK_IS_TABLE_CELL(m_element.get());
1289
1290 if (attributeString == "AXARIAAtomic") {
1291 String atkAttribute = coreAttributeToAtkAttribute(attribute);
1292 return getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttribute) == "true";
1293 }
1294
1295 return false;
1296}
1297
1298bool AccessibilityUIElement::isAttributeSettable(JSStringRef attribute)
1299{
1300 if (!ATK_IS_OBJECT(m_element.get()))
1301 return false;
1302
1303 String attributeString = jsStringToWTFString(attribute);
1304 if (attributeString != "AXValue")
1305 return false;
1306
1307 // ATK does not have a single state or property to indicate whether or not the value
1308 // of an accessible object can be set. ATs look at several states and properties based
1309 // on the type of object. If nothing explicitly indicates the value can or cannot be
1310 // set, ATs make role- and interface-based decisions. We'll do something similar here.
1311
1312 // This state is expected to be present only for text widgets and contenteditable elements.
1313 if (checkElementState(m_element.get(), ATK_STATE_EDITABLE))
1314 return true;
1315
1316#if ATK_CHECK_VERSION(2,11,2)
1317 // This state is applicable to checkboxes, radiobuttons, switches, etc.
1318 if (checkElementState(m_element.get(), ATK_STATE_CHECKABLE))
1319 return true;
1320#endif
1321
1322#if ATK_CHECK_VERSION(2,15,3)
1323 // This state is expected to be present only for controls and only if explicitly set.
1324 if (checkElementState(m_element.get(), ATK_STATE_READ_ONLY))
1325 return false;
1326#endif
1327
1328 // We expose an object attribute to ATs when there is an author-provided ARIA property
1329 // and also when there is a supported ARIA role but no author-provided value.
1330 String isReadOnly = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "readonly");
1331 if (!isReadOnly.isEmpty())
1332 return isReadOnly == "true" ? false : true;
1333
1334 // If we have a native listbox or combobox and the value can be set, the options should
1335 // have ATK_STATE_SELECTABLE.
1336 AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
1337 if (role == ATK_ROLE_LIST_BOX || role == ATK_ROLE_COMBO_BOX) {
1338 if (GRefPtr<AtkObject> child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(m_element.get()), 0))) {
1339 if (atk_object_get_role(ATK_OBJECT(child.get())) == ATK_ROLE_MENU)
1340 child = adoptGRef(atk_object_ref_accessible_child(ATK_OBJECT(child.get()), 0));
1341 return child && checkElementState(child.get(), ATK_STATE_SELECTABLE);
1342 }
1343 }
1344
1345 // If we have a native element which exposes a range whose value can be set, it should
1346 // be focusable and have a true range.
1347 if (ATK_IS_VALUE(m_element.get()) && checkElementState(m_element.get(), ATK_STATE_FOCUSABLE))
1348 return minValue() != maxValue();
1349
1350 return false;
1351}
1352
1353bool AccessibilityUIElement::isAttributeSupported(JSStringRef attribute)
1354{
1355 if (!ATK_IS_OBJECT(m_element.get()))
1356 return false;
1357
1358 String atkAttributeName = coreAttributeToAtkAttribute(attribute);
1359 if (atkAttributeName.isEmpty())
1360 return false;
1361
1362 // In ATK, "busy" is a state and is supported on all AtkObject instances.
1363 if (atkAttributeName == "busy")
1364 return true;
1365
1366 // For now, an attribute is supported whether it's exposed as a object or a text attribute.
1367 String attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, atkAttributeName);
1368 if (attributeValue.isEmpty())
1369 attributeValue = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), TextAttributeType, atkAttributeName);
1370
1371 // When the aria-live value is "off", we expose that value via the "live" object attribute.
1372 if (atkAttributeName == "live" && attributeValue == "off")
1373 return false;
1374
1375 return !attributeValue.isEmpty();
1376}
1377
1378JSRetainPtr<JSStringRef> AccessibilityUIElement::parameterizedAttributeNames()
1379{
1380 // FIXME: implement
1381 return JSStringCreateWithCharacters(0, 0);
1382}
1383
1384JSRetainPtr<JSStringRef> AccessibilityUIElement::role()
1385{
1386 if (!ATK_IS_OBJECT(m_element.get()))
1387 return JSStringCreateWithCharacters(0, 0);
1388
1389 GUniquePtr<char> roleStringWithPrefix(g_strdup_printf("AXRole: %s", roleToString(ATK_OBJECT(m_element.get()))));
1390 return JSStringCreateWithUTF8CString(roleStringWithPrefix.get());
1391}
1392
1393JSRetainPtr<JSStringRef> AccessibilityUIElement::subrole()
1394{
1395 // FIXME: implement
1396 return JSStringCreateWithCharacters(0, 0);
1397}
1398
1399JSRetainPtr<JSStringRef> AccessibilityUIElement::roleDescription()
1400{
1401 String roleDescription = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "roledescription");
1402 GUniquePtr<gchar> axRoleDescription(g_strdup_printf("AXRoleDescription: %s", roleDescription.utf8().data()));
1403
1404 return JSStringCreateWithUTF8CString(axRoleDescription.get());
1405}
1406
1407JSRetainPtr<JSStringRef> AccessibilityUIElement::computedRoleString()
1408{
1409 String role = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "computed-role");
1410 if (!role.isEmpty())
1411 return JSStringCreateWithUTF8CString(role.utf8().data());
1412
1413 return JSStringCreateWithCharacters(0, 0);
1414}
1415
1416JSRetainPtr<JSStringRef> AccessibilityUIElement::title()
1417{
1418 if (!ATK_IS_OBJECT(m_element.get()))
1419 return JSStringCreateWithCharacters(0, 0);
1420
1421 const gchar* name = atk_object_get_name(ATK_OBJECT(m_element.get()));
1422 GUniquePtr<gchar> axTitle(g_strdup_printf("AXTitle: %s", name ? name : ""));
1423
1424 return JSStringCreateWithUTF8CString(axTitle.get());
1425}
1426
1427JSRetainPtr<JSStringRef> AccessibilityUIElement::description()
1428{
1429 if (!ATK_IS_OBJECT(m_element.get()))
1430 return JSStringCreateWithCharacters(0, 0);
1431
1432 const gchar* description = atk_object_get_description(ATK_OBJECT(m_element.get()));
1433 if (!description)
1434 return JSStringCreateWithCharacters(0, 0);
1435
1436 GUniquePtr<gchar> axDesc(g_strdup_printf("AXDescription: %s", description));
1437
1438 return JSStringCreateWithUTF8CString(axDesc.get());
1439}
1440
1441JSRetainPtr<JSStringRef> AccessibilityUIElement::orientation() const
1442{
1443 if (!ATK_IS_OBJECT(m_element.get()))
1444 return JSStringCreateWithCharacters(0, 0);
1445
1446 const gchar* axOrientation = nullptr;
1447 if (checkElementState(m_element.get(), ATK_STATE_HORIZONTAL))
1448 axOrientation = "AXOrientation: AXHorizontalOrientation";
1449 else if (checkElementState(m_element.get(), ATK_STATE_VERTICAL))
1450 axOrientation = "AXOrientation: AXVerticalOrientation";
1451 else
1452 axOrientation = "AXOrientation: AXUnknownOrientation";
1453
1454 return JSStringCreateWithUTF8CString(axOrientation);
1455}
1456
1457JSRetainPtr<JSStringRef> AccessibilityUIElement::stringValue()
1458{
1459 if (!ATK_IS_TEXT(m_element.get()))
1460 return JSStringCreateWithCharacters(0, 0);
1461
1462 GUniquePtr<gchar> text(atk_text_get_text(ATK_TEXT(m_element.get()), 0, -1));
1463 GUniquePtr<gchar> textWithReplacedCharacters(replaceCharactersForResults(text.get()));
1464 GUniquePtr<gchar> axValue(g_strdup_printf("AXValue: %s", textWithReplacedCharacters.get()));
1465
1466 return JSStringCreateWithUTF8CString(axValue.get());
1467}
1468
1469JSRetainPtr<JSStringRef> AccessibilityUIElement::language()
1470{
1471 if (!ATK_IS_OBJECT(m_element.get()))
1472 return JSStringCreateWithCharacters(0, 0);
1473
1474 const gchar* locale = atk_object_get_object_locale(ATK_OBJECT(m_element.get()));
1475 if (!locale)
1476 return JSStringCreateWithCharacters(0, 0);
1477
1478 GUniquePtr<char> axValue(g_strdup_printf("AXLanguage: %s", locale));
1479 return JSStringCreateWithUTF8CString(axValue.get());
1480}
1481
1482JSRetainPtr<JSStringRef> AccessibilityUIElement::helpText() const
1483{
1484 if (!ATK_IS_OBJECT(m_element.get()))
1485 return JSStringCreateWithCharacters(0, 0);
1486
1487 AtkRelationSet* relationSet = atk_object_ref_relation_set(ATK_OBJECT(m_element.get()));
1488 if (!relationSet)
1489 return JSStringCreateWithCharacters(0, 0);
1490
1491 AtkRelation* relation = atk_relation_set_get_relation_by_type(relationSet, ATK_RELATION_DESCRIBED_BY);
1492 if (!relation)
1493 return JSStringCreateWithCharacters(0, 0);
1494
1495 GPtrArray* targetList = atk_relation_get_target(relation);
1496 if (!targetList || !targetList->len)
1497 return JSStringCreateWithCharacters(0, 0);
1498
1499 StringBuilder builder;
1500 builder.appendLiteral("AXHelp: ");
1501
1502 for (guint targetCount = 0; targetCount < targetList->len; targetCount++) {
1503 if (AtkObject* target = static_cast<AtkObject*>(g_ptr_array_index(targetList, targetCount))) {
1504 GUniquePtr<gchar> text(atk_text_get_text(ATK_TEXT(target), 0, -1));
1505 if (targetCount)
1506 builder.append(' ');
1507 builder.append(text.get());
1508 }
1509 }
1510
1511 g_object_unref(relationSet);
1512
1513 return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
1514}
1515
1516double AccessibilityUIElement::x()
1517{
1518 if (!ATK_IS_COMPONENT(m_element.get()))
1519 return 0;
1520
1521 int x;
1522#if ATK_CHECK_VERSION(2,11,90)
1523 atk_component_get_extents(ATK_COMPONENT(m_element.get()), &x, nullptr, nullptr, nullptr, ATK_XY_SCREEN);
1524#else
1525 atk_component_get_position(ATK_COMPONENT(m_element.get()), &x, nullptr, ATK_XY_SCREEN);
1526#endif
1527 return x;
1528}
1529
1530double AccessibilityUIElement::y()
1531{
1532 if (!ATK_IS_COMPONENT(m_element.get()))
1533 return 0;
1534
1535 int y;
1536#if ATK_CHECK_VERSION(2,11,90)
1537 atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, &y, nullptr, nullptr, ATK_XY_SCREEN);
1538#else
1539 atk_component_get_position(ATK_COMPONENT(m_element.get()), nullptr, &y, ATK_XY_SCREEN);
1540#endif
1541 return y;
1542}
1543
1544double AccessibilityUIElement::width()
1545{
1546 if (!ATK_IS_COMPONENT(m_element.get()))
1547 return 0;
1548
1549 int width;
1550#if ATK_CHECK_VERSION(2,11,90)
1551 atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, nullptr, &width, nullptr, ATK_XY_WINDOW);
1552#else
1553 atk_component_get_size(ATK_COMPONENT(m_element.get()), &width, nullptr);
1554#endif
1555 return width;
1556}
1557
1558double AccessibilityUIElement::height()
1559{
1560 if (!ATK_IS_COMPONENT(m_element.get()))
1561 return 0;
1562
1563 int height;
1564#if ATK_CHECK_VERSION(2,11,90)
1565 atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, nullptr, nullptr, &height, ATK_XY_WINDOW);
1566#else
1567 atk_component_get_size(ATK_COMPONENT(m_element.get()), nullptr, &height);
1568#endif
1569 return height;
1570}
1571
1572double AccessibilityUIElement::clickPointX()
1573{
1574 if (!ATK_IS_COMPONENT(m_element.get()))
1575 return 0;
1576
1577 int x, width;
1578#if ATK_CHECK_VERSION(2,11,90)
1579 atk_component_get_extents(ATK_COMPONENT(m_element.get()), &x, nullptr, &width, nullptr, ATK_XY_WINDOW);
1580#else
1581 atk_component_get_position(ATK_COMPONENT(m_element.get()), &x, nullptr, ATK_XY_WINDOW);
1582 atk_component_get_size(ATK_COMPONENT(m_element.get()), &width, nullptr);
1583#endif
1584
1585 return x + width / 2.0;
1586}
1587
1588double AccessibilityUIElement::clickPointY()
1589{
1590 if (!ATK_IS_COMPONENT(m_element.get()))
1591 return 0;
1592
1593 int y, height;
1594#if ATK_CHECK_VERSION(2,11,90)
1595 atk_component_get_extents(ATK_COMPONENT(m_element.get()), nullptr, &y, nullptr, &height, ATK_XY_WINDOW);
1596#else
1597 atk_component_get_position(ATK_COMPONENT(m_element.get()), nullptr, &y, ATK_XY_WINDOW);
1598 atk_component_get_size(ATK_COMPONENT(m_element.get()), nullptr, &height);
1599#endif
1600
1601 return y + height / 2.0;
1602}
1603
1604double AccessibilityUIElement::intValue() const
1605{
1606 if (!ATK_IS_OBJECT(m_element.get()))
1607 return 0;
1608
1609 if (ATK_IS_VALUE(m_element.get())) {
1610#if ATK_CHECK_VERSION(2,11,92)
1611 double value;
1612 atk_value_get_value_and_text(ATK_VALUE(m_element.get()), &value, nullptr);
1613 return value;
1614#else
1615 GValue value = G_VALUE_INIT;
1616 atk_value_get_current_value(ATK_VALUE(m_element.get()), &value);
1617 if (!G_VALUE_HOLDS_FLOAT(&value))
1618 return 0;
1619 return g_value_get_float(&value);
1620#endif
1621 }
1622
1623 // Consider headings as an special case when returning the "int value" of
1624 // an AccessibilityUIElement, so we can reuse some tests to check the level
1625 // both for HTML headings and objects with the aria-level attribute.
1626 if (atk_object_get_role(ATK_OBJECT(m_element.get())) == ATK_ROLE_HEADING) {
1627 String headingLevel = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "level");
1628 bool ok;
1629 double headingLevelValue = headingLevel.toDouble(&ok);
1630 if (ok)
1631 return headingLevelValue;
1632 }
1633
1634 return 0;
1635}
1636
1637double AccessibilityUIElement::minValue()
1638{
1639 if (!ATK_IS_VALUE(m_element.get()))
1640 return 0;
1641#if ATK_CHECK_VERSION(2,11,92)
1642 return rangeMinMaxValue(ATK_VALUE(m_element.get()), RangeLimitMinimum);
1643#else
1644 GValue value = G_VALUE_INIT;
1645 atk_value_get_minimum_value(ATK_VALUE(m_element.get()), &value);
1646 if (!G_VALUE_HOLDS_FLOAT(&value))
1647 return 0;
1648
1649 return g_value_get_float(&value);
1650#endif
1651}
1652
1653double AccessibilityUIElement::maxValue()
1654{
1655 if (!ATK_IS_VALUE(m_element.get()))
1656 return 0;
1657
1658#if ATK_CHECK_VERSION(2,11,92)
1659 return rangeMinMaxValue(ATK_VALUE(m_element.get()), RangeLimitMaximum);
1660#else
1661 GValue value = G_VALUE_INIT;
1662 atk_value_get_maximum_value(ATK_VALUE(m_element.get()), &value);
1663 if (!G_VALUE_HOLDS_FLOAT(&value))
1664 return 0;
1665
1666 return g_value_get_float(&value);
1667#endif
1668}
1669
1670JSRetainPtr<JSStringRef> AccessibilityUIElement::valueDescription()
1671{
1672 String valueText = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "valuetext");
1673 GUniquePtr<gchar> valueDescription(g_strdup_printf("AXValueDescription: %s", valueText.utf8().data()));
1674 return JSStringCreateWithUTF8CString(valueDescription.get());
1675}
1676
1677int AccessibilityUIElement::insertionPointLineNumber()
1678{
1679 // FIXME: implement
1680 return -1;
1681}
1682
1683bool AccessibilityUIElement::isPressActionSupported()
1684{
1685 if (!ATK_IS_ACTION(m_element.get()))
1686 return false;
1687
1688 const gchar* actionName = atk_action_get_name(ATK_ACTION(m_element.get()), 0);
1689 return equalLettersIgnoringASCIICase(String(actionName), "press") || equalLettersIgnoringASCIICase(String(actionName), "jump");
1690}
1691
1692bool AccessibilityUIElement::isIncrementActionSupported()
1693{
1694 // FIXME: implement
1695 return false;
1696}
1697
1698bool AccessibilityUIElement::isDecrementActionSupported()
1699{
1700 // FIXME: implement
1701 return false;
1702}
1703
1704bool AccessibilityUIElement::isEnabled()
1705{
1706 return checkElementState(m_element.get(), ATK_STATE_ENABLED);
1707}
1708
1709bool AccessibilityUIElement::isRequired() const
1710{
1711 return checkElementState(m_element.get(), ATK_STATE_REQUIRED);
1712}
1713
1714bool AccessibilityUIElement::isFocused() const
1715{
1716 return checkElementState(m_element.get(), ATK_STATE_FOCUSED);
1717}
1718
1719bool AccessibilityUIElement::isSelected() const
1720{
1721 return checkElementState(m_element.get(), ATK_STATE_SELECTED);
1722}
1723
1724bool AccessibilityUIElement::isSelectedOptionActive() const
1725{
1726 return checkElementState(m_element.get(), ATK_STATE_ACTIVE);
1727}
1728
1729bool AccessibilityUIElement::isExpanded() const
1730{
1731 return checkElementState(m_element.get(), ATK_STATE_EXPANDED);
1732}
1733
1734bool AccessibilityUIElement::isChecked() const
1735{
1736 return checkElementState(m_element.get(), ATK_STATE_CHECKED);
1737}
1738
1739bool AccessibilityUIElement::isIndeterminate() const
1740{
1741 return checkElementState(m_element.get(), ATK_STATE_INDETERMINATE);
1742}
1743
1744int AccessibilityUIElement::hierarchicalLevel() const
1745{
1746 String level = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "level");
1747 if (!level.isEmpty())
1748 return level.toInt();
1749
1750 return 0;
1751}
1752
1753JSRetainPtr<JSStringRef> AccessibilityUIElement::speakAs()
1754{
1755 // FIXME: implement
1756 return JSStringCreateWithCharacters(0, 0);
1757}
1758
1759bool AccessibilityUIElement::ariaIsGrabbed() const
1760{
1761 return getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "grabbed") == "true";
1762}
1763
1764JSRetainPtr<JSStringRef> AccessibilityUIElement::ariaDropEffects() const
1765{
1766 String dropEffects = getAttributeSetValueForId(ATK_OBJECT(m_element.get()), ObjectAttributeType, "dropeffect");
1767 return dropEffects.isEmpty() ? JSStringCreateWithCharacters(0, 0) : JSStringCreateWithUTF8CString(dropEffects.utf8().data());
1768}
1769
1770// parameterized attributes
1771int AccessibilityUIElement::lineForIndex(int index)
1772{
1773 if (!ATK_IS_TEXT(m_element.get()))
1774 return -1;
1775
1776 if (index < 0 || index > atk_text_get_character_count(ATK_TEXT(m_element.get())))
1777 return -1;
1778
1779 GUniquePtr<gchar> text(atk_text_get_text(ATK_TEXT(m_element.get()), 0, index));
1780 int lineNo = 0;
1781 for (gchar* offset = text.get(); *offset; ++offset) {
1782 if (*offset == '\n')
1783 ++lineNo;
1784 }
1785
1786 return lineNo;
1787}
1788
1789JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForLine(int line)
1790{
1791 if (!ATK_IS_TEXT(m_element.get()))
1792 return JSStringCreateWithCharacters(0, 0);
1793
1794 AtkText* text = ATK_TEXT(m_element.get());
1795 gint startOffset = 0, endOffset = 0;
1796 for (int i = 0; i <= line; ++i)
1797 atk_text_get_string_at_offset(text, endOffset, ATK_TEXT_GRANULARITY_LINE, &startOffset, &endOffset);
1798
1799 GUniquePtr<gchar> range(g_strdup_printf("{%d, %d}", startOffset, endOffset - startOffset));
1800 return JSStringCreateWithUTF8CString(range.get());
1801}
1802
1803JSRetainPtr<JSStringRef> AccessibilityUIElement::rangeForPosition(int x, int y)
1804{
1805 // FIXME: implement
1806 return JSStringCreateWithCharacters(0, 0);
1807}
1808
1809JSRetainPtr<JSStringRef> AccessibilityUIElement::boundsForRange(unsigned location, unsigned length)
1810{
1811 if (!ATK_IS_TEXT(m_element.get()))
1812 return JSStringCreateWithCharacters(0, 0);
1813
1814 AtkTextRectangle rect;
1815 atk_text_get_range_extents(ATK_TEXT(m_element.get()), location, location + length, ATK_XY_WINDOW, &rect);
1816
1817 GUniquePtr<gchar> bounds(g_strdup_printf("{%d, %d, %d, %d}", rect.x, rect.y, rect.width, rect.height));
1818 return JSStringCreateWithUTF8CString(bounds.get());
1819}
1820
1821JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForRange(unsigned location, unsigned length)
1822{
1823 if (!ATK_IS_TEXT(m_element.get()))
1824 return JSStringCreateWithCharacters(0, 0);
1825
1826 String string = atk_text_get_text(ATK_TEXT(m_element.get()), location, location + length);
1827 return JSStringCreateWithUTF8CString(string.utf8().data());
1828}
1829
1830JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForRange(unsigned location, unsigned length)
1831{
1832 if (!ATK_IS_TEXT(m_element.get()))
1833 return JSStringCreateWithCharacters(0, 0);
1834
1835 StringBuilder builder;
1836
1837 // The default text attributes apply to the entire element.
1838 builder.appendLiteral("\n\tDefault text attributes:\n\t\t");
1839 builder.append(attributeSetToString(getAttributeSet(m_element.get(), TextAttributeType), "\n\t\t"));
1840
1841 // The attribute run provides attributes specific to the range of text at the specified offset.
1842 AtkText* text = ATK_TEXT(m_element.get());
1843 gint start = 0, end = 0;
1844 for (unsigned i = location; i < location + length; i = end) {
1845 AtkAttributeSet* attributeSet = atk_text_get_run_attributes(text, i, &start, &end);
1846 GUniquePtr<gchar> substring(replaceCharactersForResults(atk_text_get_text(text, start, end)));
1847 builder.appendLiteral("\n\tRange attributes for '");
1848 builder.append(substring.get());
1849 builder.appendLiteral("':\n\t\t");
1850 builder.append(attributeSetToString(attributeSet, "\n\t\t"));
1851 }
1852
1853 return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
1854}
1855
1856bool AccessibilityUIElement::attributedStringRangeIsMisspelled(unsigned location, unsigned length)
1857{
1858 // FIXME: implement
1859 return false;
1860}
1861
1862unsigned AccessibilityUIElement::uiElementCountForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
1863{
1864 // FIXME: implement
1865 return 0;
1866}
1867
1868RefPtr<AccessibilityUIElement> AccessibilityUIElement::uiElementForSearchPredicate(JSContextRef context, AccessibilityUIElement* startElement, bool isDirectionNext, JSValueRef searchKey, JSStringRef searchText, bool visibleOnly, bool immediateDescendantsOnly)
1869{
1870 // FIXME: implement
1871 return nullptr;
1872}
1873
1874JSRetainPtr<JSStringRef> AccessibilityUIElement::selectTextWithCriteria(JSContextRef context, JSStringRef ambiguityResolution, JSValueRef searchStrings, JSStringRef replacementString, JSStringRef activity)
1875{
1876 // FIXME: implement
1877 return nullptr;
1878}
1879
1880JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumnHeaders()
1881{
1882 if (!ATK_IS_TABLE(m_element.get()))
1883 return JSStringCreateWithCharacters(0, 0);
1884
1885 Vector<RefPtr<AccessibilityUIElement> > columnHeaders = getTableColumnHeaders(ATK_TABLE(m_element.get()));
1886 return createStringWithAttributes(columnHeaders);
1887}
1888
1889JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRowHeaders()
1890{
1891 if (!ATK_IS_TABLE(m_element.get()))
1892 return JSStringCreateWithCharacters(0, 0);
1893
1894 Vector<RefPtr<AccessibilityUIElement> > rowHeaders = getTableRowHeaders(ATK_TABLE(m_element.get()));
1895 return createStringWithAttributes(rowHeaders);
1896}
1897
1898JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfColumns()
1899{
1900 // FIXME: implement
1901 return JSStringCreateWithCharacters(0, 0);
1902}
1903
1904JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfRows()
1905{
1906 // FIXME: implement
1907 return JSStringCreateWithCharacters(0, 0);
1908}
1909
1910JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfVisibleCells()
1911{
1912 if (!ATK_IS_TABLE(m_element.get()))
1913 return JSStringCreateWithCharacters(0, 0);
1914
1915 Vector<RefPtr<AccessibilityUIElement> > visibleCells = getVisibleCells(this);
1916 return createStringWithAttributes(visibleCells);
1917}
1918
1919JSRetainPtr<JSStringRef> AccessibilityUIElement::attributesOfHeader()
1920{
1921 // FIXME: implement
1922 return JSStringCreateWithCharacters(0, 0);
1923}
1924
1925int AccessibilityUIElement::rowCount()
1926{
1927 if (!ATK_IS_TABLE(m_element.get()))
1928 return 0;
1929
1930 return atk_table_get_n_rows(ATK_TABLE(m_element.get()));
1931}
1932
1933int AccessibilityUIElement::columnCount()
1934{
1935 if (!ATK_IS_TABLE(m_element.get()))
1936 return 0;
1937
1938 return atk_table_get_n_columns(ATK_TABLE(m_element.get()));
1939}
1940
1941int AccessibilityUIElement::indexInTable()
1942{
1943 // FIXME: implement
1944 return -1;
1945}
1946
1947JSRetainPtr<JSStringRef> AccessibilityUIElement::rowIndexRange()
1948{
1949 // Range in table for rows.
1950 return indexRangeInTable(m_element.get(), true);
1951}
1952
1953JSRetainPtr<JSStringRef> AccessibilityUIElement::columnIndexRange()
1954{
1955 // Range in table for columns.
1956 return indexRangeInTable(m_element.get(), false);
1957}
1958
1959RefPtr<AccessibilityUIElement> AccessibilityUIElement::cellForColumnAndRow(unsigned col, unsigned row)
1960{
1961 if (!ATK_IS_TABLE(m_element.get()))
1962 return nullptr;
1963
1964 // Adopt the AtkObject representing the cell because
1965 // at_table_ref_at() transfers full ownership.
1966 GRefPtr<AtkObject> foundCell = adoptGRef(atk_table_ref_at(ATK_TABLE(m_element.get()), row, col));
1967 return foundCell ? AccessibilityUIElement::create(foundCell.get()).ptr() : nullptr;
1968}
1969
1970RefPtr<AccessibilityUIElement> AccessibilityUIElement::horizontalScrollbar() const
1971{
1972 // FIXME: implement
1973 return nullptr;
1974}
1975
1976RefPtr<AccessibilityUIElement> AccessibilityUIElement::verticalScrollbar() const
1977{
1978 // FIXME: implement
1979 return nullptr;
1980}
1981
1982JSRetainPtr<JSStringRef> AccessibilityUIElement::selectedTextRange()
1983{
1984 if (!ATK_IS_TEXT(m_element.get()))
1985 return JSStringCreateWithCharacters(0, 0);
1986
1987 gint start, end;
1988 g_free(atk_text_get_selection(ATK_TEXT(m_element.get()), 0, &start, &end));
1989
1990 GUniquePtr<gchar> selection(g_strdup_printf("{%d, %d}", start, end - start));
1991 return JSStringCreateWithUTF8CString(selection.get());
1992}
1993
1994bool AccessibilityUIElement::setSelectedTextRange(unsigned location, unsigned length)
1995{
1996 if (!ATK_IS_TEXT(m_element.get()))
1997 return false;
1998
1999 if (!length)
2000 return atk_text_set_caret_offset(ATK_TEXT(m_element.get()), location);
2001
2002 return atk_text_set_selection(ATK_TEXT(m_element.get()), 0, location, location + length);
2003}
2004
2005void AccessibilityUIElement::increment()
2006{
2007 alterCurrentValue(m_element.get(), 1);
2008}
2009
2010void AccessibilityUIElement::decrement()
2011{
2012 alterCurrentValue(m_element.get(), -1);
2013}
2014
2015void AccessibilityUIElement::showMenu()
2016{
2017 // FIXME: implement
2018}
2019
2020void AccessibilityUIElement::press()
2021{
2022 if (!ATK_IS_ACTION(m_element.get()))
2023 return;
2024
2025 // Only one action per object is supported so far.
2026 atk_action_do_action(ATK_ACTION(m_element.get()), 0);
2027}
2028
2029void AccessibilityUIElement::setSelectedChild(AccessibilityUIElement* element) const
2030{
2031 // FIXME: implement
2032}
2033
2034void AccessibilityUIElement::setSelectedChildAtIndex(unsigned index) const
2035{
2036 if (!ATK_IS_SELECTION(m_element.get()))
2037 return;
2038
2039 atk_selection_add_selection(ATK_SELECTION(m_element.get()), index);
2040}
2041
2042void AccessibilityUIElement::removeSelectionAtIndex(unsigned index) const
2043{
2044 if (!ATK_IS_SELECTION(m_element.get()))
2045 return;
2046
2047 atk_selection_remove_selection(ATK_SELECTION(m_element.get()), index);
2048}
2049
2050void AccessibilityUIElement::clearSelectedChildren() const
2051{
2052 if (!ATK_IS_SELECTION(m_element.get()))
2053 return;
2054
2055 atk_selection_clear_selection(ATK_SELECTION(m_element.get()));
2056}
2057
2058JSRetainPtr<JSStringRef> AccessibilityUIElement::accessibilityValue() const
2059{
2060 // FIXME: implement
2061 return JSStringCreateWithCharacters(0, 0);
2062}
2063
2064JSRetainPtr<JSStringRef> AccessibilityUIElement::documentEncoding()
2065{
2066 if (!ATK_IS_DOCUMENT(m_element.get()))
2067 return JSStringCreateWithCharacters(0, 0);
2068
2069 AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
2070 if (role != ATK_ROLE_DOCUMENT_WEB)
2071 return JSStringCreateWithCharacters(0, 0);
2072
2073 return JSStringCreateWithUTF8CString(atk_document_get_attribute_value(ATK_DOCUMENT(m_element.get()), "Encoding"));
2074}
2075
2076JSRetainPtr<JSStringRef> AccessibilityUIElement::documentURI()
2077{
2078 if (!ATK_IS_DOCUMENT(m_element.get()))
2079 return JSStringCreateWithCharacters(0, 0);
2080
2081 AtkRole role = atk_object_get_role(ATK_OBJECT(m_element.get()));
2082 if (role != ATK_ROLE_DOCUMENT_WEB)
2083 return JSStringCreateWithCharacters(0, 0);
2084
2085 return JSStringCreateWithUTF8CString(atk_document_get_attribute_value(ATK_DOCUMENT(m_element.get()), "URI"));
2086}
2087
2088JSRetainPtr<JSStringRef> AccessibilityUIElement::url()
2089{
2090 if (!ATK_IS_HYPERLINK_IMPL(m_element.get()))
2091 return JSStringCreateWithCharacters(0, 0);
2092
2093 AtkHyperlink* hyperlink = atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(m_element.get()));
2094 GUniquePtr<char> hyperlinkURI(atk_hyperlink_get_uri(hyperlink, 0));
2095
2096 if (!hyperlinkURI.get())
2097 return JSStringCreateWithUTF8CString("AXURL: (null)");
2098
2099 // Build the result string, stripping the absolute URL paths if present.
2100 char* localURI = g_strstr_len(hyperlinkURI.get(), -1, "LayoutTests");
2101 String axURL = makeString("AXURL: ", localURI ? localURI : hyperlinkURI.get());
2102 return JSStringCreateWithUTF8CString(axURL.utf8().data());
2103}
2104
2105bool AccessibilityUIElement::addNotificationListener(JSValueRef functionCallback)
2106{
2107 if (!functionCallback)
2108 return false;
2109
2110 // Only one notification listener per element.
2111 if (m_notificationHandler)
2112 return false;
2113
2114 m_notificationHandler = AccessibilityNotificationHandler::create();
2115 m_notificationHandler->setPlatformElement(platformUIElement());
2116 m_notificationHandler->setNotificationFunctionCallback(functionCallback);
2117
2118 return true;
2119}
2120
2121bool AccessibilityUIElement::removeNotificationListener()
2122{
2123 // Programmers should not be trying to remove a listener that's already removed.
2124 ASSERT(m_notificationHandler);
2125 m_notificationHandler = nullptr;
2126
2127 return true;
2128}
2129
2130bool AccessibilityUIElement::isFocusable() const
2131{
2132 return checkElementState(m_element.get(), ATK_STATE_FOCUSABLE);
2133}
2134
2135bool AccessibilityUIElement::isSelectable() const
2136{
2137 return checkElementState(m_element.get(), ATK_STATE_SELECTABLE);
2138}
2139
2140bool AccessibilityUIElement::isMultiSelectable() const
2141{
2142 return checkElementState(m_element.get(), ATK_STATE_MULTISELECTABLE);
2143}
2144
2145bool AccessibilityUIElement::isVisible() const
2146{
2147 return checkElementState(m_element.get(), ATK_STATE_VISIBLE);
2148}
2149
2150bool AccessibilityUIElement::isOffScreen() const
2151{
2152 // FIXME: implement
2153 return false;
2154}
2155
2156bool AccessibilityUIElement::isCollapsed() const
2157{
2158 // FIXME: implement
2159 return false;
2160}
2161
2162bool AccessibilityUIElement::isIgnored() const
2163{
2164 // FIXME: implement
2165 return false;
2166}
2167
2168bool AccessibilityUIElement::isSingleLine() const
2169{
2170 return checkElementState(m_element.get(), ATK_STATE_SINGLE_LINE);
2171}
2172
2173bool AccessibilityUIElement::isMultiLine() const
2174{
2175 return checkElementState(m_element.get(), ATK_STATE_MULTI_LINE);
2176}
2177
2178bool AccessibilityUIElement::hasPopup() const
2179{
2180 return checkElementState(m_element.get(), ATK_STATE_HAS_POPUP);
2181}
2182
2183void AccessibilityUIElement::takeFocus()
2184{
2185 // FIXME: implement
2186}
2187
2188void AccessibilityUIElement::takeSelection()
2189{
2190 // FIXME: implement
2191}
2192
2193void AccessibilityUIElement::addSelection()
2194{
2195 // FIXME: implement
2196}
2197
2198void AccessibilityUIElement::removeSelection()
2199{
2200 // FIXME: implement
2201}
2202
2203// Text markers
2204RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::lineTextMarkerRangeForTextMarker(AccessibilityTextMarker* textMarker)
2205{
2206 // FIXME: implement
2207 return nullptr;
2208}
2209
2210RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForElement(AccessibilityUIElement* element)
2211{
2212 // FIXME: implement
2213 return nullptr;
2214}
2215
2216int AccessibilityUIElement::textMarkerRangeLength(AccessibilityTextMarkerRange* range)
2217{
2218 // FIXME: implement
2219 return 0;
2220}
2221
2222RefPtr<AccessibilityTextMarker> AccessibilityUIElement::previousTextMarker(AccessibilityTextMarker* textMarker)
2223{
2224 // FIXME: implement
2225 return nullptr;
2226}
2227
2228RefPtr<AccessibilityTextMarker> AccessibilityUIElement::nextTextMarker(AccessibilityTextMarker* textMarker)
2229{
2230 // FIXME: implement
2231 return nullptr;
2232}
2233
2234JSRetainPtr<JSStringRef> AccessibilityUIElement::stringForTextMarkerRange(AccessibilityTextMarkerRange* markerRange)
2235{
2236 // FIXME: implement
2237 return JSStringCreateWithCharacters(0, 0);
2238}
2239
2240RefPtr<AccessibilityTextMarkerRange> AccessibilityUIElement::textMarkerRangeForMarkers(AccessibilityTextMarker* startMarker, AccessibilityTextMarker* endMarker)
2241{
2242 // FIXME: implement
2243 return nullptr;
2244}
2245
2246RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
2247{
2248 // FIXME: implement
2249 return nullptr;
2250}
2251
2252RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForTextMarkerRange(AccessibilityTextMarkerRange* range)
2253{
2254 // FIXME: implement
2255 return nullptr;
2256}
2257
2258RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarkerForBounds(int x, int y, int width, int height)
2259{
2260 // FIXME: implement
2261 return nullptr;
2262}
2263
2264RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarkerForBounds(int x, int y, int width, int height)
2265{
2266 // FIXME: implement
2267 return nullptr;
2268}
2269
2270RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForPoint(int x, int y)
2271{
2272 // FIXME: implement
2273 return nullptr;
2274}
2275
2276RefPtr<AccessibilityUIElement> AccessibilityUIElement::accessibilityElementForTextMarker(AccessibilityTextMarker* marker)
2277{
2278 // FIXME: implement
2279 return nullptr;
2280}
2281
2282JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRange(AccessibilityTextMarkerRange*)
2283{
2284 return nullptr;
2285}
2286
2287JSRetainPtr<JSStringRef> AccessibilityUIElement::attributedStringForTextMarkerRangeWithOptions(AccessibilityTextMarkerRange*, bool)
2288{
2289 return nullptr;
2290}
2291
2292bool AccessibilityUIElement::attributedStringForTextMarkerRangeContainsAttribute(JSStringRef attribute, AccessibilityTextMarkerRange* range)
2293{
2294 // FIXME: implement
2295 return false;
2296}
2297
2298int AccessibilityUIElement::indexForTextMarker(AccessibilityTextMarker* marker)
2299{
2300 // FIXME: implement
2301 return -1;
2302}
2303
2304bool AccessibilityUIElement::isTextMarkerValid(AccessibilityTextMarker* textMarker)
2305{
2306 // FIXME: implement
2307 return false;
2308}
2309
2310RefPtr<AccessibilityTextMarker> AccessibilityUIElement::textMarkerForIndex(int textIndex)
2311{
2312 // FIXME: implement
2313 return nullptr;
2314}
2315
2316RefPtr<AccessibilityTextMarker> AccessibilityUIElement::startTextMarker()
2317{
2318 // FIXME: implement
2319 return nullptr;
2320}
2321
2322RefPtr<AccessibilityTextMarker> AccessibilityUIElement::endTextMarker()
2323{
2324 // FIXME: implement
2325 return nullptr;
2326}
2327
2328bool AccessibilityUIElement::setSelectedVisibleTextRange(AccessibilityTextMarkerRange*)
2329{
2330 return false;
2331}
2332
2333void AccessibilityUIElement::scrollToMakeVisible()
2334{
2335#if ATK_CHECK_VERSION(2, 30, 0)
2336 if (!ATK_IS_COMPONENT(m_element.get()))
2337 return;
2338
2339 atk_component_scroll_to(ATK_COMPONENT(m_element.get()), ATK_SCROLL_ANYWHERE);
2340#endif
2341}
2342
2343void AccessibilityUIElement::scrollToGlobalPoint(int x, int y)
2344{
2345#if ATK_CHECK_VERSION(2, 30, 0)
2346 if (!ATK_IS_COMPONENT(m_element.get()))
2347 return;
2348
2349 atk_component_scroll_to_point(ATK_COMPONENT(m_element.get()), ATK_XY_WINDOW, x, y);
2350#endif
2351}
2352
2353void AccessibilityUIElement::scrollToMakeVisibleWithSubFocus(int x, int y, int width, int height)
2354{
2355 // FIXME: implement
2356}
2357
2358JSRetainPtr<JSStringRef> AccessibilityUIElement::supportedActions() const
2359{
2360 // FIXME: implement
2361 return nullptr;
2362}
2363
2364JSRetainPtr<JSStringRef> AccessibilityUIElement::pathDescription() const
2365{
2366 notImplemented();
2367 return nullptr;
2368}
2369
2370JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPostscriptsDescription() const
2371{
2372 notImplemented();
2373 return nullptr;
2374}
2375
2376JSRetainPtr<JSStringRef> AccessibilityUIElement::mathPrescriptsDescription() const
2377{
2378 notImplemented();
2379 return nullptr;
2380}
2381
2382JSRetainPtr<JSStringRef> AccessibilityUIElement::classList() const
2383{
2384 notImplemented();
2385 return nullptr;
2386}
2387
2388JSRetainPtr<JSStringRef> stringAtOffset(PlatformUIElement element, AtkTextBoundary boundary, int offset)
2389{
2390 if (!ATK_IS_TEXT(element.get()))
2391 return JSStringCreateWithCharacters(0, 0);
2392
2393 gint startOffset, endOffset;
2394 StringBuilder builder;
2395
2396#if ATK_CHECK_VERSION(2, 10, 0)
2397 AtkTextGranularity granularity;
2398 switch (boundary) {
2399 case ATK_TEXT_BOUNDARY_CHAR:
2400 granularity = ATK_TEXT_GRANULARITY_CHAR;
2401 break;
2402 case ATK_TEXT_BOUNDARY_WORD_START:
2403 granularity = ATK_TEXT_GRANULARITY_WORD;
2404 break;
2405 case ATK_TEXT_BOUNDARY_LINE_START:
2406 granularity = ATK_TEXT_GRANULARITY_LINE;
2407 break;
2408 case ATK_TEXT_BOUNDARY_SENTENCE_START:
2409 granularity = ATK_TEXT_GRANULARITY_SENTENCE;
2410 break;
2411 default:
2412 return JSStringCreateWithCharacters(0, 0);
2413 }
2414
2415 builder.append(atk_text_get_string_at_offset(ATK_TEXT(element.get()), offset, granularity, &startOffset, &endOffset));
2416#else
2417 builder.append(atk_text_get_text_at_offset(ATK_TEXT(element.get()), offset, boundary, &startOffset, &endOffset));
2418#endif
2419 builder.appendLiteral(", ");
2420 builder.appendNumber(startOffset);
2421 builder.appendLiteral(", ");
2422 builder.appendNumber(endOffset);
2423 return JSStringCreateWithUTF8CString(builder.toString().utf8().data());
2424}
2425
2426JSRetainPtr<JSStringRef> AccessibilityUIElement::characterAtOffset(int offset)
2427{
2428 return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_CHAR, offset);
2429}
2430
2431JSRetainPtr<JSStringRef> AccessibilityUIElement::wordAtOffset(int offset)
2432{
2433 return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_WORD_START, offset);
2434}
2435
2436JSRetainPtr<JSStringRef> AccessibilityUIElement::lineAtOffset(int offset)
2437{
2438 return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_LINE_START, offset);
2439}
2440
2441JSRetainPtr<JSStringRef> AccessibilityUIElement::sentenceAtOffset(int offset)
2442{
2443 return stringAtOffset(m_element, ATK_TEXT_BOUNDARY_SENTENCE_START, offset);
2444}
2445
2446bool AccessibilityUIElement::replaceTextInRange(JSStringRef, int, int)
2447{
2448 notImplemented();
2449 return false;
2450}
2451
2452} // namespace WTR
2453
2454#endif // HAVE(ACCESSIBILITY)
2455