1 /*
2 * Copyright (C) 2008 Nuanti Ltd.
3 * Copyright (C) 2009 Igalia S.L.
4 * Copyright (C) 2009 Jan Alonzo
5 *
6 * Portions from Mozilla a11y, copyright as follows:
7 *
8 * The Original Code is mozilla.org code.
9 *
10 * The Initial Developer of the Original Code is
11 * Sun Microsystems, Inc.
12 * Portions created by the Initial Developer are Copyright (C) 2002
13 * the Initial Developer. All Rights Reserved.
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Library General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Library General Public License for more details.
24 *
25 * You should have received a copy of the GNU Library General Public License
26 * along with this library; see the file COPYING.LIB. If not, write to
27 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 * Boston, MA 02110-1301, USA.
29 */
30
31 #include "config.h"
32 #include "AccessibilityObjectWrapperAtk.h"
33
34 #if HAVE(ACCESSIBILITY)
35
36 #include "AXObjectCache.h"
37 #include "AccessibilityList.h"
38 #include "AccessibilityListBox.h"
39 #include "AccessibilityListBoxOption.h"
40 #include "AccessibilityTable.h"
41 #include "AccessibilityTableCell.h"
42 #include "AccessibilityTableColumn.h"
43 #include "AccessibilityTableRow.h"
44 #include "CharacterNames.h"
45 #include "Document.h"
46 #include "DocumentType.h"
47 #include "Editor.h"
48 #include "Frame.h"
49 #include "FrameView.h"
50 #include "GOwnPtr.h"
51 #include "HostWindow.h"
52 #include "HTMLNames.h"
53 #include "HTMLTableCaptionElement.h"
54 #include "HTMLTableElement.h"
55 #include "InlineTextBox.h"
56 #include "IntRect.h"
57 #include "NotImplemented.h"
58 #include "RenderListItem.h"
59 #include "RenderListMarker.h"
60 #include "RenderText.h"
61 #include "SelectElement.h"
62 #include "Settings.h"
63 #include "TextEncoding.h"
64 #include "TextIterator.h"
65 #include "WebKitAccessibleHyperlink.h"
66 #include "htmlediting.h"
67 #include "visible_units.h"
68
69 #include <atk/atk.h>
70 #include <glib.h>
71 #include <glib/gprintf.h>
72 #include <libgail-util/gail-util.h>
73 #include <pango/pango.h>
74 #include <wtf/text/AtomicString.h>
75 #include <wtf/text/CString.h>
76
77 using namespace WebCore;
78
fallbackObject()79 static AccessibilityObject* fallbackObject()
80 {
81 // FIXME: An AXObjectCache with a Document is meaningless.
82 static AXObjectCache* fallbackCache = new AXObjectCache(0);
83 static AccessibilityObject* object = 0;
84 if (!object) {
85 // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack
86 object = fallbackCache->getOrCreate(ListBoxOptionRole);
87 object->ref();
88 }
89
90 return object;
91 }
92
93 // Used to provide const char* returns.
returnString(const String & str)94 static const char* returnString(const String& str)
95 {
96 static CString returnedString;
97 returnedString = str.utf8();
98 return returnedString.data();
99 }
100
core(WebKitAccessible * accessible)101 static AccessibilityObject* core(WebKitAccessible* accessible)
102 {
103 if (!accessible)
104 return 0;
105
106 return accessible->m_object;
107 }
108
core(AtkObject * object)109 static AccessibilityObject* core(AtkObject* object)
110 {
111 if (!WEBKIT_IS_ACCESSIBLE(object))
112 return 0;
113
114 return core(WEBKIT_ACCESSIBLE(object));
115 }
116
core(AtkAction * action)117 static AccessibilityObject* core(AtkAction* action)
118 {
119 return core(ATK_OBJECT(action));
120 }
121
core(AtkSelection * selection)122 static AccessibilityObject* core(AtkSelection* selection)
123 {
124 return core(ATK_OBJECT(selection));
125 }
126
core(AtkText * text)127 static AccessibilityObject* core(AtkText* text)
128 {
129 return core(ATK_OBJECT(text));
130 }
131
core(AtkEditableText * text)132 static AccessibilityObject* core(AtkEditableText* text)
133 {
134 return core(ATK_OBJECT(text));
135 }
136
core(AtkComponent * component)137 static AccessibilityObject* core(AtkComponent* component)
138 {
139 return core(ATK_OBJECT(component));
140 }
141
core(AtkImage * image)142 static AccessibilityObject* core(AtkImage* image)
143 {
144 return core(ATK_OBJECT(image));
145 }
146
core(AtkTable * table)147 static AccessibilityObject* core(AtkTable* table)
148 {
149 return core(ATK_OBJECT(table));
150 }
151
core(AtkHypertext * hypertext)152 static AccessibilityObject* core(AtkHypertext* hypertext)
153 {
154 return core(ATK_OBJECT(hypertext));
155 }
156
core(AtkDocument * document)157 static AccessibilityObject* core(AtkDocument* document)
158 {
159 return core(ATK_OBJECT(document));
160 }
161
core(AtkValue * value)162 static AccessibilityObject* core(AtkValue* value)
163 {
164 return core(ATK_OBJECT(value));
165 }
166
167 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset);
168
webkit_accessible_get_name(AtkObject * object)169 static const gchar* webkit_accessible_get_name(AtkObject* object)
170 {
171 AccessibilityObject* coreObject = core(object);
172 if (!coreObject->isAccessibilityRenderObject())
173 return returnString(coreObject->stringValue());
174
175 if (coreObject->isControl()) {
176 AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
177 if (label) {
178 AtkObject* atkObject = label->wrapper();
179 if (ATK_IS_TEXT(atkObject))
180 return webkit_accessible_text_get_text(ATK_TEXT(atkObject), 0, -1);
181 }
182
183 // Try text under the node.
184 String textUnder = coreObject->textUnderElement();
185 if (textUnder.length())
186 return returnString(textUnder);
187 }
188
189 if (coreObject->isImage() || coreObject->isInputImage()) {
190 Node* node = coreObject->node();
191 if (node && node->isHTMLElement()) {
192 // Get the attribute rather than altText String so as not to fall back on title.
193 String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr);
194 if (!alt.isEmpty())
195 return returnString(alt);
196 }
197 }
198
199 // Fallback for the webArea object: just return the document's title.
200 if (coreObject->isWebArea()) {
201 Document* document = coreObject->document();
202 if (document)
203 return returnString(document->title());
204 }
205
206 return returnString(coreObject->stringValue());
207 }
208
webkit_accessible_get_description(AtkObject * object)209 static const gchar* webkit_accessible_get_description(AtkObject* object)
210 {
211 AccessibilityObject* coreObject = core(object);
212 Node* node = 0;
213 if (coreObject->isAccessibilityRenderObject())
214 node = coreObject->node();
215 if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole)
216 return returnString(coreObject->accessibilityDescription());
217
218 // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here.
219 if (coreObject->roleValue() == TableRole) {
220 String summary = static_cast<HTMLTableElement*>(node)->summary();
221 if (!summary.isEmpty())
222 return returnString(summary);
223 }
224
225 // The title attribute should be reliably available as the object's descripton.
226 // We do not want to fall back on other attributes in its absence. See bug 25524.
227 String title = toHTMLElement(node)->title();
228 if (!title.isEmpty())
229 return returnString(title);
230
231 return returnString(coreObject->accessibilityDescription());
232 }
233
setAtkRelationSetFromCoreObject(AccessibilityObject * coreObject,AtkRelationSet * relationSet)234 static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet)
235 {
236 if (coreObject->isControl()) {
237 AccessibilityObject* label = coreObject->correspondingLabelForControlElement();
238 if (label)
239 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper());
240 } else {
241 AccessibilityObject* control = coreObject->correspondingControlForLabelElement();
242 if (control)
243 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper());
244 }
245 }
246
247 static gpointer webkit_accessible_parent_class = 0;
248
isRootObject(AccessibilityObject * coreObject)249 static bool isRootObject(AccessibilityObject* coreObject)
250 {
251 // The root accessible object in WebCore is always an object with
252 // the ScrolledArea role with one child with the WebArea role.
253 if (!coreObject || !coreObject->isScrollView())
254 return false;
255
256 AccessibilityObject* firstChild = coreObject->firstChild();
257 if (!firstChild || !firstChild->isWebArea())
258 return false;
259
260 return true;
261 }
262
atkParentOfRootObject(AtkObject * object)263 static AtkObject* atkParentOfRootObject(AtkObject* object)
264 {
265 AccessibilityObject* coreObject = core(object);
266 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
267
268 // The top level object claims to not have a parent. This makes it
269 // impossible for assistive technologies to ascend the accessible
270 // hierarchy all the way to the application. (Bug 30489)
271 if (!coreParent && isRootObject(coreObject)) {
272 Document* document = coreObject->document();
273 if (!document)
274 return 0;
275
276 HostWindow* hostWindow = document->view()->hostWindow();
277 if (hostWindow) {
278 PlatformPageClient scrollView = hostWindow->platformPageClient();
279 if (scrollView) {
280 GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView);
281 if (scrollViewParent)
282 return gtk_widget_get_accessible(scrollViewParent);
283 }
284 }
285 }
286
287 if (!coreParent)
288 return 0;
289
290 return coreParent->wrapper();
291 }
292
webkit_accessible_get_parent(AtkObject * object)293 static AtkObject* webkit_accessible_get_parent(AtkObject* object)
294 {
295 AccessibilityObject* coreObject = core(object);
296 AccessibilityObject* coreParent = coreObject->parentObjectUnignored();
297 if (!coreParent && isRootObject(coreObject))
298 return atkParentOfRootObject(object);
299
300 if (!coreParent)
301 return 0;
302
303 return coreParent->wrapper();
304 }
305
webkit_accessible_get_n_children(AtkObject * object)306 static gint webkit_accessible_get_n_children(AtkObject* object)
307 {
308 return core(object)->children().size();
309 }
310
webkit_accessible_ref_child(AtkObject * object,gint index)311 static AtkObject* webkit_accessible_ref_child(AtkObject* object, gint index)
312 {
313 AccessibilityObject* coreObject = core(object);
314 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
315 if (index < 0 || static_cast<unsigned>(index) >= children.size())
316 return 0;
317
318 AccessibilityObject* coreChild = children.at(index).get();
319
320 if (!coreChild)
321 return 0;
322
323 AtkObject* child = coreChild->wrapper();
324 atk_object_set_parent(child, object);
325 g_object_ref(child);
326
327 return child;
328 }
329
webkit_accessible_get_index_in_parent(AtkObject * object)330 static gint webkit_accessible_get_index_in_parent(AtkObject* object)
331 {
332 AccessibilityObject* coreObject = core(object);
333 AccessibilityObject* parent = coreObject->parentObjectUnignored();
334
335 if (!parent && isRootObject(coreObject)) {
336 AtkObject* atkParent = atkParentOfRootObject(object);
337 if (!atkParent)
338 return -1;
339
340 unsigned count = atk_object_get_n_accessible_children(atkParent);
341 for (unsigned i = 0; i < count; ++i) {
342 AtkObject* child = atk_object_ref_accessible_child(atkParent, i);
343 bool childIsObject = child == object;
344 g_object_unref(child);
345 if (childIsObject)
346 return i;
347 }
348 }
349
350 AccessibilityObject::AccessibilityChildrenVector children = parent->children();
351 unsigned count = children.size();
352 for (unsigned i = 0; i < count; ++i) {
353 if (children[i] == coreObject)
354 return i;
355 }
356
357 return -1;
358 }
359
addAttributeToSet(AtkAttributeSet * attributeSet,const char * name,const char * value)360 static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const char* name, const char* value)
361 {
362 AtkAttribute* attribute = static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
363 attribute->name = g_strdup(name);
364 attribute->value = g_strdup(value);
365 attributeSet = g_slist_prepend(attributeSet, attribute);
366
367 return attributeSet;
368 }
369
webkit_accessible_get_attributes(AtkObject * object)370 static AtkAttributeSet* webkit_accessible_get_attributes(AtkObject* object)
371 {
372 AtkAttributeSet* attributeSet = 0;
373 attributeSet = addAttributeToSet(attributeSet, "toolkit", "WebKitGtk");
374
375 AccessibilityObject* coreObject = core(object);
376 if (!coreObject)
377 return attributeSet;
378
379 int headingLevel = coreObject->headingLevel();
380 if (headingLevel) {
381 String value = String::number(headingLevel);
382 attributeSet = addAttributeToSet(attributeSet, "level", value.utf8().data());
383 }
384
385 // Set the 'layout-guess' attribute to help Assistive
386 // Technologies know when an exposed table is not data table.
387 if (coreObject->isAccessibilityTable() && !coreObject->isDataTable())
388 attributeSet = addAttributeToSet(attributeSet, "layout-guess", "true");
389
390 return attributeSet;
391 }
392
atkRole(AccessibilityRole role)393 static AtkRole atkRole(AccessibilityRole role)
394 {
395 switch (role) {
396 case UnknownRole:
397 return ATK_ROLE_UNKNOWN;
398 case ButtonRole:
399 return ATK_ROLE_PUSH_BUTTON;
400 case RadioButtonRole:
401 return ATK_ROLE_RADIO_BUTTON;
402 case CheckBoxRole:
403 return ATK_ROLE_CHECK_BOX;
404 case SliderRole:
405 return ATK_ROLE_SLIDER;
406 case TabGroupRole:
407 return ATK_ROLE_PAGE_TAB_LIST;
408 case TextFieldRole:
409 case TextAreaRole:
410 return ATK_ROLE_ENTRY;
411 case StaticTextRole:
412 return ATK_ROLE_TEXT;
413 case OutlineRole:
414 return ATK_ROLE_TREE;
415 case MenuBarRole:
416 return ATK_ROLE_MENU_BAR;
417 case MenuListPopupRole:
418 case MenuRole:
419 return ATK_ROLE_MENU;
420 case MenuListOptionRole:
421 case MenuItemRole:
422 return ATK_ROLE_MENU_ITEM;
423 case ColumnRole:
424 //return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right?
425 return ATK_ROLE_UNKNOWN; // Matches Mozilla
426 case RowRole:
427 //return ATK_ROLE_TABLE_ROW_HEADER; // Is this right?
428 return ATK_ROLE_LIST_ITEM; // Matches Mozilla
429 case ToolbarRole:
430 return ATK_ROLE_TOOL_BAR;
431 case BusyIndicatorRole:
432 return ATK_ROLE_PROGRESS_BAR; // Is this right?
433 case ProgressIndicatorRole:
434 //return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp
435 return ATK_ROLE_PROGRESS_BAR;
436 case WindowRole:
437 return ATK_ROLE_WINDOW;
438 case PopUpButtonRole:
439 case ComboBoxRole:
440 return ATK_ROLE_COMBO_BOX;
441 case SplitGroupRole:
442 return ATK_ROLE_SPLIT_PANE;
443 case SplitterRole:
444 return ATK_ROLE_SEPARATOR;
445 case ColorWellRole:
446 return ATK_ROLE_COLOR_CHOOSER;
447 case ListRole:
448 return ATK_ROLE_LIST;
449 case ScrollBarRole:
450 return ATK_ROLE_SCROLL_BAR;
451 case ScrollAreaRole:
452 return ATK_ROLE_SCROLL_PANE;
453 case GridRole: // Is this right?
454 case TableRole:
455 return ATK_ROLE_TABLE;
456 case ApplicationRole:
457 return ATK_ROLE_APPLICATION;
458 case GroupRole:
459 case RadioGroupRole:
460 return ATK_ROLE_PANEL;
461 case RowHeaderRole: // Row headers are cells after all.
462 case ColumnHeaderRole: // Column headers are cells after all.
463 case CellRole:
464 return ATK_ROLE_TABLE_CELL;
465 case LinkRole:
466 case WebCoreLinkRole:
467 case ImageMapLinkRole:
468 return ATK_ROLE_LINK;
469 case ImageMapRole:
470 case ImageRole:
471 return ATK_ROLE_IMAGE;
472 case ListMarkerRole:
473 return ATK_ROLE_TEXT;
474 case WebAreaRole:
475 //return ATK_ROLE_HTML_CONTAINER; // Is this right?
476 return ATK_ROLE_DOCUMENT_FRAME;
477 case HeadingRole:
478 return ATK_ROLE_HEADING;
479 case ListBoxRole:
480 return ATK_ROLE_LIST;
481 case ListItemRole:
482 case ListBoxOptionRole:
483 return ATK_ROLE_LIST_ITEM;
484 case ParagraphRole:
485 return ATK_ROLE_PARAGRAPH;
486 case LabelRole:
487 return ATK_ROLE_LABEL;
488 case DivRole:
489 return ATK_ROLE_SECTION;
490 case FormRole:
491 return ATK_ROLE_FORM;
492 default:
493 return ATK_ROLE_UNKNOWN;
494 }
495 }
496
webkit_accessible_get_role(AtkObject * object)497 static AtkRole webkit_accessible_get_role(AtkObject* object)
498 {
499 AccessibilityObject* coreObject = core(object);
500
501 if (!coreObject)
502 return ATK_ROLE_UNKNOWN;
503
504 // Note: Why doesn't WebCore have a password field for this
505 if (coreObject->isPasswordField())
506 return ATK_ROLE_PASSWORD_TEXT;
507
508 return atkRole(coreObject->roleValue());
509 }
510
selectionBelongsToObject(AccessibilityObject * coreObject,VisibleSelection & selection)511 static bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection)
512 {
513 if (!coreObject || !coreObject->isAccessibilityRenderObject())
514 return false;
515
516 if (selection.isNone())
517 return false;
518
519 RefPtr<Range> range = selection.toNormalizedRange();
520 if (!range)
521 return false;
522
523 // We want to check that both the selection intersects the node
524 // AND that the selection is not just "touching" one of the
525 // boundaries for the selected node. We want to check whether the
526 // node is actually inside the region, at least partially.
527 Node* node = coreObject->node();
528 Node* lastDescendant = node->lastDescendant();
529 ExceptionCode ec = 0;
530 return (range->intersectsNode(node, ec)
531 && (range->endContainer() != node || range->endOffset())
532 && (range->startContainer() != lastDescendant || range->startOffset() != lastOffsetInNode(lastDescendant)));
533 }
534
isTextWithCaret(AccessibilityObject * coreObject)535 static bool isTextWithCaret(AccessibilityObject* coreObject)
536 {
537 if (!coreObject || !coreObject->isAccessibilityRenderObject())
538 return false;
539
540 Document* document = coreObject->document();
541 if (!document)
542 return false;
543
544 Frame* frame = document->frame();
545 if (!frame)
546 return false;
547
548 Settings* settings = frame->settings();
549 if (!settings || !settings->caretBrowsingEnabled())
550 return false;
551
552 // Check text objects and paragraphs only.
553 AtkObject* axObject = coreObject->wrapper();
554 AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID;
555 if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH)
556 return false;
557
558 // Finally, check whether the caret is set in the current object.
559 VisibleSelection selection = coreObject->selection();
560 if (!selection.isCaret())
561 return false;
562
563 return selectionBelongsToObject(coreObject, selection);
564 }
565
setAtkStateSetFromCoreObject(AccessibilityObject * coreObject,AtkStateSet * stateSet)566 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet)
567 {
568 AccessibilityObject* parent = coreObject->parentObject();
569 bool isListBoxOption = parent && parent->isListBox();
570
571 // Please keep the state list in alphabetical order
572 if (coreObject->isChecked())
573 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED);
574
575 // FIXME: isReadOnly does not seem to do the right thing for
576 // controls, so check explicitly for them. In addition, because
577 // isReadOnly is false for listBoxOptions, we need to add one
578 // more check so that we do not present them as being "editable".
579 if ((!coreObject->isReadOnly() ||
580 (coreObject->isControl() && coreObject->canSetValueAttribute())) &&
581 !isListBoxOption)
582 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE);
583
584 // FIXME: Put both ENABLED and SENSITIVE together here for now
585 if (coreObject->isEnabled()) {
586 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED);
587 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE);
588 }
589
590 if (coreObject->canSetExpandedAttribute())
591 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE);
592
593 if (coreObject->isExpanded())
594 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED);
595
596 if (coreObject->canSetFocusAttribute())
597 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
598
599 if (coreObject->isFocused() || isTextWithCaret(coreObject))
600 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
601
602 // TODO: ATK_STATE_HORIZONTAL
603
604 if (coreObject->isIndeterminate())
605 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE);
606
607 if (coreObject->isMultiSelectable())
608 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE);
609
610 // TODO: ATK_STATE_OPAQUE
611
612 if (coreObject->isPressed())
613 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED);
614
615 // TODO: ATK_STATE_SELECTABLE_TEXT
616
617 if (coreObject->canSetSelectedAttribute()) {
618 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE);
619 // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED}
620 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
621 // former.
622 if (isListBoxOption)
623 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
624 }
625
626 if (coreObject->isSelected()) {
627 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED);
628 // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED}
629 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the
630 // former.
631 if (isListBoxOption)
632 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED);
633 }
634
635 // FIXME: Group both SHOWING and VISIBLE here for now
636 // Not sure how to handle this in WebKit, see bug
637 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other
638 // issues with SHOWING vs VISIBLE within GTK+
639 if (!coreObject->isOffScreen()) {
640 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING);
641 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE);
642 }
643
644 // Mutually exclusive, so we group these two
645 if (coreObject->roleValue() == TextFieldRole)
646 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE);
647 else if (coreObject->roleValue() == TextAreaRole)
648 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE);
649
650 // TODO: ATK_STATE_SENSITIVE
651
652 // TODO: ATK_STATE_VERTICAL
653
654 if (coreObject->isVisited())
655 atk_state_set_add_state(stateSet, ATK_STATE_VISITED);
656 }
657
webkit_accessible_ref_state_set(AtkObject * object)658 static AtkStateSet* webkit_accessible_ref_state_set(AtkObject* object)
659 {
660 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object);
661 AccessibilityObject* coreObject = core(object);
662
663 if (coreObject == fallbackObject()) {
664 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT);
665 return stateSet;
666 }
667
668 // Text objects must be focusable.
669 AtkRole role = atk_object_get_role(object);
670 if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH)
671 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE);
672
673 setAtkStateSetFromCoreObject(coreObject, stateSet);
674 return stateSet;
675 }
676
webkit_accessible_ref_relation_set(AtkObject * object)677 static AtkRelationSet* webkit_accessible_ref_relation_set(AtkObject* object)
678 {
679 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object);
680 AccessibilityObject* coreObject = core(object);
681
682 setAtkRelationSetFromCoreObject(coreObject, relationSet);
683
684 return relationSet;
685 }
686
webkit_accessible_init(AtkObject * object,gpointer data)687 static void webkit_accessible_init(AtkObject* object, gpointer data)
688 {
689 if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize)
690 ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data);
691
692 WEBKIT_ACCESSIBLE(object)->m_object = reinterpret_cast<AccessibilityObject*>(data);
693 }
694
webkit_accessible_finalize(GObject * object)695 static void webkit_accessible_finalize(GObject* object)
696 {
697 // This is a good time to clear the return buffer.
698 returnString(String());
699
700 G_OBJECT_CLASS(webkit_accessible_parent_class)->finalize(object);
701 }
702
webkit_accessible_class_init(AtkObjectClass * klass)703 static void webkit_accessible_class_init(AtkObjectClass* klass)
704 {
705 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass);
706
707 webkit_accessible_parent_class = g_type_class_peek_parent(klass);
708
709 gobjectClass->finalize = webkit_accessible_finalize;
710
711 klass->initialize = webkit_accessible_init;
712 klass->get_name = webkit_accessible_get_name;
713 klass->get_description = webkit_accessible_get_description;
714 klass->get_parent = webkit_accessible_get_parent;
715 klass->get_n_children = webkit_accessible_get_n_children;
716 klass->ref_child = webkit_accessible_ref_child;
717 klass->get_role = webkit_accessible_get_role;
718 klass->ref_state_set = webkit_accessible_ref_state_set;
719 klass->get_index_in_parent = webkit_accessible_get_index_in_parent;
720 klass->get_attributes = webkit_accessible_get_attributes;
721 klass->ref_relation_set = webkit_accessible_ref_relation_set;
722 }
723
724 GType
webkit_accessible_get_type(void)725 webkit_accessible_get_type(void)
726 {
727 static volatile gsize type_volatile = 0;
728
729 if (g_once_init_enter(&type_volatile)) {
730 static const GTypeInfo tinfo = {
731 sizeof(WebKitAccessibleClass),
732 (GBaseInitFunc) 0,
733 (GBaseFinalizeFunc) 0,
734 (GClassInitFunc) webkit_accessible_class_init,
735 (GClassFinalizeFunc) 0,
736 0, /* class data */
737 sizeof(WebKitAccessible), /* instance size */
738 0, /* nb preallocs */
739 (GInstanceInitFunc) 0,
740 0 /* value table */
741 };
742
743 GType type = g_type_register_static(ATK_TYPE_OBJECT,
744 "WebKitAccessible", &tinfo, GTypeFlags(0));
745 g_once_init_leave(&type_volatile, type);
746 }
747
748 return type_volatile;
749 }
750
webkit_accessible_action_do_action(AtkAction * action,gint i)751 static gboolean webkit_accessible_action_do_action(AtkAction* action, gint i)
752 {
753 g_return_val_if_fail(i == 0, FALSE);
754 return core(action)->performDefaultAction();
755 }
756
webkit_accessible_action_get_n_actions(AtkAction * action)757 static gint webkit_accessible_action_get_n_actions(AtkAction* action)
758 {
759 return 1;
760 }
761
webkit_accessible_action_get_description(AtkAction * action,gint i)762 static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i)
763 {
764 g_return_val_if_fail(i == 0, 0);
765 // TODO: Need a way to provide/localize action descriptions.
766 notImplemented();
767 return "";
768 }
769
webkit_accessible_action_get_keybinding(AtkAction * action,gint i)770 static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i)
771 {
772 g_return_val_if_fail(i == 0, 0);
773 // FIXME: Construct a proper keybinding string.
774 return returnString(core(action)->accessKey().string());
775 }
776
webkit_accessible_action_get_name(AtkAction * action,gint i)777 static const gchar* webkit_accessible_action_get_name(AtkAction* action, gint i)
778 {
779 g_return_val_if_fail(i == 0, 0);
780 return returnString(core(action)->actionVerb());
781 }
782
atk_action_interface_init(AtkActionIface * iface)783 static void atk_action_interface_init(AtkActionIface* iface)
784 {
785 iface->do_action = webkit_accessible_action_do_action;
786 iface->get_n_actions = webkit_accessible_action_get_n_actions;
787 iface->get_description = webkit_accessible_action_get_description;
788 iface->get_keybinding = webkit_accessible_action_get_keybinding;
789 iface->get_name = webkit_accessible_action_get_name;
790 }
791
792 // Selection (for controls)
793
listObjectForSelection(AtkSelection * selection)794 static AccessibilityObject* listObjectForSelection(AtkSelection* selection)
795 {
796 AccessibilityObject* coreSelection = core(selection);
797
798 // Only list boxes and menu lists supported so far.
799 if (!coreSelection->isListBox() && !coreSelection->isMenuList())
800 return 0;
801
802 // For list boxes the list object is just itself.
803 if (coreSelection->isListBox())
804 return coreSelection;
805
806 // For menu lists we need to return the first accessible child,
807 // with role MenuListPopupRole, since that's the one holding the list
808 // of items with role MenuListOptionRole.
809 AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children();
810 if (!children.size())
811 return 0;
812
813 AccessibilityObject* listObject = children.at(0).get();
814 if (!listObject->isMenuListPopup())
815 return 0;
816
817 return listObject;
818 }
819
optionFromList(AtkSelection * selection,gint i)820 static AccessibilityObject* optionFromList(AtkSelection* selection, gint i)
821 {
822 AccessibilityObject* coreSelection = core(selection);
823 if (!coreSelection || i < 0)
824 return 0;
825
826 // Need to select the proper list object depending on the type.
827 AccessibilityObject* listObject = listObjectForSelection(selection);
828 if (!listObject)
829 return 0;
830
831 AccessibilityObject::AccessibilityChildrenVector options = listObject->children();
832 if (i < static_cast<gint>(options.size()))
833 return options.at(i).get();
834
835 return 0;
836 }
837
optionFromSelection(AtkSelection * selection,gint i)838 static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i)
839 {
840 // i is the ith selection as opposed to the ith child.
841
842 AccessibilityObject* coreSelection = core(selection);
843 if (!coreSelection || !coreSelection->isAccessibilityRenderObject() || i < 0)
844 return 0;
845
846 AccessibilityObject::AccessibilityChildrenVector selectedItems;
847 if (coreSelection->isListBox())
848 coreSelection->selectedChildren(selectedItems);
849 else if (coreSelection->isMenuList()) {
850 RenderObject* renderer = coreSelection->renderer();
851 if (!renderer)
852 return 0;
853
854 SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node()));
855 int selectedIndex = selectNode->selectedIndex();
856 const Vector<Element*> listItems = selectNode->listItems();
857
858 if (selectedIndex < 0 || selectedIndex >= static_cast<int>(listItems.size()))
859 return 0;
860
861 return optionFromList(selection, selectedIndex);
862 }
863
864 if (i < static_cast<gint>(selectedItems.size()))
865 return selectedItems.at(i).get();
866
867 return 0;
868 }
869
webkit_accessible_selection_add_selection(AtkSelection * selection,gint i)870 static gboolean webkit_accessible_selection_add_selection(AtkSelection* selection, gint i)
871 {
872 AccessibilityObject* coreSelection = core(selection);
873 if (!coreSelection)
874 return false;
875
876 AccessibilityObject* option = optionFromList(selection, i);
877 if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
878 option->setSelected(true);
879 return option->isSelected();
880 }
881
882 return false;
883 }
884
webkit_accessible_selection_clear_selection(AtkSelection * selection)885 static gboolean webkit_accessible_selection_clear_selection(AtkSelection* selection)
886 {
887 AccessibilityObject* coreSelection = core(selection);
888 if (!coreSelection)
889 return false;
890
891 AccessibilityObject::AccessibilityChildrenVector selectedItems;
892 if (coreSelection->isListBox() || coreSelection->isMenuList()) {
893 // Set the list of selected items to an empty list; then verify that it worked.
894 AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
895 listBox->setSelectedChildren(selectedItems);
896 listBox->selectedChildren(selectedItems);
897 return selectedItems.size() == 0;
898 }
899 return false;
900 }
901
webkit_accessible_selection_ref_selection(AtkSelection * selection,gint i)902 static AtkObject* webkit_accessible_selection_ref_selection(AtkSelection* selection, gint i)
903 {
904 AccessibilityObject* option = optionFromSelection(selection, i);
905 if (option) {
906 AtkObject* child = option->wrapper();
907 g_object_ref(child);
908 return child;
909 }
910
911 return 0;
912 }
913
webkit_accessible_selection_get_selection_count(AtkSelection * selection)914 static gint webkit_accessible_selection_get_selection_count(AtkSelection* selection)
915 {
916 AccessibilityObject* coreSelection = core(selection);
917 if (!coreSelection || !coreSelection->isAccessibilityRenderObject())
918 return 0;
919
920 if (coreSelection->isListBox()) {
921 AccessibilityObject::AccessibilityChildrenVector selectedItems;
922 coreSelection->selectedChildren(selectedItems);
923 return static_cast<gint>(selectedItems.size());
924 }
925
926 if (coreSelection->isMenuList()) {
927 RenderObject* renderer = coreSelection->renderer();
928 if (!renderer)
929 return 0;
930
931 SelectElement* selectNode = toSelectElement(static_cast<Element*>(renderer->node()));
932 int selectedIndex = selectNode->selectedIndex();
933 const Vector<Element*> listItems = selectNode->listItems();
934
935 return selectedIndex >= 0 && selectedIndex < static_cast<int>(listItems.size());
936 }
937
938 return 0;
939 }
940
webkit_accessible_selection_is_child_selected(AtkSelection * selection,gint i)941 static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i)
942 {
943 AccessibilityObject* coreSelection = core(selection);
944 if (!coreSelection)
945 return 0;
946
947 AccessibilityObject* option = optionFromList(selection, i);
948 if (option && (coreSelection->isListBox() || coreSelection->isMenuList()))
949 return option->isSelected();
950
951 return false;
952 }
953
webkit_accessible_selection_remove_selection(AtkSelection * selection,gint i)954 static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i)
955 {
956 AccessibilityObject* coreSelection = core(selection);
957 if (!coreSelection)
958 return 0;
959
960 // TODO: This is only getting called if i == 0. What is preventing the rest?
961 AccessibilityObject* option = optionFromSelection(selection, i);
962 if (option && (coreSelection->isListBox() || coreSelection->isMenuList())) {
963 option->setSelected(false);
964 return !option->isSelected();
965 }
966
967 return false;
968 }
969
webkit_accessible_selection_select_all_selection(AtkSelection * selection)970 static gboolean webkit_accessible_selection_select_all_selection(AtkSelection* selection)
971 {
972 AccessibilityObject* coreSelection = core(selection);
973 if (!coreSelection || !coreSelection->isMultiSelectable())
974 return false;
975
976 AccessibilityObject::AccessibilityChildrenVector children = coreSelection->children();
977 if (coreSelection->isListBox()) {
978 AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection);
979 listBox->setSelectedChildren(children);
980 AccessibilityObject::AccessibilityChildrenVector selectedItems;
981 listBox->selectedChildren(selectedItems);
982 return selectedItems.size() == children.size();
983 }
984
985 return false;
986 }
987
atk_selection_interface_init(AtkSelectionIface * iface)988 static void atk_selection_interface_init(AtkSelectionIface* iface)
989 {
990 iface->add_selection = webkit_accessible_selection_add_selection;
991 iface->clear_selection = webkit_accessible_selection_clear_selection;
992 iface->ref_selection = webkit_accessible_selection_ref_selection;
993 iface->get_selection_count = webkit_accessible_selection_get_selection_count;
994 iface->is_child_selected = webkit_accessible_selection_is_child_selected;
995 iface->remove_selection = webkit_accessible_selection_remove_selection;
996 iface->select_all_selection = webkit_accessible_selection_select_all_selection;
997 }
998
999 // Text
1000
utf8Substr(const gchar * string,gint start,gint end)1001 static gchar* utf8Substr(const gchar* string, gint start, gint end)
1002 {
1003 ASSERT(string);
1004 glong strLen = g_utf8_strlen(string, -1);
1005 if (start > strLen || end > strLen)
1006 return 0;
1007 gchar* startPtr = g_utf8_offset_to_pointer(string, start);
1008 gsize lenInBytes = g_utf8_offset_to_pointer(string, end + 1) - startPtr;
1009 gchar* output = static_cast<gchar*>(g_malloc0(lenInBytes + 1));
1010 return g_utf8_strncpy(output, startPtr, end - start + 1);
1011 }
1012
1013 // This function is not completely general, is it's tied to the
1014 // internals of WebCore's text presentation.
convertUniCharToUTF8(const UChar * characters,gint length,int from,int to)1015 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
1016 {
1017 CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables);
1018 gchar* utf8String = utf8Substr(stringUTF8.data(), from, to);
1019 if (!g_utf8_validate(utf8String, -1, 0)) {
1020 g_free(utf8String);
1021 return 0;
1022 }
1023 gsize len = strlen(utf8String);
1024 GString* ret = g_string_new_len(0, len);
1025 gchar* ptr = utf8String;
1026
1027 // WebCore introduces line breaks in the text that do not reflect
1028 // the layout you see on the screen, replace them with spaces
1029 while (len > 0) {
1030 gint index, start;
1031 pango_find_paragraph_boundary(ptr, len, &index, &start);
1032 g_string_append_len(ret, ptr, index);
1033 if (index == start)
1034 break;
1035 g_string_append_c(ret, ' ');
1036 ptr += start;
1037 len -= start;
1038 }
1039
1040 g_free(utf8String);
1041 return g_string_free(ret, FALSE);
1042 }
1043
textForRenderer(RenderObject * renderer)1044 gchar* textForRenderer(RenderObject* renderer)
1045 {
1046 GString* resultText = g_string_new(0);
1047
1048 if (!renderer)
1049 return g_string_free(resultText, FALSE);
1050
1051 // For RenderBlocks, piece together the text from the RenderText objects they contain.
1052 for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) {
1053 if (object->isBR()) {
1054 g_string_append(resultText, "\n");
1055 continue;
1056 }
1057
1058 RenderText* renderText;
1059 if (object->isText())
1060 renderText = toRenderText(object);
1061 else {
1062 if (object->isReplaced())
1063 g_string_append_unichar(resultText, objectReplacementCharacter);
1064
1065 // We need to check children, if any, to consider when
1066 // current object is not a text object but some of its
1067 // children are, in order not to miss those portions of
1068 // text by not properly handling those situations
1069 if (object->firstChild())
1070 g_string_append(resultText, textForRenderer(object));
1071
1072 continue;
1073 }
1074
1075 InlineTextBox* box = renderText ? renderText->firstTextBox() : 0;
1076 while (box) {
1077 gchar* text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end());
1078 g_string_append(resultText, text);
1079 // Newline chars in the source result in separate text boxes, so check
1080 // before adding a newline in the layout. See bug 25415 comment #78.
1081 // If the next sibling is a BR, we'll add the newline when we examine that child.
1082 if (!box->nextOnLineExists() && (!object->nextSibling() || !object->nextSibling()->isBR()))
1083 g_string_append(resultText, "\n");
1084 box = box->nextTextBox();
1085 }
1086 }
1087
1088 // Insert the text of the marker for list item in the right place, if present
1089 if (renderer->isListItem()) {
1090 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1091 if (renderer->style()->direction() == LTR)
1092 g_string_prepend(resultText, markerText.utf8().data());
1093 else
1094 g_string_append(resultText, markerText.utf8().data());
1095 }
1096
1097 return g_string_free(resultText, FALSE);
1098 }
1099
textForObject(AccessibilityObject * coreObject)1100 gchar* textForObject(AccessibilityObject* coreObject)
1101 {
1102 GString* str = g_string_new(0);
1103
1104 // For text controls, we can get the text line by line.
1105 if (coreObject->isTextControl()) {
1106 unsigned textLength = coreObject->textLength();
1107 int lineNumber = 0;
1108 PlainTextRange range = coreObject->doAXRangeForLine(lineNumber);
1109 while (range.length) {
1110 // When a line of text wraps in a text area, the final space is removed.
1111 if (range.start + range.length < textLength)
1112 range.length -= 1;
1113 String lineText = coreObject->doAXStringForRange(range);
1114 g_string_append(str, lineText.utf8().data());
1115 g_string_append(str, "\n");
1116 range = coreObject->doAXRangeForLine(++lineNumber);
1117 }
1118 } else if (coreObject->isAccessibilityRenderObject()) {
1119 GOwnPtr<gchar> rendererText(textForRenderer(coreObject->renderer()));
1120 g_string_append(str, rendererText.get());
1121 }
1122
1123 return g_string_free(str, FALSE);
1124 }
1125
webkit_accessible_text_get_text(AtkText * text,gint startOffset,gint endOffset)1126 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset)
1127 {
1128 AccessibilityObject* coreObject = core(text);
1129
1130 int end = endOffset;
1131 if (endOffset == -1) {
1132 end = coreObject->stringValue().length();
1133 if (!end)
1134 end = coreObject->textUnderElement().length();
1135 }
1136
1137 String ret;
1138 if (coreObject->isTextControl())
1139 ret = coreObject->doAXStringForRange(PlainTextRange(0, endOffset));
1140 else {
1141 ret = coreObject->stringValue();
1142 if (!ret)
1143 ret = coreObject->textUnderElement();
1144 }
1145
1146 if (!ret.length()) {
1147 // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs)
1148 ret = String(textForObject(coreObject));
1149 if (!end)
1150 end = ret.length();
1151 }
1152
1153 // Prefix a item number/bullet if needed
1154 if (coreObject->roleValue() == ListItemRole) {
1155 RenderObject* objRenderer = coreObject->renderer();
1156 if (objRenderer && objRenderer->isListItem()) {
1157 String markerText = toRenderListItem(objRenderer)->markerTextWithSuffix();
1158 ret = objRenderer->style()->direction() == LTR ? markerText + ret : ret + markerText;
1159 if (endOffset == -1)
1160 end += markerText.length();
1161 }
1162 }
1163
1164 ret = ret.substring(startOffset, end - startOffset);
1165 return g_strdup(ret.utf8().data());
1166 }
1167
getGailTextUtilForAtk(AtkText * textObject)1168 static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject)
1169 {
1170 gpointer data = g_object_get_data(G_OBJECT(textObject), "webkit-accessible-gail-text-util");
1171 if (data)
1172 return static_cast<GailTextUtil*>(data);
1173
1174 GailTextUtil* gailTextUtil = gail_text_util_new();
1175 gail_text_util_text_setup(gailTextUtil, webkit_accessible_text_get_text(textObject, 0, -1));
1176 g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-gail-text-util", gailTextUtil, g_object_unref);
1177 return gailTextUtil;
1178 }
1179
getPangoLayoutForAtk(AtkText * textObject)1180 static PangoLayout* getPangoLayoutForAtk(AtkText* textObject)
1181 {
1182 AccessibilityObject* coreObject = core(textObject);
1183
1184 Document* document = coreObject->document();
1185 if (!document)
1186 return 0;
1187
1188 HostWindow* hostWindow = document->view()->hostWindow();
1189 if (!hostWindow)
1190 return 0;
1191 PlatformPageClient webView = hostWindow->platformPageClient();
1192 if (!webView)
1193 return 0;
1194
1195 // Create a string with the layout as it appears on the screen
1196 PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(coreObject));
1197 g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref);
1198 return layout;
1199 }
1200
webkit_accessible_text_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundaryType,gint * startOffset,gint * endOffset)1201 static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1202 {
1203 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset);
1204 }
1205
webkit_accessible_text_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundaryType,gint * startOffset,gint * endOffset)1206 static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1207 {
1208 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset);
1209 }
1210
webkit_accessible_text_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundaryType,gint * startOffset,gint * endOffset)1211 static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset)
1212 {
1213 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset);
1214 }
1215
webkit_accessible_text_get_character_at_offset(AtkText * text,gint offset)1216 static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset)
1217 {
1218 notImplemented();
1219 return 0;
1220 }
1221
webkit_accessible_text_get_caret_offset(AtkText * text)1222 static gint webkit_accessible_text_get_caret_offset(AtkText* text)
1223 {
1224 // coreObject is the unignored object whose offset the caller is requesting.
1225 // focusedObject is the object with the caret. It is likely ignored -- unless it's a link.
1226 AccessibilityObject* coreObject = core(text);
1227 if (!coreObject->isAccessibilityRenderObject())
1228 return 0;
1229
1230 Document* document = coreObject->document();
1231 if (!document)
1232 return 0;
1233
1234 Node* focusedNode = coreObject->selection().end().deprecatedNode();
1235 if (!focusedNode)
1236 return 0;
1237
1238 RenderObject* focusedRenderer = focusedNode->renderer();
1239 AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer);
1240
1241 int offset;
1242 // Don't ignore links if the offset is being requested for a link.
1243 if (!objectAndOffsetUnignored(focusedObject, offset, !coreObject->isLink()))
1244 return 0;
1245
1246 RenderObject* renderer = coreObject->renderer();
1247 if (renderer && renderer->isListItem()) {
1248 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1249
1250 // We need to adjust the offset for the list item marker.
1251 offset += markerText.length();
1252 }
1253
1254 // TODO: Verify this for RTL text.
1255 return offset;
1256 }
1257
baselinePositionForRenderObject(RenderObject * renderObject)1258 static int baselinePositionForRenderObject(RenderObject* renderObject)
1259 {
1260 // FIXME: This implementation of baselinePosition originates from RenderObject.cpp and was
1261 // removed in r70072. The implementation looks incorrect though, because this is not the
1262 // baseline of the underlying RenderObject, but of the AccessibilityRenderObject.
1263 const FontMetrics& fontMetrics = renderObject->firstLineStyle()->fontMetrics();
1264 return fontMetrics.ascent() + (renderObject->firstLineStyle()->computedLineHeight() - fontMetrics.height()) / 2;
1265 }
1266
getAttributeSetForAccessibilityObject(const AccessibilityObject * object)1267 static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object)
1268 {
1269 if (!object->isAccessibilityRenderObject())
1270 return 0;
1271
1272 RenderObject* renderer = object->renderer();
1273 RenderStyle* style = renderer->style();
1274
1275 AtkAttributeSet* result = 0;
1276 GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize()));
1277 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get());
1278
1279 Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor);
1280 if (bgColor.isValid()) {
1281 buffer.set(g_strdup_printf("%i,%i,%i",
1282 bgColor.red(), bgColor.green(), bgColor.blue()));
1283 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get());
1284 }
1285
1286 Color fgColor = style->visitedDependentColor(CSSPropertyColor);
1287 if (fgColor.isValid()) {
1288 buffer.set(g_strdup_printf("%i,%i,%i",
1289 fgColor.red(), fgColor.green(), fgColor.blue()));
1290 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get());
1291 }
1292
1293 int baselinePosition;
1294 bool includeRise = true;
1295 switch (style->verticalAlign()) {
1296 case SUB:
1297 baselinePosition = -1 * baselinePositionForRenderObject(renderer);
1298 break;
1299 case SUPER:
1300 baselinePosition = baselinePositionForRenderObject(renderer);
1301 break;
1302 case BASELINE:
1303 baselinePosition = 0;
1304 break;
1305 default:
1306 includeRise = false;
1307 break;
1308 }
1309
1310 if (includeRise) {
1311 buffer.set(g_strdup_printf("%i", baselinePosition));
1312 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get());
1313 }
1314
1315 int indentation = style->textIndent().calcValue(object->size().width());
1316 if (indentation != undefinedLength) {
1317 buffer.set(g_strdup_printf("%i", indentation));
1318 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get());
1319 }
1320
1321 String fontFamilyName = style->font().family().family().string();
1322 if (fontFamilyName.left(8) == "-webkit-")
1323 fontFamilyName = fontFamilyName.substring(8);
1324
1325 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data());
1326
1327 int fontWeight = -1;
1328 switch (style->font().weight()) {
1329 case FontWeight100:
1330 fontWeight = 100;
1331 break;
1332 case FontWeight200:
1333 fontWeight = 200;
1334 break;
1335 case FontWeight300:
1336 fontWeight = 300;
1337 break;
1338 case FontWeight400:
1339 fontWeight = 400;
1340 break;
1341 case FontWeight500:
1342 fontWeight = 500;
1343 break;
1344 case FontWeight600:
1345 fontWeight = 600;
1346 break;
1347 case FontWeight700:
1348 fontWeight = 700;
1349 break;
1350 case FontWeight800:
1351 fontWeight = 800;
1352 break;
1353 case FontWeight900:
1354 fontWeight = 900;
1355 }
1356 if (fontWeight > 0) {
1357 buffer.set(g_strdup_printf("%i", fontWeight));
1358 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get());
1359 }
1360
1361 switch (style->textAlign()) {
1362 case TAAUTO:
1363 case TASTART:
1364 case TAEND:
1365 break;
1366 case LEFT:
1367 case WEBKIT_LEFT:
1368 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left");
1369 break;
1370 case RIGHT:
1371 case WEBKIT_RIGHT:
1372 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right");
1373 break;
1374 case CENTER:
1375 case WEBKIT_CENTER:
1376 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center");
1377 break;
1378 case JUSTIFY:
1379 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill");
1380 }
1381
1382 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none");
1383
1384 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal");
1385
1386 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false");
1387
1388 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false");
1389
1390 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true");
1391
1392 return result;
1393 }
1394
compareAttribute(const AtkAttribute * a,const AtkAttribute * b)1395 static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b)
1396 {
1397 return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value);
1398 }
1399
1400 // Returns an AtkAttributeSet with the elements of a1 which are either
1401 // not present or different in a2. Neither a1 nor a2 should be used
1402 // after calling this function.
attributeSetDifference(AtkAttributeSet * a1,AtkAttributeSet * a2)1403 static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* a1, AtkAttributeSet* a2)
1404 {
1405 if (!a2)
1406 return a1;
1407
1408 AtkAttributeSet* i = a1;
1409 AtkAttributeSet* found;
1410 AtkAttributeSet* toDelete = 0;
1411
1412 while (i) {
1413 found = g_slist_find_custom(a2, i->data, (GCompareFunc)compareAttribute);
1414 if (found) {
1415 AtkAttributeSet* t = i->next;
1416 toDelete = g_slist_prepend(toDelete, i->data);
1417 a1 = g_slist_delete_link(a1, i);
1418 i = t;
1419 } else
1420 i = i->next;
1421 }
1422
1423 atk_attribute_set_free(a2);
1424 atk_attribute_set_free(toDelete);
1425 return a1;
1426 }
1427
accessibilityObjectLength(const AccessibilityObject * object)1428 static guint accessibilityObjectLength(const AccessibilityObject* object)
1429 {
1430 // Non render objects are not taken into account
1431 if (!object->isAccessibilityRenderObject())
1432 return 0;
1433
1434 // For those objects implementing the AtkText interface we use the
1435 // well known API to always get the text in a consistent way
1436 AtkObject* atkObj = ATK_OBJECT(object->wrapper());
1437 if (ATK_IS_TEXT(atkObj)) {
1438 GOwnPtr<gchar> text(webkit_accessible_text_get_text(ATK_TEXT(atkObj), 0, -1));
1439 return g_utf8_strlen(text.get(), -1);
1440 }
1441
1442 // Even if we don't expose list markers to Assistive
1443 // Technologies, we need to have a way to measure their length
1444 // for those cases when it's needed to take it into account
1445 // separately (as in getAccessibilityObjectForOffset)
1446 RenderObject* renderer = object->renderer();
1447 if (renderer && renderer->isListMarker()) {
1448 RenderListMarker* marker = toRenderListMarker(renderer);
1449 return marker->text().length() + marker->suffix().length();
1450 }
1451
1452 return 0;
1453 }
1454
getAccessibilityObjectForOffset(const AccessibilityObject * object,guint offset,gint * startOffset,gint * endOffset)1455 static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset)
1456 {
1457 const AccessibilityObject* result;
1458 guint length = accessibilityObjectLength(object);
1459 if (length > offset) {
1460 *startOffset = 0;
1461 *endOffset = length;
1462 result = object;
1463 } else {
1464 *startOffset = -1;
1465 *endOffset = -1;
1466 result = 0;
1467 }
1468
1469 if (!object->firstChild())
1470 return result;
1471
1472 AccessibilityObject* child = object->firstChild();
1473 guint currentOffset = 0;
1474 guint childPosition = 0;
1475 while (child && currentOffset <= offset) {
1476 guint childLength = accessibilityObjectLength(child);
1477 currentOffset = childLength + childPosition;
1478 if (currentOffset > offset) {
1479 gint childStartOffset;
1480 gint childEndOffset;
1481 const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition, &childStartOffset, &childEndOffset);
1482 if (childStartOffset >= 0) {
1483 *startOffset = childStartOffset + childPosition;
1484 *endOffset = childEndOffset + childPosition;
1485 result = grandChild;
1486 }
1487 } else {
1488 childPosition += childLength;
1489 child = child->nextSibling();
1490 }
1491 }
1492 return result;
1493 }
1494
getRunAttributesFromAccesibilityObject(const AccessibilityObject * element,gint offset,gint * startOffset,gint * endOffset)1495 static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset)
1496 {
1497 const AccessibilityObject *child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset);
1498 if (!child) {
1499 *startOffset = -1;
1500 *endOffset = -1;
1501 return 0;
1502 }
1503
1504 AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element);
1505 AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child);
1506
1507 return attributeSetDifference(childAttributes, defaultAttributes);
1508 }
1509
webkit_accessible_text_get_run_attributes(AtkText * text,gint offset,gint * startOffset,gint * endOffset)1510 static AtkAttributeSet* webkit_accessible_text_get_run_attributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset)
1511 {
1512 AccessibilityObject* coreObject = core(text);
1513 AtkAttributeSet* result;
1514
1515 if (!coreObject) {
1516 *startOffset = 0;
1517 *endOffset = atk_text_get_character_count(text);
1518 return 0;
1519 }
1520
1521 if (offset == -1)
1522 offset = atk_text_get_caret_offset(text);
1523
1524 result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset);
1525
1526 if (*startOffset < 0) {
1527 *startOffset = offset;
1528 *endOffset = offset;
1529 }
1530
1531 return result;
1532 }
1533
webkit_accessible_text_get_default_attributes(AtkText * text)1534 static AtkAttributeSet* webkit_accessible_text_get_default_attributes(AtkText* text)
1535 {
1536 AccessibilityObject* coreObject = core(text);
1537 if (!coreObject || !coreObject->isAccessibilityRenderObject())
1538 return 0;
1539
1540 return getAttributeSetForAccessibilityObject(coreObject);
1541 }
1542
textExtents(AtkText * text,gint startOffset,gint length,AtkCoordType coords)1543 static IntRect textExtents(AtkText* text, gint startOffset, gint length, AtkCoordType coords)
1544 {
1545 gchar* textContent = webkit_accessible_text_get_text(text, startOffset, -1);
1546 gint textLength = g_utf8_strlen(textContent, -1);
1547
1548 // The first case (endOffset of -1) should work, but seems broken for all Gtk+ apps.
1549 gint rangeLength = length;
1550 if (rangeLength < 0 || rangeLength > textLength)
1551 rangeLength = textLength;
1552 AccessibilityObject* coreObject = core(text);
1553
1554 IntRect extents = coreObject->doAXBoundsForRange(PlainTextRange(startOffset, rangeLength));
1555 switch(coords) {
1556 case ATK_XY_SCREEN:
1557 if (Document* document = coreObject->document())
1558 extents = document->view()->contentsToScreen(extents);
1559 break;
1560 case ATK_XY_WINDOW:
1561 // No-op
1562 break;
1563 }
1564
1565 return extents;
1566 }
1567
webkit_accessible_text_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)1568 static void webkit_accessible_text_get_character_extents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords)
1569 {
1570 IntRect extents = textExtents(text, offset, 1, coords);
1571 *x = extents.x();
1572 *y = extents.y();
1573 *width = extents.width();
1574 *height = extents.height();
1575 }
1576
webkit_accessible_text_get_range_extents(AtkText * text,gint startOffset,gint endOffset,AtkCoordType coords,AtkTextRectangle * rect)1577 static void webkit_accessible_text_get_range_extents(AtkText* text, gint startOffset, gint endOffset, AtkCoordType coords, AtkTextRectangle* rect)
1578 {
1579 IntRect extents = textExtents(text, startOffset, endOffset - startOffset, coords);
1580 rect->x = extents.x();
1581 rect->y = extents.y();
1582 rect->width = extents.width();
1583 rect->height = extents.height();
1584 }
1585
webkit_accessible_text_get_character_count(AtkText * text)1586 static gint webkit_accessible_text_get_character_count(AtkText* text)
1587 {
1588 return accessibilityObjectLength(core(text));
1589 }
1590
webkit_accessible_text_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)1591 static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gint y, AtkCoordType coords)
1592 {
1593 // FIXME: Use the AtkCoordType
1594 // TODO: Is it correct to ignore range.length?
1595 IntPoint pos(x, y);
1596 PlainTextRange range = core(text)->doAXRangeForPosition(pos);
1597 return range.start;
1598 }
1599
getSelectionOffsetsForObject(AccessibilityObject * coreObject,VisibleSelection & selection,gint & startOffset,gint & endOffset)1600 static void getSelectionOffsetsForObject(AccessibilityObject* coreObject, VisibleSelection& selection, gint& startOffset, gint& endOffset)
1601 {
1602 if (!coreObject->isAccessibilityRenderObject())
1603 return;
1604
1605 // Early return if the selection doesn't affect the selected node.
1606 if (!selectionBelongsToObject(coreObject, selection))
1607 return;
1608
1609 // We need to find the exact start and end positions in the
1610 // selected node that intersects the selection, to later on get
1611 // the right values for the effective start and end offsets.
1612 ExceptionCode ec = 0;
1613 Position nodeRangeStart;
1614 Position nodeRangeEnd;
1615 Node* node = coreObject->node();
1616 RefPtr<Range> selRange = selection.toNormalizedRange();
1617
1618 // If the selection affects the selected node and its first
1619 // possible position is also in the selection, we must set
1620 // nodeRangeStart to that position, otherwise to the selection's
1621 // start position (it would belong to the node anyway).
1622 Node* firstLeafNode = node->firstDescendant();
1623 if (selRange->isPointInRange(firstLeafNode, 0, ec))
1624 nodeRangeStart = firstPositionInOrBeforeNode(firstLeafNode);
1625 else
1626 nodeRangeStart = selRange->startPosition();
1627
1628 // If the selection affects the selected node and its last
1629 // possible position is also in the selection, we must set
1630 // nodeRangeEnd to that position, otherwise to the selection's
1631 // end position (it would belong to the node anyway).
1632 Node* lastLeafNode = node->lastDescendant();
1633 if (selRange->isPointInRange(lastLeafNode, lastOffsetInNode(lastLeafNode), ec))
1634 nodeRangeEnd = lastPositionInOrAfterNode(lastLeafNode);
1635 else
1636 nodeRangeEnd = selRange->endPosition();
1637
1638 // Calculate position of the selected range inside the object.
1639 Position parentFirstPosition = firstPositionInOrBeforeNode(node);
1640 RefPtr<Range> rangeInParent = Range::create(node->document(), parentFirstPosition, nodeRangeStart);
1641
1642 // Set values for start and end offsets.
1643 startOffset = TextIterator::rangeLength(rangeInParent.get(), true);
1644
1645 // We need to adjust the offsets for the list item marker.
1646 RenderObject* renderer = coreObject->renderer();
1647 if (renderer && renderer->isListItem()) {
1648 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1649 startOffset += markerText.length();
1650 }
1651
1652 RefPtr<Range> nodeRange = Range::create(node->document(), nodeRangeStart, nodeRangeEnd);
1653 endOffset = startOffset + TextIterator::rangeLength(nodeRange.get(), true);
1654 }
1655
webkit_accessible_text_get_n_selections(AtkText * text)1656 static gint webkit_accessible_text_get_n_selections(AtkText* text)
1657 {
1658 AccessibilityObject* coreObject = core(text);
1659 VisibleSelection selection = coreObject->selection();
1660
1661 // Only range selections are needed for the purpose of this method
1662 if (!selection.isRange())
1663 return 0;
1664
1665 // We don't support multiple selections for now, so there's only
1666 // two possibilities
1667 // Also, we don't want to do anything if the selection does not
1668 // belong to the currently selected object. We have to check since
1669 // there's no way to get the selection for a given object, only
1670 // the global one (the API is a bit confusing)
1671 return selectionBelongsToObject(coreObject, selection) ? 1 : 0;
1672 }
1673
webkit_accessible_text_get_selection(AtkText * text,gint selectionNum,gint * startOffset,gint * endOffset)1674 static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selectionNum, gint* startOffset, gint* endOffset)
1675 {
1676 // Default values, unless the contrary is proved
1677 *startOffset = *endOffset = 0;
1678
1679 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1680 if (selectionNum)
1681 return 0;
1682
1683 // Get the offsets of the selection for the selected object
1684 AccessibilityObject* coreObject = core(text);
1685 VisibleSelection selection = coreObject->selection();
1686 getSelectionOffsetsForObject(coreObject, selection, *startOffset, *endOffset);
1687
1688 // Return 0 instead of "", as that's the expected result for
1689 // this AtkText method when there's no selection
1690 if (*startOffset == *endOffset)
1691 return 0;
1692
1693 return webkit_accessible_text_get_text(text, *startOffset, *endOffset);
1694 }
1695
webkit_accessible_text_add_selection(AtkText * text,gint start_offset,gint end_offset)1696 static gboolean webkit_accessible_text_add_selection(AtkText* text, gint start_offset, gint end_offset)
1697 {
1698 notImplemented();
1699 return FALSE;
1700 }
1701
webkit_accessible_text_set_selection(AtkText * text,gint selectionNum,gint startOffset,gint endOffset)1702 static gboolean webkit_accessible_text_set_selection(AtkText* text, gint selectionNum, gint startOffset, gint endOffset)
1703 {
1704 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1705 if (selectionNum)
1706 return FALSE;
1707
1708 AccessibilityObject* coreObject = core(text);
1709 if (!coreObject->isAccessibilityRenderObject())
1710 return FALSE;
1711
1712 // Consider -1 and out-of-bound values and correct them to length
1713 gint textCount = webkit_accessible_text_get_character_count(text);
1714 if (startOffset < 0 || startOffset > textCount)
1715 startOffset = textCount;
1716 if (endOffset < 0 || endOffset > textCount)
1717 endOffset = textCount;
1718
1719 // We need to adjust the offsets for the list item marker.
1720 RenderObject* renderer = coreObject->renderer();
1721 if (renderer && renderer->isListItem()) {
1722 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1723 int markerLength = markerText.length();
1724 if (startOffset < markerLength || endOffset < markerLength)
1725 return FALSE;
1726
1727 startOffset -= markerLength;
1728 endOffset -= markerLength;
1729 }
1730
1731 PlainTextRange textRange(startOffset, endOffset - startOffset);
1732 VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1733 if (range.isNull())
1734 return FALSE;
1735
1736 coreObject->setSelectedVisiblePositionRange(range);
1737 return TRUE;
1738 }
1739
webkit_accessible_text_remove_selection(AtkText * text,gint selectionNum)1740 static gboolean webkit_accessible_text_remove_selection(AtkText* text, gint selectionNum)
1741 {
1742 // WebCore does not support multiple selection, so anything but 0 does not make sense for now.
1743 if (selectionNum)
1744 return FALSE;
1745
1746 // Do nothing if current selection doesn't belong to the object
1747 if (!webkit_accessible_text_get_n_selections(text))
1748 return FALSE;
1749
1750 // Set a new 0-sized selection to the caret position, in order
1751 // to simulate selection removal (GAIL style)
1752 gint caretOffset = webkit_accessible_text_get_caret_offset(text);
1753 return webkit_accessible_text_set_selection(text, selectionNum, caretOffset, caretOffset);
1754 }
1755
webkit_accessible_text_set_caret_offset(AtkText * text,gint offset)1756 static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset)
1757 {
1758 AccessibilityObject* coreObject = core(text);
1759
1760 if (!coreObject->isAccessibilityRenderObject())
1761 return FALSE;
1762
1763 RenderObject* renderer = coreObject->renderer();
1764 if (renderer && renderer->isListItem()) {
1765 String markerText = toRenderListItem(renderer)->markerTextWithSuffix();
1766 int markerLength = markerText.length();
1767 if (offset < markerLength)
1768 return FALSE;
1769
1770 // We need to adjust the offset for list items.
1771 offset -= markerLength;
1772 }
1773
1774 PlainTextRange textRange(offset, 0);
1775 VisiblePositionRange range = coreObject->visiblePositionRangeForRange(textRange);
1776 if (range.isNull())
1777 return FALSE;
1778
1779 coreObject->setSelectedVisiblePositionRange(range);
1780 return TRUE;
1781 }
1782
atk_text_interface_init(AtkTextIface * iface)1783 static void atk_text_interface_init(AtkTextIface* iface)
1784 {
1785 iface->get_text = webkit_accessible_text_get_text;
1786 iface->get_text_after_offset = webkit_accessible_text_get_text_after_offset;
1787 iface->get_text_at_offset = webkit_accessible_text_get_text_at_offset;
1788 iface->get_character_at_offset = webkit_accessible_text_get_character_at_offset;
1789 iface->get_text_before_offset = webkit_accessible_text_get_text_before_offset;
1790 iface->get_caret_offset = webkit_accessible_text_get_caret_offset;
1791 iface->get_run_attributes = webkit_accessible_text_get_run_attributes;
1792 iface->get_default_attributes = webkit_accessible_text_get_default_attributes;
1793 iface->get_character_extents = webkit_accessible_text_get_character_extents;
1794 iface->get_range_extents = webkit_accessible_text_get_range_extents;
1795 iface->get_character_count = webkit_accessible_text_get_character_count;
1796 iface->get_offset_at_point = webkit_accessible_text_get_offset_at_point;
1797 iface->get_n_selections = webkit_accessible_text_get_n_selections;
1798 iface->get_selection = webkit_accessible_text_get_selection;
1799
1800 // set methods
1801 iface->add_selection = webkit_accessible_text_add_selection;
1802 iface->remove_selection = webkit_accessible_text_remove_selection;
1803 iface->set_selection = webkit_accessible_text_set_selection;
1804 iface->set_caret_offset = webkit_accessible_text_set_caret_offset;
1805 }
1806
1807 // EditableText
1808
webkit_accessible_editable_text_set_run_attributes(AtkEditableText * text,AtkAttributeSet * attrib_set,gint start_offset,gint end_offset)1809 static gboolean webkit_accessible_editable_text_set_run_attributes(AtkEditableText* text, AtkAttributeSet* attrib_set, gint start_offset, gint end_offset)
1810 {
1811 notImplemented();
1812 return FALSE;
1813 }
1814
webkit_accessible_editable_text_set_text_contents(AtkEditableText * text,const gchar * string)1815 static void webkit_accessible_editable_text_set_text_contents(AtkEditableText* text, const gchar* string)
1816 {
1817 // FIXME: string nullcheck?
1818 core(text)->setValue(String::fromUTF8(string));
1819 }
1820
webkit_accessible_editable_text_insert_text(AtkEditableText * text,const gchar * string,gint length,gint * position)1821 static void webkit_accessible_editable_text_insert_text(AtkEditableText* text, const gchar* string, gint length, gint* position)
1822 {
1823 // FIXME: string nullcheck?
1824
1825 AccessibilityObject* coreObject = core(text);
1826 // FIXME: Not implemented in WebCore
1827 //coreObject->setSelectedTextRange(PlainTextRange(*position, 0));
1828 //coreObject->setSelectedText(String::fromUTF8(string));
1829
1830 Document* document = coreObject->document();
1831 if (!document || !document->frame())
1832 return;
1833
1834 coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(*position, 0)));
1835 coreObject->setFocused(true);
1836 // FIXME: We should set position to the actual inserted text length, which may be less than that requested.
1837 if (document->frame()->editor()->insertTextWithoutSendingTextEvent(String::fromUTF8(string), false, 0))
1838 *position += length;
1839 }
1840
webkit_accessible_editable_text_copy_text(AtkEditableText * text,gint start_pos,gint end_pos)1841 static void webkit_accessible_editable_text_copy_text(AtkEditableText* text, gint start_pos, gint end_pos)
1842 {
1843 notImplemented();
1844 }
1845
webkit_accessible_editable_text_cut_text(AtkEditableText * text,gint start_pos,gint end_pos)1846 static void webkit_accessible_editable_text_cut_text(AtkEditableText* text, gint start_pos, gint end_pos)
1847 {
1848 notImplemented();
1849 }
1850
webkit_accessible_editable_text_delete_text(AtkEditableText * text,gint start_pos,gint end_pos)1851 static void webkit_accessible_editable_text_delete_text(AtkEditableText* text, gint start_pos, gint end_pos)
1852 {
1853 AccessibilityObject* coreObject = core(text);
1854 // FIXME: Not implemented in WebCore
1855 //coreObject->setSelectedTextRange(PlainTextRange(start_pos, end_pos - start_pos));
1856 //coreObject->setSelectedText(String());
1857
1858 Document* document = coreObject->document();
1859 if (!document || !document->frame())
1860 return;
1861
1862 coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos)));
1863 coreObject->setFocused(true);
1864 document->frame()->editor()->performDelete();
1865 }
1866
webkit_accessible_editable_text_paste_text(AtkEditableText * text,gint position)1867 static void webkit_accessible_editable_text_paste_text(AtkEditableText* text, gint position)
1868 {
1869 notImplemented();
1870 }
1871
atk_editable_text_interface_init(AtkEditableTextIface * iface)1872 static void atk_editable_text_interface_init(AtkEditableTextIface* iface)
1873 {
1874 iface->set_run_attributes = webkit_accessible_editable_text_set_run_attributes;
1875 iface->set_text_contents = webkit_accessible_editable_text_set_text_contents;
1876 iface->insert_text = webkit_accessible_editable_text_insert_text;
1877 iface->copy_text = webkit_accessible_editable_text_copy_text;
1878 iface->cut_text = webkit_accessible_editable_text_cut_text;
1879 iface->delete_text = webkit_accessible_editable_text_delete_text;
1880 iface->paste_text = webkit_accessible_editable_text_paste_text;
1881 }
1882
contentsToAtk(AccessibilityObject * coreObject,AtkCoordType coordType,IntRect rect,gint * x,gint * y,gint * width=0,gint * height=0)1883 static void contentsToAtk(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width = 0, gint* height = 0)
1884 {
1885 FrameView* frameView = coreObject->documentFrameView();
1886
1887 if (frameView) {
1888 switch (coordType) {
1889 case ATK_XY_WINDOW:
1890 rect = frameView->contentsToWindow(rect);
1891 break;
1892 case ATK_XY_SCREEN:
1893 rect = frameView->contentsToScreen(rect);
1894 break;
1895 }
1896 }
1897
1898 if (x)
1899 *x = rect.x();
1900 if (y)
1901 *y = rect.y();
1902 if (width)
1903 *width = rect.width();
1904 if (height)
1905 *height = rect.height();
1906 }
1907
atkToContents(AccessibilityObject * coreObject,AtkCoordType coordType,gint x,gint y)1908 static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coordType, gint x, gint y)
1909 {
1910 IntPoint pos(x, y);
1911
1912 FrameView* frameView = coreObject->documentFrameView();
1913 if (frameView) {
1914 switch (coordType) {
1915 case ATK_XY_SCREEN:
1916 return frameView->screenToContents(pos);
1917 case ATK_XY_WINDOW:
1918 return frameView->windowToContents(pos);
1919 }
1920 }
1921
1922 return pos;
1923 }
1924
webkit_accessible_component_ref_accessible_at_point(AtkComponent * component,gint x,gint y,AtkCoordType coordType)1925 static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkComponent* component, gint x, gint y, AtkCoordType coordType)
1926 {
1927 IntPoint pos = atkToContents(core(component), coordType, x, y);
1928
1929 AccessibilityObject* target = core(component)->accessibilityHitTest(pos);
1930 if (!target)
1931 return 0;
1932 g_object_ref(target->wrapper());
1933 return target->wrapper();
1934 }
1935
webkit_accessible_component_get_extents(AtkComponent * component,gint * x,gint * y,gint * width,gint * height,AtkCoordType coordType)1936 static void webkit_accessible_component_get_extents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType)
1937 {
1938 IntRect rect = core(component)->elementRect();
1939 contentsToAtk(core(component), coordType, rect, x, y, width, height);
1940 }
1941
webkit_accessible_component_grab_focus(AtkComponent * component)1942 static gboolean webkit_accessible_component_grab_focus(AtkComponent* component)
1943 {
1944 core(component)->setFocused(true);
1945 return core(component)->isFocused();
1946 }
1947
atk_component_interface_init(AtkComponentIface * iface)1948 static void atk_component_interface_init(AtkComponentIface* iface)
1949 {
1950 iface->ref_accessible_at_point = webkit_accessible_component_ref_accessible_at_point;
1951 iface->get_extents = webkit_accessible_component_get_extents;
1952 iface->grab_focus = webkit_accessible_component_grab_focus;
1953 }
1954
1955 // Image
1956
webkit_accessible_image_get_image_position(AtkImage * image,gint * x,gint * y,AtkCoordType coordType)1957 static void webkit_accessible_image_get_image_position(AtkImage* image, gint* x, gint* y, AtkCoordType coordType)
1958 {
1959 IntRect rect = core(image)->elementRect();
1960 contentsToAtk(core(image), coordType, rect, x, y);
1961 }
1962
webkit_accessible_image_get_image_description(AtkImage * image)1963 static const gchar* webkit_accessible_image_get_image_description(AtkImage* image)
1964 {
1965 return returnString(core(image)->accessibilityDescription());
1966 }
1967
webkit_accessible_image_get_image_size(AtkImage * image,gint * width,gint * height)1968 static void webkit_accessible_image_get_image_size(AtkImage* image, gint* width, gint* height)
1969 {
1970 IntSize size = core(image)->size();
1971
1972 if (width)
1973 *width = size.width();
1974 if (height)
1975 *height = size.height();
1976 }
1977
atk_image_interface_init(AtkImageIface * iface)1978 static void atk_image_interface_init(AtkImageIface* iface)
1979 {
1980 iface->get_image_position = webkit_accessible_image_get_image_position;
1981 iface->get_image_description = webkit_accessible_image_get_image_description;
1982 iface->get_image_size = webkit_accessible_image_get_image_size;
1983 }
1984
1985 // Table
1986
cell(AtkTable * table,guint row,guint column)1987 static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column)
1988 {
1989 AccessibilityObject* accTable = core(table);
1990 if (accTable->isAccessibilityRenderObject())
1991 return static_cast<AccessibilityTable*>(accTable)->cellForColumnAndRow(column, row);
1992 return 0;
1993 }
1994
cellIndex(AccessibilityTableCell * axCell,AccessibilityTable * axTable)1995 static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable)
1996 {
1997 // Calculate the cell's index as if we had a traditional Gtk+ table in
1998 // which cells are all direct children of the table, arranged row-first.
1999 AccessibilityObject::AccessibilityChildrenVector allCells;
2000 axTable->cells(allCells);
2001 AccessibilityObject::AccessibilityChildrenVector::iterator position;
2002 position = std::find(allCells.begin(), allCells.end(), axCell);
2003 if (position == allCells.end())
2004 return -1;
2005 return position - allCells.begin();
2006 }
2007
cellAtIndex(AtkTable * table,gint index)2008 static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index)
2009 {
2010 AccessibilityObject* accTable = core(table);
2011 if (accTable->isAccessibilityRenderObject()) {
2012 AccessibilityObject::AccessibilityChildrenVector allCells;
2013 static_cast<AccessibilityTable*>(accTable)->cells(allCells);
2014 if (0 <= index && static_cast<unsigned>(index) < allCells.size()) {
2015 AccessibilityObject* accCell = allCells.at(index).get();
2016 return static_cast<AccessibilityTableCell*>(accCell);
2017 }
2018 }
2019 return 0;
2020 }
2021
webkit_accessible_table_ref_at(AtkTable * table,gint row,gint column)2022 static AtkObject* webkit_accessible_table_ref_at(AtkTable* table, gint row, gint column)
2023 {
2024 AccessibilityTableCell* axCell = cell(table, row, column);
2025 if (!axCell)
2026 return 0;
2027 return axCell->wrapper();
2028 }
2029
webkit_accessible_table_get_index_at(AtkTable * table,gint row,gint column)2030 static gint webkit_accessible_table_get_index_at(AtkTable* table, gint row, gint column)
2031 {
2032 AccessibilityTableCell* axCell = cell(table, row, column);
2033 AccessibilityTable* axTable = static_cast<AccessibilityTable*>(core(table));
2034 return cellIndex(axCell, axTable);
2035 }
2036
webkit_accessible_table_get_column_at_index(AtkTable * table,gint index)2037 static gint webkit_accessible_table_get_column_at_index(AtkTable* table, gint index)
2038 {
2039 AccessibilityTableCell* axCell = cellAtIndex(table, index);
2040 if (axCell){
2041 pair<int, int> columnRange;
2042 axCell->columnIndexRange(columnRange);
2043 return columnRange.first;
2044 }
2045 return -1;
2046 }
2047
webkit_accessible_table_get_row_at_index(AtkTable * table,gint index)2048 static gint webkit_accessible_table_get_row_at_index(AtkTable* table, gint index)
2049 {
2050 AccessibilityTableCell* axCell = cellAtIndex(table, index);
2051 if (axCell){
2052 pair<int, int> rowRange;
2053 axCell->rowIndexRange(rowRange);
2054 return rowRange.first;
2055 }
2056 return -1;
2057 }
2058
webkit_accessible_table_get_n_columns(AtkTable * table)2059 static gint webkit_accessible_table_get_n_columns(AtkTable* table)
2060 {
2061 AccessibilityObject* accTable = core(table);
2062 if (accTable->isAccessibilityRenderObject())
2063 return static_cast<AccessibilityTable*>(accTable)->columnCount();
2064 return 0;
2065 }
2066
webkit_accessible_table_get_n_rows(AtkTable * table)2067 static gint webkit_accessible_table_get_n_rows(AtkTable* table)
2068 {
2069 AccessibilityObject* accTable = core(table);
2070 if (accTable->isAccessibilityRenderObject())
2071 return static_cast<AccessibilityTable*>(accTable)->rowCount();
2072 return 0;
2073 }
2074
webkit_accessible_table_get_column_extent_at(AtkTable * table,gint row,gint column)2075 static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint row, gint column)
2076 {
2077 AccessibilityTableCell* axCell = cell(table, row, column);
2078 if (axCell) {
2079 pair<int, int> columnRange;
2080 axCell->columnIndexRange(columnRange);
2081 return columnRange.second;
2082 }
2083 return 0;
2084 }
2085
webkit_accessible_table_get_row_extent_at(AtkTable * table,gint row,gint column)2086 static gint webkit_accessible_table_get_row_extent_at(AtkTable* table, gint row, gint column)
2087 {
2088 AccessibilityTableCell* axCell = cell(table, row, column);
2089 if (axCell) {
2090 pair<int, int> rowRange;
2091 axCell->rowIndexRange(rowRange);
2092 return rowRange.second;
2093 }
2094 return 0;
2095 }
2096
webkit_accessible_table_get_column_header(AtkTable * table,gint column)2097 static AtkObject* webkit_accessible_table_get_column_header(AtkTable* table, gint column)
2098 {
2099 AccessibilityObject* accTable = core(table);
2100 if (accTable->isAccessibilityRenderObject()) {
2101 AccessibilityObject::AccessibilityChildrenVector allColumnHeaders;
2102 static_cast<AccessibilityTable*>(accTable)->columnHeaders(allColumnHeaders);
2103 unsigned columnCount = allColumnHeaders.size();
2104 for (unsigned k = 0; k < columnCount; ++k) {
2105 pair<int, int> columnRange;
2106 AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allColumnHeaders.at(k).get());
2107 cell->columnIndexRange(columnRange);
2108 if (columnRange.first <= column && column < columnRange.first + columnRange.second)
2109 return allColumnHeaders[k]->wrapper();
2110 }
2111 }
2112 return 0;
2113 }
2114
webkit_accessible_table_get_row_header(AtkTable * table,gint row)2115 static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row)
2116 {
2117 AccessibilityObject* accTable = core(table);
2118 if (accTable->isAccessibilityRenderObject()) {
2119 AccessibilityObject::AccessibilityChildrenVector allRowHeaders;
2120 static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders);
2121 unsigned rowCount = allRowHeaders.size();
2122 for (unsigned k = 0; k < rowCount; ++k) {
2123 pair<int, int> rowRange;
2124 AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allRowHeaders.at(k).get());
2125 cell->rowIndexRange(rowRange);
2126 if (rowRange.first <= row && row < rowRange.first + rowRange.second)
2127 return allRowHeaders[k]->wrapper();
2128 }
2129 }
2130 return 0;
2131 }
2132
webkit_accessible_table_get_caption(AtkTable * table)2133 static AtkObject* webkit_accessible_table_get_caption(AtkTable* table)
2134 {
2135 AccessibilityObject* accTable = core(table);
2136 if (accTable->isAccessibilityRenderObject()) {
2137 Node* node = accTable->node();
2138 if (node && node->hasTagName(HTMLNames::tableTag)) {
2139 HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(node)->caption();
2140 if (caption)
2141 return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->node())->wrapper();
2142 }
2143 }
2144 return 0;
2145 }
2146
webkit_accessible_table_get_column_description(AtkTable * table,gint column)2147 static const gchar* webkit_accessible_table_get_column_description(AtkTable* table, gint column)
2148 {
2149 AtkObject* columnHeader = atk_table_get_column_header(table, column);
2150 if (columnHeader && ATK_IS_TEXT(columnHeader))
2151 return webkit_accessible_text_get_text(ATK_TEXT(columnHeader), 0, -1);
2152
2153 return 0;
2154 }
2155
webkit_accessible_table_get_row_description(AtkTable * table,gint row)2156 static const gchar* webkit_accessible_table_get_row_description(AtkTable* table, gint row)
2157 {
2158 AtkObject* rowHeader = atk_table_get_row_header(table, row);
2159 if (rowHeader && ATK_IS_TEXT(rowHeader))
2160 return webkit_accessible_text_get_text(ATK_TEXT(rowHeader), 0, -1);
2161
2162 return 0;
2163 }
2164
atk_table_interface_init(AtkTableIface * iface)2165 static void atk_table_interface_init(AtkTableIface* iface)
2166 {
2167 iface->ref_at = webkit_accessible_table_ref_at;
2168 iface->get_index_at = webkit_accessible_table_get_index_at;
2169 iface->get_column_at_index = webkit_accessible_table_get_column_at_index;
2170 iface->get_row_at_index = webkit_accessible_table_get_row_at_index;
2171 iface->get_n_columns = webkit_accessible_table_get_n_columns;
2172 iface->get_n_rows = webkit_accessible_table_get_n_rows;
2173 iface->get_column_extent_at = webkit_accessible_table_get_column_extent_at;
2174 iface->get_row_extent_at = webkit_accessible_table_get_row_extent_at;
2175 iface->get_column_header = webkit_accessible_table_get_column_header;
2176 iface->get_row_header = webkit_accessible_table_get_row_header;
2177 iface->get_caption = webkit_accessible_table_get_caption;
2178 iface->get_column_description = webkit_accessible_table_get_column_description;
2179 iface->get_row_description = webkit_accessible_table_get_row_description;
2180 }
2181
webkitAccessibleHypertextGetLink(AtkHypertext * hypertext,gint index)2182 static AtkHyperlink* webkitAccessibleHypertextGetLink(AtkHypertext* hypertext, gint index)
2183 {
2184 AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children();
2185 if (index < 0 || static_cast<unsigned>(index) >= children.size())
2186 return 0;
2187
2188 gint currentLink = -1;
2189 for (unsigned i = 0; i < children.size(); i++) {
2190 AccessibilityObject* coreChild = children.at(i).get();
2191 if (!coreChild->accessibilityIsIgnored()) {
2192 AtkObject* axObject = coreChild->wrapper();
2193 if (!axObject || !ATK_IS_HYPERLINK_IMPL(axObject))
2194 continue;
2195
2196 currentLink++;
2197 if (index != currentLink)
2198 continue;
2199
2200 return atk_hyperlink_impl_get_hyperlink(ATK_HYPERLINK_IMPL(axObject));
2201 }
2202 }
2203
2204 return 0;
2205 }
2206
webkitAccessibleHypertextGetNLinks(AtkHypertext * hypertext)2207 static gint webkitAccessibleHypertextGetNLinks(AtkHypertext* hypertext)
2208 {
2209 AccessibilityObject::AccessibilityChildrenVector children = core(hypertext)->children();
2210 if (!children.size())
2211 return 0;
2212
2213 gint linksFound = 0;
2214 for (size_t i = 0; i < children.size(); i++) {
2215 AccessibilityObject* coreChild = children.at(i).get();
2216 if (!coreChild->accessibilityIsIgnored()) {
2217 AtkObject* axObject = coreChild->wrapper();
2218 if (axObject && ATK_IS_HYPERLINK_IMPL(axObject))
2219 linksFound++;
2220 }
2221 }
2222
2223 return linksFound;
2224 }
2225
webkitAccessibleHypertextGetLinkIndex(AtkHypertext * hypertext,gint charIndex)2226 static gint webkitAccessibleHypertextGetLinkIndex(AtkHypertext* hypertext, gint charIndex)
2227 {
2228 size_t linksCount = webkitAccessibleHypertextGetNLinks(hypertext);
2229 if (!linksCount)
2230 return -1;
2231
2232 for (size_t i = 0; i < linksCount; i++) {
2233 AtkHyperlink* hyperlink = ATK_HYPERLINK(webkitAccessibleHypertextGetLink(hypertext, i));
2234 gint startIndex = atk_hyperlink_get_start_index(hyperlink);
2235 gint endIndex = atk_hyperlink_get_end_index(hyperlink);
2236
2237 // Check if the char index in the link's offset range
2238 if (startIndex <= charIndex && charIndex < endIndex)
2239 return i;
2240 }
2241
2242 // Not found if reached
2243 return -1;
2244 }
2245
atkHypertextInterfaceInit(AtkHypertextIface * iface)2246 static void atkHypertextInterfaceInit(AtkHypertextIface* iface)
2247 {
2248 iface->get_link = webkitAccessibleHypertextGetLink;
2249 iface->get_n_links = webkitAccessibleHypertextGetNLinks;
2250 iface->get_link_index = webkitAccessibleHypertextGetLinkIndex;
2251 }
2252
webkitAccessibleHyperlinkImplGetHyperlink(AtkHyperlinkImpl * hyperlink)2253 static AtkHyperlink* webkitAccessibleHyperlinkImplGetHyperlink(AtkHyperlinkImpl* hyperlink)
2254 {
2255 AtkHyperlink* hyperlinkObject = ATK_HYPERLINK(g_object_get_data(G_OBJECT(hyperlink), "hyperlink-object"));
2256 if (!hyperlinkObject) {
2257 hyperlinkObject = ATK_HYPERLINK(webkitAccessibleHyperlinkNew(hyperlink));
2258 g_object_set_data(G_OBJECT(hyperlink), "hyperlink-object", hyperlinkObject);
2259 }
2260 return hyperlinkObject;
2261 }
2262
atkHyperlinkImplInterfaceInit(AtkHyperlinkImplIface * iface)2263 static void atkHyperlinkImplInterfaceInit(AtkHyperlinkImplIface* iface)
2264 {
2265 iface->get_hyperlink = webkitAccessibleHyperlinkImplGetHyperlink;
2266 }
2267
documentAttributeValue(AtkDocument * document,const gchar * attribute)2268 static const gchar* documentAttributeValue(AtkDocument* document, const gchar* attribute)
2269 {
2270 Document* coreDocument = core(document)->document();
2271 if (!coreDocument)
2272 return 0;
2273
2274 String value = String();
2275 if (!g_ascii_strcasecmp(attribute, "DocType") && coreDocument->doctype())
2276 value = coreDocument->doctype()->name();
2277 else if (!g_ascii_strcasecmp(attribute, "Encoding"))
2278 value = coreDocument->charset();
2279 else if (!g_ascii_strcasecmp(attribute, "URI"))
2280 value = coreDocument->documentURI();
2281 if (!value.isEmpty())
2282 return returnString(value);
2283
2284 return 0;
2285 }
2286
webkit_accessible_document_get_attribute_value(AtkDocument * document,const gchar * attribute)2287 static const gchar* webkit_accessible_document_get_attribute_value(AtkDocument* document, const gchar* attribute)
2288 {
2289 return documentAttributeValue(document, attribute);
2290 }
2291
webkit_accessible_document_get_attributes(AtkDocument * document)2292 static AtkAttributeSet* webkit_accessible_document_get_attributes(AtkDocument* document)
2293 {
2294 AtkAttributeSet* attributeSet = 0;
2295 const gchar* attributes [] = {"DocType", "Encoding", "URI"};
2296
2297 for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) {
2298 const gchar* value = documentAttributeValue(document, attributes[i]);
2299 if (value)
2300 attributeSet = addAttributeToSet(attributeSet, attributes[i], value);
2301 }
2302
2303 return attributeSet;
2304 }
2305
webkit_accessible_document_get_locale(AtkDocument * document)2306 static const gchar* webkit_accessible_document_get_locale(AtkDocument* document)
2307 {
2308
2309 // TODO: Should we fall back on lang xml:lang when the following comes up empty?
2310 String language = core(document)->language();
2311 if (!language.isEmpty())
2312 return returnString(language);
2313
2314 return 0;
2315 }
2316
atk_document_interface_init(AtkDocumentIface * iface)2317 static void atk_document_interface_init(AtkDocumentIface* iface)
2318 {
2319 iface->get_document_attribute_value = webkit_accessible_document_get_attribute_value;
2320 iface->get_document_attributes = webkit_accessible_document_get_attributes;
2321 iface->get_document_locale = webkit_accessible_document_get_locale;
2322 }
2323
2324
webkitAccessibleValueGetCurrentValue(AtkValue * value,GValue * gValue)2325 static void webkitAccessibleValueGetCurrentValue(AtkValue* value, GValue* gValue)
2326 {
2327 memset(gValue, 0, sizeof(GValue));
2328 g_value_init(gValue, G_TYPE_DOUBLE);
2329 g_value_set_double(gValue, core(value)->valueForRange());
2330 }
2331
webkitAccessibleValueGetMaximumValue(AtkValue * value,GValue * gValue)2332 static void webkitAccessibleValueGetMaximumValue(AtkValue* value, GValue* gValue)
2333 {
2334 memset(gValue, 0, sizeof(GValue));
2335 g_value_init(gValue, G_TYPE_DOUBLE);
2336 g_value_set_double(gValue, core(value)->maxValueForRange());
2337 }
2338
webkitAccessibleValueGetMinimumValue(AtkValue * value,GValue * gValue)2339 static void webkitAccessibleValueGetMinimumValue(AtkValue* value, GValue* gValue)
2340 {
2341 memset(gValue, 0, sizeof(GValue));
2342 g_value_init(gValue, G_TYPE_DOUBLE);
2343 g_value_set_double(gValue, core(value)->minValueForRange());
2344 }
2345
webkitAccessibleValueSetCurrentValue(AtkValue * value,const GValue * gValue)2346 static gboolean webkitAccessibleValueSetCurrentValue(AtkValue* value, const GValue* gValue)
2347 {
2348 if (!G_VALUE_HOLDS_DOUBLE(gValue) && !G_VALUE_HOLDS_INT(gValue))
2349 return FALSE;
2350
2351 AccessibilityObject* coreObject = core(value);
2352 if (!coreObject->canSetValueAttribute())
2353 return FALSE;
2354
2355 if (G_VALUE_HOLDS_DOUBLE(gValue))
2356 coreObject->setValue(String::number(g_value_get_double(gValue)));
2357 else
2358 coreObject->setValue(String::number(g_value_get_int(gValue)));
2359
2360 return TRUE;
2361 }
2362
webkitAccessibleValueGetMinimumIncrement(AtkValue * value,GValue * gValue)2363 static void webkitAccessibleValueGetMinimumIncrement(AtkValue* value, GValue* gValue)
2364 {
2365 memset(gValue, 0, sizeof(GValue));
2366 g_value_init(gValue, G_TYPE_DOUBLE);
2367
2368 // There's not such a thing in the WAI-ARIA specification, thus return zero.
2369 g_value_set_double(gValue, 0.0);
2370 }
2371
atkValueInterfaceInit(AtkValueIface * iface)2372 static void atkValueInterfaceInit(AtkValueIface* iface)
2373 {
2374 iface->get_current_value = webkitAccessibleValueGetCurrentValue;
2375 iface->get_maximum_value = webkitAccessibleValueGetMaximumValue;
2376 iface->get_minimum_value = webkitAccessibleValueGetMinimumValue;
2377 iface->set_current_value = webkitAccessibleValueSetCurrentValue;
2378 iface->get_minimum_increment = webkitAccessibleValueGetMinimumIncrement;
2379 }
2380
2381 static const GInterfaceInfo AtkInterfacesInitFunctions[] = {
2382 {(GInterfaceInitFunc)atk_action_interface_init,
2383 (GInterfaceFinalizeFunc) 0, 0},
2384 {(GInterfaceInitFunc)atk_selection_interface_init,
2385 (GInterfaceFinalizeFunc) 0, 0},
2386 {(GInterfaceInitFunc)atk_editable_text_interface_init,
2387 (GInterfaceFinalizeFunc) 0, 0},
2388 {(GInterfaceInitFunc)atk_text_interface_init,
2389 (GInterfaceFinalizeFunc) 0, 0},
2390 {(GInterfaceInitFunc)atk_component_interface_init,
2391 (GInterfaceFinalizeFunc) 0, 0},
2392 {(GInterfaceInitFunc)atk_image_interface_init,
2393 (GInterfaceFinalizeFunc) 0, 0},
2394 {(GInterfaceInitFunc)atk_table_interface_init,
2395 (GInterfaceFinalizeFunc) 0, 0},
2396 {(GInterfaceInitFunc)atkHypertextInterfaceInit,
2397 (GInterfaceFinalizeFunc) 0, 0},
2398 {(GInterfaceInitFunc)atkHyperlinkImplInterfaceInit,
2399 (GInterfaceFinalizeFunc) 0, 0},
2400 {(GInterfaceInitFunc)atk_document_interface_init,
2401 (GInterfaceFinalizeFunc) 0, 0},
2402 {(GInterfaceInitFunc)atkValueInterfaceInit,
2403 (GInterfaceFinalizeFunc) 0, 0}
2404 };
2405
2406 enum WAIType {
2407 WAI_ACTION,
2408 WAI_SELECTION,
2409 WAI_EDITABLE_TEXT,
2410 WAI_TEXT,
2411 WAI_COMPONENT,
2412 WAI_IMAGE,
2413 WAI_TABLE,
2414 WAI_HYPERTEXT,
2415 WAI_HYPERLINK,
2416 WAI_DOCUMENT,
2417 WAI_VALUE,
2418 };
2419
GetAtkInterfaceTypeFromWAIType(WAIType type)2420 static GType GetAtkInterfaceTypeFromWAIType(WAIType type)
2421 {
2422 switch (type) {
2423 case WAI_ACTION:
2424 return ATK_TYPE_ACTION;
2425 case WAI_SELECTION:
2426 return ATK_TYPE_SELECTION;
2427 case WAI_EDITABLE_TEXT:
2428 return ATK_TYPE_EDITABLE_TEXT;
2429 case WAI_TEXT:
2430 return ATK_TYPE_TEXT;
2431 case WAI_COMPONENT:
2432 return ATK_TYPE_COMPONENT;
2433 case WAI_IMAGE:
2434 return ATK_TYPE_IMAGE;
2435 case WAI_TABLE:
2436 return ATK_TYPE_TABLE;
2437 case WAI_HYPERTEXT:
2438 return ATK_TYPE_HYPERTEXT;
2439 case WAI_HYPERLINK:
2440 return ATK_TYPE_HYPERLINK_IMPL;
2441 case WAI_DOCUMENT:
2442 return ATK_TYPE_DOCUMENT;
2443 case WAI_VALUE:
2444 return ATK_TYPE_VALUE;
2445 }
2446
2447 return G_TYPE_INVALID;
2448 }
2449
getInterfaceMaskFromObject(AccessibilityObject * coreObject)2450 static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject)
2451 {
2452 guint16 interfaceMask = 0;
2453
2454 // Component interface is always supported
2455 interfaceMask |= 1 << WAI_COMPONENT;
2456
2457 AccessibilityRole role = coreObject->roleValue();
2458
2459 // Action
2460 // As the implementation of the AtkAction interface is a very
2461 // basic one (just relays in executing the default action for each
2462 // object, and only supports having one action per object), it is
2463 // better just to implement this interface for every instance of
2464 // the WebKitAccessible class and let WebCore decide what to do.
2465 interfaceMask |= 1 << WAI_ACTION;
2466
2467 // Selection
2468 if (coreObject->isListBox() || coreObject->isMenuList())
2469 interfaceMask |= 1 << WAI_SELECTION;
2470
2471 // Get renderer if available.
2472 RenderObject* renderer = 0;
2473 if (coreObject->isAccessibilityRenderObject())
2474 renderer = coreObject->renderer();
2475
2476 // Hyperlink (links and embedded objects).
2477 if (coreObject->isLink() || (renderer && renderer->isReplaced()))
2478 interfaceMask |= 1 << WAI_HYPERLINK;
2479
2480 // Text & Editable Text
2481 if (role == StaticTextRole || coreObject->isMenuListOption())
2482 interfaceMask |= 1 << WAI_TEXT;
2483 else {
2484 if (coreObject->isTextControl()) {
2485 interfaceMask |= 1 << WAI_TEXT;
2486 if (!coreObject->isReadOnly())
2487 interfaceMask |= 1 << WAI_EDITABLE_TEXT;
2488 } else {
2489 if (role != TableRole) {
2490 interfaceMask |= 1 << WAI_HYPERTEXT;
2491 if (renderer && renderer->childrenInline())
2492 interfaceMask |= 1 << WAI_TEXT;
2493 }
2494
2495 // Add the TEXT interface for list items whose
2496 // first accessible child has a text renderer
2497 if (role == ListItemRole) {
2498 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children();
2499 if (children.size()) {
2500 AccessibilityObject* axRenderChild = children.at(0).get();
2501 interfaceMask |= getInterfaceMaskFromObject(axRenderChild);
2502 }
2503 }
2504 }
2505 }
2506
2507 // Image
2508 if (coreObject->isImage())
2509 interfaceMask |= 1 << WAI_IMAGE;
2510
2511 // Table
2512 if (role == TableRole)
2513 interfaceMask |= 1 << WAI_TABLE;
2514
2515 // Document
2516 if (role == WebAreaRole)
2517 interfaceMask |= 1 << WAI_DOCUMENT;
2518
2519 // Value
2520 if (role == SliderRole)
2521 interfaceMask |= 1 << WAI_VALUE;
2522
2523 return interfaceMask;
2524 }
2525
getUniqueAccessibilityTypeName(guint16 interfaceMask)2526 static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask)
2527 {
2528 #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */
2529 static char name[WAI_TYPE_NAME_LEN + 1];
2530
2531 g_sprintf(name, "WAIType%x", interfaceMask);
2532 name[WAI_TYPE_NAME_LEN] = '\0';
2533
2534 return name;
2535 }
2536
getAccessibilityTypeFromObject(AccessibilityObject * coreObject)2537 static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject)
2538 {
2539 static const GTypeInfo typeInfo = {
2540 sizeof(WebKitAccessibleClass),
2541 (GBaseInitFunc) 0,
2542 (GBaseFinalizeFunc) 0,
2543 (GClassInitFunc) 0,
2544 (GClassFinalizeFunc) 0,
2545 0, /* class data */
2546 sizeof(WebKitAccessible), /* instance size */
2547 0, /* nb preallocs */
2548 (GInstanceInitFunc) 0,
2549 0 /* value table */
2550 };
2551
2552 guint16 interfaceMask = getInterfaceMaskFromObject(coreObject);
2553 const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask);
2554 GType type = g_type_from_name(atkTypeName);
2555 if (type)
2556 return type;
2557
2558 type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE,
2559 atkTypeName,
2560 &typeInfo, GTypeFlags(0));
2561 for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) {
2562 if (interfaceMask & (1 << i))
2563 g_type_add_interface_static(type,
2564 GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)),
2565 &AtkInterfacesInitFunctions[i]);
2566 }
2567
2568 return type;
2569 }
2570
webkit_accessible_new(AccessibilityObject * coreObject)2571 WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject)
2572 {
2573 GType type = getAccessibilityTypeFromObject(coreObject);
2574 AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0));
2575
2576 atk_object_initialize(object, coreObject);
2577
2578 return WEBKIT_ACCESSIBLE(object);
2579 }
2580
webkit_accessible_get_accessibility_object(WebKitAccessible * accessible)2581 AccessibilityObject* webkit_accessible_get_accessibility_object(WebKitAccessible* accessible)
2582 {
2583 return accessible->m_object;
2584 }
2585
webkit_accessible_detach(WebKitAccessible * accessible)2586 void webkit_accessible_detach(WebKitAccessible* accessible)
2587 {
2588 ASSERT(accessible->m_object);
2589
2590 if (core(accessible)->roleValue() == WebAreaRole)
2591 g_signal_emit_by_name(accessible, "state-change", "defunct", true);
2592
2593 // We replace the WebCore AccessibilityObject with a fallback object that
2594 // provides default implementations to avoid repetitive null-checking after
2595 // detachment.
2596 accessible->m_object = fallbackObject();
2597 }
2598
webkit_accessible_get_focused_element(WebKitAccessible * accessible)2599 AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible)
2600 {
2601 if (!accessible->m_object)
2602 return 0;
2603
2604 RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement();
2605 if (!focusedObj)
2606 return 0;
2607
2608 return focusedObj->wrapper();
2609 }
2610
objectAndOffsetUnignored(AccessibilityObject * coreObject,int & offset,bool ignoreLinks)2611 AccessibilityObject* objectAndOffsetUnignored(AccessibilityObject* coreObject, int& offset, bool ignoreLinks)
2612 {
2613 // Indication that something bogus has transpired.
2614 offset = -1;
2615
2616 AccessibilityObject* realObject = coreObject;
2617 if (realObject->accessibilityIsIgnored())
2618 realObject = realObject->parentObjectUnignored();
2619 if (!realObject)
2620 return 0;
2621
2622 if (ignoreLinks && realObject->isLink())
2623 realObject = realObject->parentObjectUnignored();
2624 if (!realObject)
2625 return 0;
2626
2627 Node* node = realObject->node();
2628 if (node) {
2629 VisiblePosition startPosition = VisiblePosition(positionBeforeNode(node), DOWNSTREAM);
2630 VisiblePosition endPosition = realObject->selection().visibleEnd();
2631
2632 if (startPosition == endPosition)
2633 offset = 0;
2634 else if (!isStartOfLine(endPosition)) {
2635 RefPtr<Range> range = makeRange(startPosition, endPosition.previous());
2636 offset = TextIterator::rangeLength(range.get(), true) + 1;
2637 } else {
2638 RefPtr<Range> range = makeRange(startPosition, endPosition);
2639 offset = TextIterator::rangeLength(range.get(), true);
2640 }
2641
2642 }
2643
2644 return realObject;
2645 }
2646
2647 #endif // HAVE(ACCESSIBILITY)
2648