|
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 "AccessibilityListBox.h" |
|
38 #include "AccessibilityListBoxOption.h" |
|
39 #include "AccessibilityRenderObject.h" |
|
40 #include "AccessibilityTable.h" |
|
41 #include "AccessibilityTableCell.h" |
|
42 #include "AccessibilityTableColumn.h" |
|
43 #include "AccessibilityTableRow.h" |
|
44 #include "AtomicString.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 "RenderText.h" |
|
59 #include "TextEncoding.h" |
|
60 #include <wtf/text/CString.h> |
|
61 |
|
62 #include <atk/atk.h> |
|
63 #include <glib.h> |
|
64 #include <glib/gprintf.h> |
|
65 #include <libgail-util/gail-util.h> |
|
66 #include <pango/pango.h> |
|
67 |
|
68 using namespace WebCore; |
|
69 |
|
70 static AccessibilityObject* fallbackObject() |
|
71 { |
|
72 static AXObjectCache* fallbackCache = new AXObjectCache; |
|
73 static AccessibilityObject* object = 0; |
|
74 if (!object) { |
|
75 // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack |
|
76 object = fallbackCache->getOrCreate(ListBoxOptionRole); |
|
77 object->ref(); |
|
78 } |
|
79 |
|
80 return object; |
|
81 } |
|
82 |
|
83 // Used to provide const char* returns. |
|
84 static const char* returnString(const String& str) |
|
85 { |
|
86 static CString returnedString; |
|
87 returnedString = str.utf8(); |
|
88 return returnedString.data(); |
|
89 } |
|
90 |
|
91 static AccessibilityObject* core(WebKitAccessible* accessible) |
|
92 { |
|
93 if (!accessible) |
|
94 return 0; |
|
95 |
|
96 return accessible->m_object; |
|
97 } |
|
98 |
|
99 static AccessibilityObject* core(AtkObject* object) |
|
100 { |
|
101 if (!WEBKIT_IS_ACCESSIBLE(object)) |
|
102 return 0; |
|
103 |
|
104 return core(WEBKIT_ACCESSIBLE(object)); |
|
105 } |
|
106 |
|
107 static AccessibilityObject* core(AtkAction* action) |
|
108 { |
|
109 return core(ATK_OBJECT(action)); |
|
110 } |
|
111 |
|
112 static AccessibilityObject* core(AtkSelection* selection) |
|
113 { |
|
114 return core(ATK_OBJECT(selection)); |
|
115 } |
|
116 |
|
117 static AccessibilityObject* core(AtkText* text) |
|
118 { |
|
119 return core(ATK_OBJECT(text)); |
|
120 } |
|
121 |
|
122 static AccessibilityObject* core(AtkEditableText* text) |
|
123 { |
|
124 return core(ATK_OBJECT(text)); |
|
125 } |
|
126 |
|
127 static AccessibilityObject* core(AtkComponent* component) |
|
128 { |
|
129 return core(ATK_OBJECT(component)); |
|
130 } |
|
131 |
|
132 static AccessibilityObject* core(AtkImage* image) |
|
133 { |
|
134 return core(ATK_OBJECT(image)); |
|
135 } |
|
136 |
|
137 static AccessibilityObject* core(AtkTable* table) |
|
138 { |
|
139 return core(ATK_OBJECT(table)); |
|
140 } |
|
141 |
|
142 static AccessibilityObject* core(AtkDocument* document) |
|
143 { |
|
144 return core(ATK_OBJECT(document)); |
|
145 } |
|
146 |
|
147 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset); |
|
148 |
|
149 static const gchar* webkit_accessible_get_name(AtkObject* object) |
|
150 { |
|
151 AccessibilityObject* coreObject = core(object); |
|
152 if (!coreObject->isAccessibilityRenderObject()) |
|
153 return returnString(coreObject->stringValue()); |
|
154 |
|
155 AccessibilityRenderObject* renderObject = static_cast<AccessibilityRenderObject*>(coreObject); |
|
156 if (coreObject->isControl()) { |
|
157 AccessibilityObject* label = renderObject->correspondingLabelForControlElement(); |
|
158 if (label) { |
|
159 AtkObject* atkObject = label->wrapper(); |
|
160 if (ATK_IS_TEXT(atkObject)) |
|
161 return webkit_accessible_text_get_text(ATK_TEXT(atkObject), 0, -1); |
|
162 } |
|
163 } |
|
164 |
|
165 if (renderObject->isImage() || renderObject->isInputImage()) { |
|
166 Node* node = renderObject->renderer()->node(); |
|
167 if (node && node->isHTMLElement()) { |
|
168 // Get the attribute rather than altText String so as not to fall back on title. |
|
169 String alt = static_cast<HTMLElement*>(node)->getAttribute(HTMLNames::altAttr); |
|
170 if (!alt.isEmpty()) |
|
171 return returnString(alt); |
|
172 } |
|
173 } |
|
174 |
|
175 return returnString(coreObject->stringValue()); |
|
176 } |
|
177 |
|
178 static const gchar* webkit_accessible_get_description(AtkObject* object) |
|
179 { |
|
180 AccessibilityObject* coreObject = core(object); |
|
181 Node* node = 0; |
|
182 if (coreObject->isAccessibilityRenderObject()) |
|
183 node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); |
|
184 if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole) |
|
185 return returnString(coreObject->accessibilityDescription()); |
|
186 |
|
187 // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here. |
|
188 if (coreObject->roleValue() == TableRole) { |
|
189 String summary = static_cast<HTMLTableElement*>(node)->summary(); |
|
190 if (!summary.isEmpty()) |
|
191 return returnString(summary); |
|
192 } |
|
193 |
|
194 // The title attribute should be reliably available as the object's descripton. |
|
195 // We do not want to fall back on other attributes in its absence. See bug 25524. |
|
196 String title = static_cast<HTMLElement*>(node)->title(); |
|
197 if (!title.isEmpty()) |
|
198 return returnString(title); |
|
199 |
|
200 return returnString(coreObject->accessibilityDescription()); |
|
201 } |
|
202 |
|
203 static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) |
|
204 { |
|
205 AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); |
|
206 if (accObject->isControl()) { |
|
207 AccessibilityObject* label = accObject->correspondingLabelForControlElement(); |
|
208 if (label) |
|
209 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); |
|
210 } else { |
|
211 AccessibilityObject* control = accObject->correspondingControlForLabelElement(); |
|
212 if (control) |
|
213 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); |
|
214 } |
|
215 } |
|
216 |
|
217 static gpointer webkit_accessible_parent_class = 0; |
|
218 |
|
219 static AtkObject* atkParentOfWebView(AtkObject* object) |
|
220 { |
|
221 AccessibilityObject* coreParent = core(object)->parentObjectUnignored(); |
|
222 |
|
223 // The top level web view claims to not have a parent. This makes it |
|
224 // impossible for assistive technologies to ascend the accessible |
|
225 // hierarchy all the way to the application. (Bug 30489) |
|
226 if (!coreParent && core(object)->isWebArea()) { |
|
227 HostWindow* hostWindow = core(object)->document()->view()->hostWindow(); |
|
228 if (hostWindow) { |
|
229 PlatformPageClient webView = hostWindow->platformPageClient(); |
|
230 if (webView) { |
|
231 GtkWidget* webViewParent = gtk_widget_get_parent(webView); |
|
232 if (webViewParent) |
|
233 return gtk_widget_get_accessible(webViewParent); |
|
234 } |
|
235 } |
|
236 } |
|
237 |
|
238 if (!coreParent) |
|
239 return 0; |
|
240 |
|
241 return coreParent->wrapper(); |
|
242 } |
|
243 |
|
244 static AtkObject* webkit_accessible_get_parent(AtkObject* object) |
|
245 { |
|
246 AccessibilityObject* coreParent = core(object)->parentObjectUnignored(); |
|
247 if (!coreParent && core(object)->isWebArea()) |
|
248 return atkParentOfWebView(object); |
|
249 |
|
250 if (!coreParent) |
|
251 return 0; |
|
252 |
|
253 return coreParent->wrapper(); |
|
254 } |
|
255 |
|
256 static gint webkit_accessible_get_n_children(AtkObject* object) |
|
257 { |
|
258 return core(object)->children().size(); |
|
259 } |
|
260 |
|
261 static AtkObject* webkit_accessible_ref_child(AtkObject* object, gint index) |
|
262 { |
|
263 AccessibilityObject* coreObject = core(object); |
|
264 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); |
|
265 if (index < 0 || static_cast<unsigned>(index) >= children.size()) |
|
266 return 0; |
|
267 |
|
268 AccessibilityObject* coreChild = children.at(index).get(); |
|
269 |
|
270 if (!coreChild) |
|
271 return 0; |
|
272 |
|
273 AtkObject* child = coreChild->wrapper(); |
|
274 atk_object_set_parent(child, object); |
|
275 g_object_ref(child); |
|
276 |
|
277 return child; |
|
278 } |
|
279 |
|
280 static gint webkit_accessible_get_index_in_parent(AtkObject* object) |
|
281 { |
|
282 AccessibilityObject* coreObject = core(object); |
|
283 AccessibilityObject* parent = coreObject->parentObjectUnignored(); |
|
284 |
|
285 if (!parent && core(object)->isWebArea()) { |
|
286 AtkObject* atkParent = atkParentOfWebView(object); |
|
287 if (!atkParent) |
|
288 return -1; |
|
289 |
|
290 unsigned count = atk_object_get_n_accessible_children(atkParent); |
|
291 for (unsigned i = 0; i < count; ++i) { |
|
292 AtkObject* child = atk_object_ref_accessible_child(atkParent, i); |
|
293 bool childIsObject = child == object; |
|
294 g_object_unref(child); |
|
295 if (childIsObject) |
|
296 return i; |
|
297 } |
|
298 } |
|
299 |
|
300 AccessibilityObject::AccessibilityChildrenVector children = parent->children(); |
|
301 unsigned count = children.size(); |
|
302 for (unsigned i = 0; i < count; ++i) { |
|
303 if (children[i] == coreObject) |
|
304 return i; |
|
305 } |
|
306 |
|
307 return -1; |
|
308 } |
|
309 |
|
310 static AtkAttributeSet* addAttributeToSet(AtkAttributeSet* attributeSet, const char* name, const char* value) |
|
311 { |
|
312 AtkAttribute* attribute = static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute))); |
|
313 attribute->name = g_strdup(name); |
|
314 attribute->value = g_strdup(value); |
|
315 attributeSet = g_slist_prepend(attributeSet, attribute); |
|
316 |
|
317 return attributeSet; |
|
318 } |
|
319 |
|
320 static AtkAttributeSet* webkit_accessible_get_attributes(AtkObject* object) |
|
321 { |
|
322 AtkAttributeSet* attributeSet = 0; |
|
323 |
|
324 attributeSet = addAttributeToSet(attributeSet, "toolkit", "WebKitGtk"); |
|
325 |
|
326 int headingLevel = core(object)->headingLevel(); |
|
327 if (headingLevel) { |
|
328 String value = String::number(headingLevel); |
|
329 attributeSet = addAttributeToSet(attributeSet, "level", value.utf8().data()); |
|
330 } |
|
331 return attributeSet; |
|
332 } |
|
333 |
|
334 static AtkRole atkRole(AccessibilityRole role) |
|
335 { |
|
336 switch (role) { |
|
337 case UnknownRole: |
|
338 return ATK_ROLE_UNKNOWN; |
|
339 case ButtonRole: |
|
340 return ATK_ROLE_PUSH_BUTTON; |
|
341 case RadioButtonRole: |
|
342 return ATK_ROLE_RADIO_BUTTON; |
|
343 case CheckBoxRole: |
|
344 return ATK_ROLE_CHECK_BOX; |
|
345 case SliderRole: |
|
346 return ATK_ROLE_SLIDER; |
|
347 case TabGroupRole: |
|
348 return ATK_ROLE_PAGE_TAB_LIST; |
|
349 case TextFieldRole: |
|
350 case TextAreaRole: |
|
351 return ATK_ROLE_ENTRY; |
|
352 case StaticTextRole: |
|
353 return ATK_ROLE_TEXT; |
|
354 case OutlineRole: |
|
355 return ATK_ROLE_TREE; |
|
356 case MenuBarRole: |
|
357 return ATK_ROLE_MENU_BAR; |
|
358 case MenuListPopupRole: |
|
359 case MenuRole: |
|
360 return ATK_ROLE_MENU; |
|
361 case MenuListOptionRole: |
|
362 case MenuItemRole: |
|
363 return ATK_ROLE_MENU_ITEM; |
|
364 case ColumnRole: |
|
365 //return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? |
|
366 return ATK_ROLE_UNKNOWN; // Matches Mozilla |
|
367 case RowRole: |
|
368 //return ATK_ROLE_TABLE_ROW_HEADER; // Is this right? |
|
369 return ATK_ROLE_LIST_ITEM; // Matches Mozilla |
|
370 case ToolbarRole: |
|
371 return ATK_ROLE_TOOL_BAR; |
|
372 case BusyIndicatorRole: |
|
373 return ATK_ROLE_PROGRESS_BAR; // Is this right? |
|
374 case ProgressIndicatorRole: |
|
375 //return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp |
|
376 return ATK_ROLE_PROGRESS_BAR; |
|
377 case WindowRole: |
|
378 return ATK_ROLE_WINDOW; |
|
379 case PopUpButtonRole: |
|
380 case ComboBoxRole: |
|
381 return ATK_ROLE_COMBO_BOX; |
|
382 case SplitGroupRole: |
|
383 return ATK_ROLE_SPLIT_PANE; |
|
384 case SplitterRole: |
|
385 return ATK_ROLE_SEPARATOR; |
|
386 case ColorWellRole: |
|
387 return ATK_ROLE_COLOR_CHOOSER; |
|
388 case ListRole: |
|
389 return ATK_ROLE_LIST; |
|
390 case ScrollBarRole: |
|
391 return ATK_ROLE_SCROLL_BAR; |
|
392 case GridRole: // Is this right? |
|
393 case TableRole: |
|
394 return ATK_ROLE_TABLE; |
|
395 case ApplicationRole: |
|
396 return ATK_ROLE_APPLICATION; |
|
397 case GroupRole: |
|
398 case RadioGroupRole: |
|
399 return ATK_ROLE_PANEL; |
|
400 case CellRole: |
|
401 return ATK_ROLE_TABLE_CELL; |
|
402 case LinkRole: |
|
403 case WebCoreLinkRole: |
|
404 case ImageMapLinkRole: |
|
405 return ATK_ROLE_LINK; |
|
406 case ImageMapRole: |
|
407 case ImageRole: |
|
408 return ATK_ROLE_IMAGE; |
|
409 case ListMarkerRole: |
|
410 return ATK_ROLE_TEXT; |
|
411 case WebAreaRole: |
|
412 //return ATK_ROLE_HTML_CONTAINER; // Is this right? |
|
413 return ATK_ROLE_DOCUMENT_FRAME; |
|
414 case HeadingRole: |
|
415 return ATK_ROLE_HEADING; |
|
416 case ListBoxRole: |
|
417 return ATK_ROLE_LIST; |
|
418 case ListItemRole: |
|
419 case ListBoxOptionRole: |
|
420 return ATK_ROLE_LIST_ITEM; |
|
421 default: |
|
422 return ATK_ROLE_UNKNOWN; |
|
423 } |
|
424 } |
|
425 |
|
426 static AtkRole webkit_accessible_get_role(AtkObject* object) |
|
427 { |
|
428 AccessibilityObject* axObject = core(object); |
|
429 |
|
430 if (!axObject) |
|
431 return ATK_ROLE_UNKNOWN; |
|
432 |
|
433 // WebCore does not know about paragraph role, label role, or section role |
|
434 if (axObject->isAccessibilityRenderObject()) { |
|
435 Node* node = static_cast<AccessibilityRenderObject*>(axObject)->renderer()->node(); |
|
436 if (node) { |
|
437 if (node->hasTagName(HTMLNames::pTag)) |
|
438 return ATK_ROLE_PARAGRAPH; |
|
439 if (node->hasTagName(HTMLNames::labelTag)) |
|
440 return ATK_ROLE_LABEL; |
|
441 if (node->hasTagName(HTMLNames::divTag)) |
|
442 return ATK_ROLE_SECTION; |
|
443 if (node->hasTagName(HTMLNames::formTag)) |
|
444 return ATK_ROLE_FORM; |
|
445 } |
|
446 } |
|
447 |
|
448 // Note: Why doesn't WebCore have a password field for this |
|
449 if (axObject->isPasswordField()) |
|
450 return ATK_ROLE_PASSWORD_TEXT; |
|
451 |
|
452 return atkRole(axObject->roleValue()); |
|
453 } |
|
454 |
|
455 static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet) |
|
456 { |
|
457 AccessibilityObject* parent = coreObject->parentObject(); |
|
458 bool isListBoxOption = parent && parent->isListBox(); |
|
459 |
|
460 // Please keep the state list in alphabetical order |
|
461 if (coreObject->isChecked()) |
|
462 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); |
|
463 |
|
464 // FIXME: isReadOnly does not seem to do the right thing for |
|
465 // controls, so check explicitly for them. In addition, because |
|
466 // isReadOnly is false for listBoxOptions, we need to add one |
|
467 // more check so that we do not present them as being "editable". |
|
468 if ((!coreObject->isReadOnly() || |
|
469 (coreObject->isControl() && coreObject->canSetValueAttribute())) && |
|
470 !isListBoxOption) |
|
471 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); |
|
472 |
|
473 // FIXME: Put both ENABLED and SENSITIVE together here for now |
|
474 if (coreObject->isEnabled()) { |
|
475 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED); |
|
476 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE); |
|
477 } |
|
478 |
|
479 if (coreObject->canSetFocusAttribute()) |
|
480 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); |
|
481 |
|
482 if (coreObject->isFocused()) |
|
483 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); |
|
484 |
|
485 // TODO: ATK_STATE_HORIZONTAL |
|
486 |
|
487 if (coreObject->isIndeterminate()) |
|
488 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); |
|
489 |
|
490 if (coreObject->isMultiSelectable()) |
|
491 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE); |
|
492 |
|
493 // TODO: ATK_STATE_OPAQUE |
|
494 |
|
495 if (coreObject->isPressed()) |
|
496 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); |
|
497 |
|
498 // TODO: ATK_STATE_SELECTABLE_TEXT |
|
499 |
|
500 if (coreObject->canSetSelectedAttribute()) { |
|
501 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE); |
|
502 // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED} |
|
503 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the |
|
504 // former. |
|
505 if (isListBoxOption) |
|
506 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); |
|
507 } |
|
508 |
|
509 if (coreObject->isSelected()) { |
|
510 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED); |
|
511 // Items in focusable lists in Gtk have both STATE_SELECT{ABLE,ED} |
|
512 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the |
|
513 // former. |
|
514 if (isListBoxOption) |
|
515 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); |
|
516 } |
|
517 |
|
518 // FIXME: Group both SHOWING and VISIBLE here for now |
|
519 // Not sure how to handle this in WebKit, see bug |
|
520 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other |
|
521 // issues with SHOWING vs VISIBLE within GTK+ |
|
522 if (!coreObject->isOffScreen()) { |
|
523 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING); |
|
524 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE); |
|
525 } |
|
526 |
|
527 // Mutually exclusive, so we group these two |
|
528 if (coreObject->roleValue() == TextFieldRole) |
|
529 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); |
|
530 else if (coreObject->roleValue() == TextAreaRole) |
|
531 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE); |
|
532 |
|
533 // TODO: ATK_STATE_SENSITIVE |
|
534 |
|
535 // TODO: ATK_STATE_VERTICAL |
|
536 |
|
537 if (coreObject->isVisited()) |
|
538 atk_state_set_add_state(stateSet, ATK_STATE_VISITED); |
|
539 } |
|
540 |
|
541 static AtkStateSet* webkit_accessible_ref_state_set(AtkObject* object) |
|
542 { |
|
543 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_state_set(object); |
|
544 AccessibilityObject* coreObject = core(object); |
|
545 |
|
546 if (coreObject == fallbackObject()) { |
|
547 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT); |
|
548 return stateSet; |
|
549 } |
|
550 |
|
551 setAtkStateSetFromCoreObject(coreObject, stateSet); |
|
552 |
|
553 return stateSet; |
|
554 } |
|
555 |
|
556 static AtkRelationSet* webkit_accessible_ref_relation_set(AtkObject* object) |
|
557 { |
|
558 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkit_accessible_parent_class)->ref_relation_set(object); |
|
559 AccessibilityObject* coreObject = core(object); |
|
560 |
|
561 setAtkRelationSetFromCoreObject(coreObject, relationSet); |
|
562 |
|
563 return relationSet; |
|
564 } |
|
565 |
|
566 static void webkit_accessible_init(AtkObject* object, gpointer data) |
|
567 { |
|
568 if (ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize) |
|
569 ATK_OBJECT_CLASS(webkit_accessible_parent_class)->initialize(object, data); |
|
570 |
|
571 WEBKIT_ACCESSIBLE(object)->m_object = reinterpret_cast<AccessibilityObject*>(data); |
|
572 } |
|
573 |
|
574 static void webkit_accessible_finalize(GObject* object) |
|
575 { |
|
576 // This is a good time to clear the return buffer. |
|
577 returnString(String()); |
|
578 |
|
579 G_OBJECT_CLASS(webkit_accessible_parent_class)->finalize(object); |
|
580 } |
|
581 |
|
582 static void webkit_accessible_class_init(AtkObjectClass* klass) |
|
583 { |
|
584 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); |
|
585 |
|
586 webkit_accessible_parent_class = g_type_class_peek_parent(klass); |
|
587 |
|
588 gobjectClass->finalize = webkit_accessible_finalize; |
|
589 |
|
590 klass->initialize = webkit_accessible_init; |
|
591 klass->get_name = webkit_accessible_get_name; |
|
592 klass->get_description = webkit_accessible_get_description; |
|
593 klass->get_parent = webkit_accessible_get_parent; |
|
594 klass->get_n_children = webkit_accessible_get_n_children; |
|
595 klass->ref_child = webkit_accessible_ref_child; |
|
596 klass->get_role = webkit_accessible_get_role; |
|
597 klass->ref_state_set = webkit_accessible_ref_state_set; |
|
598 klass->get_index_in_parent = webkit_accessible_get_index_in_parent; |
|
599 klass->get_attributes = webkit_accessible_get_attributes; |
|
600 klass->ref_relation_set = webkit_accessible_ref_relation_set; |
|
601 } |
|
602 |
|
603 GType |
|
604 webkit_accessible_get_type(void) |
|
605 { |
|
606 static volatile gsize type_volatile = 0; |
|
607 |
|
608 if (g_once_init_enter(&type_volatile)) { |
|
609 static const GTypeInfo tinfo = { |
|
610 sizeof(WebKitAccessibleClass), |
|
611 (GBaseInitFunc) 0, |
|
612 (GBaseFinalizeFunc) 0, |
|
613 (GClassInitFunc) webkit_accessible_class_init, |
|
614 (GClassFinalizeFunc) 0, |
|
615 0, /* class data */ |
|
616 sizeof(WebKitAccessible), /* instance size */ |
|
617 0, /* nb preallocs */ |
|
618 (GInstanceInitFunc) 0, |
|
619 0 /* value table */ |
|
620 }; |
|
621 |
|
622 GType type = g_type_register_static(ATK_TYPE_OBJECT, |
|
623 "WebKitAccessible", &tinfo, GTypeFlags(0)); |
|
624 g_once_init_leave(&type_volatile, type); |
|
625 } |
|
626 |
|
627 return type_volatile; |
|
628 } |
|
629 |
|
630 static gboolean webkit_accessible_action_do_action(AtkAction* action, gint i) |
|
631 { |
|
632 g_return_val_if_fail(i == 0, FALSE); |
|
633 return core(action)->performDefaultAction(); |
|
634 } |
|
635 |
|
636 static gint webkit_accessible_action_get_n_actions(AtkAction* action) |
|
637 { |
|
638 return 1; |
|
639 } |
|
640 |
|
641 static const gchar* webkit_accessible_action_get_description(AtkAction* action, gint i) |
|
642 { |
|
643 g_return_val_if_fail(i == 0, 0); |
|
644 // TODO: Need a way to provide/localize action descriptions. |
|
645 notImplemented(); |
|
646 return ""; |
|
647 } |
|
648 |
|
649 static const gchar* webkit_accessible_action_get_keybinding(AtkAction* action, gint i) |
|
650 { |
|
651 g_return_val_if_fail(i == 0, 0); |
|
652 // FIXME: Construct a proper keybinding string. |
|
653 return returnString(core(action)->accessKey().string()); |
|
654 } |
|
655 |
|
656 static const gchar* webkit_accessible_action_get_name(AtkAction* action, gint i) |
|
657 { |
|
658 g_return_val_if_fail(i == 0, 0); |
|
659 return returnString(core(action)->actionVerb()); |
|
660 } |
|
661 |
|
662 static void atk_action_interface_init(AtkActionIface* iface) |
|
663 { |
|
664 iface->do_action = webkit_accessible_action_do_action; |
|
665 iface->get_n_actions = webkit_accessible_action_get_n_actions; |
|
666 iface->get_description = webkit_accessible_action_get_description; |
|
667 iface->get_keybinding = webkit_accessible_action_get_keybinding; |
|
668 iface->get_name = webkit_accessible_action_get_name; |
|
669 } |
|
670 |
|
671 // Selection (for controls) |
|
672 |
|
673 static AccessibilityObject* optionFromList(AtkSelection* selection, gint i) |
|
674 { |
|
675 AccessibilityObject* coreSelection = core(selection); |
|
676 if (!coreSelection || i < 0) |
|
677 return 0; |
|
678 |
|
679 AccessibilityRenderObject::AccessibilityChildrenVector options = core(selection)->children(); |
|
680 if (i < static_cast<gint>(options.size())) |
|
681 return options.at(i).get(); |
|
682 |
|
683 return 0; |
|
684 } |
|
685 |
|
686 static AccessibilityObject* optionFromSelection(AtkSelection* selection, gint i) |
|
687 { |
|
688 // i is the ith selection as opposed to the ith child. |
|
689 |
|
690 AccessibilityObject* coreSelection = core(selection); |
|
691 if (!coreSelection || i < 0) |
|
692 return 0; |
|
693 |
|
694 AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; |
|
695 if (coreSelection->isListBox()) |
|
696 static_cast<AccessibilityListBox*>(coreSelection)->selectedChildren(selectedItems); |
|
697 |
|
698 // TODO: Combo boxes |
|
699 |
|
700 if (i < static_cast<gint>(selectedItems.size())) |
|
701 return selectedItems.at(i).get(); |
|
702 |
|
703 return 0; |
|
704 } |
|
705 |
|
706 static gboolean webkit_accessible_selection_add_selection(AtkSelection* selection, gint i) |
|
707 { |
|
708 AccessibilityObject* option = optionFromList(selection, i); |
|
709 if (option && core(selection)->isListBox()) { |
|
710 AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(option); |
|
711 listBoxOption->setSelected(true); |
|
712 return listBoxOption->isSelected(); |
|
713 } |
|
714 |
|
715 return false; |
|
716 } |
|
717 |
|
718 static gboolean webkit_accessible_selection_clear_selection(AtkSelection* selection) |
|
719 { |
|
720 AccessibilityObject* coreSelection = core(selection); |
|
721 if (!coreSelection) |
|
722 return false; |
|
723 |
|
724 AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; |
|
725 if (coreSelection->isListBox()) { |
|
726 // Set the list of selected items to an empty list; then verify that it worked. |
|
727 AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); |
|
728 listBox->setSelectedChildren(selectedItems); |
|
729 listBox->selectedChildren(selectedItems); |
|
730 return selectedItems.size() == 0; |
|
731 } |
|
732 return false; |
|
733 } |
|
734 |
|
735 static AtkObject* webkit_accessible_selection_ref_selection(AtkSelection* selection, gint i) |
|
736 { |
|
737 AccessibilityObject* option = optionFromSelection(selection, i); |
|
738 if (option) { |
|
739 AtkObject* child = option->wrapper(); |
|
740 g_object_ref(child); |
|
741 return child; |
|
742 } |
|
743 |
|
744 return 0; |
|
745 } |
|
746 |
|
747 static gint webkit_accessible_selection_get_selection_count(AtkSelection* selection) |
|
748 { |
|
749 AccessibilityObject* coreSelection = core(selection); |
|
750 if (coreSelection && coreSelection->isListBox()) { |
|
751 AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; |
|
752 static_cast<AccessibilityListBox*>(coreSelection)->selectedChildren(selectedItems); |
|
753 return static_cast<gint>(selectedItems.size()); |
|
754 } |
|
755 |
|
756 return 0; |
|
757 } |
|
758 |
|
759 static gboolean webkit_accessible_selection_is_child_selected(AtkSelection* selection, gint i) |
|
760 { |
|
761 AccessibilityObject* option = optionFromList(selection, i); |
|
762 if (option && core(selection)->isListBox()) |
|
763 return static_cast<AccessibilityListBoxOption*>(option)->isSelected(); |
|
764 |
|
765 return false; |
|
766 } |
|
767 |
|
768 static gboolean webkit_accessible_selection_remove_selection(AtkSelection* selection, gint i) |
|
769 { |
|
770 // TODO: This is only getting called if i == 0. What is preventing the rest? |
|
771 AccessibilityObject* option = optionFromSelection(selection, i); |
|
772 if (option && core(selection)->isListBox()) { |
|
773 AccessibilityListBoxOption* listBoxOption = static_cast<AccessibilityListBoxOption*>(option); |
|
774 listBoxOption->setSelected(false); |
|
775 return !listBoxOption->isSelected(); |
|
776 } |
|
777 |
|
778 return false; |
|
779 } |
|
780 |
|
781 static gboolean webkit_accessible_selection_select_all_selection(AtkSelection* selection) |
|
782 { |
|
783 AccessibilityObject* coreSelection = core(selection); |
|
784 if (!coreSelection || !coreSelection->isMultiSelectable()) |
|
785 return false; |
|
786 |
|
787 AccessibilityRenderObject::AccessibilityChildrenVector children = coreSelection->children(); |
|
788 if (coreSelection->isListBox()) { |
|
789 AccessibilityListBox* listBox = static_cast<AccessibilityListBox*>(coreSelection); |
|
790 listBox->setSelectedChildren(children); |
|
791 AccessibilityRenderObject::AccessibilityChildrenVector selectedItems; |
|
792 listBox->selectedChildren(selectedItems); |
|
793 return selectedItems.size() == children.size(); |
|
794 } |
|
795 |
|
796 return false; |
|
797 } |
|
798 |
|
799 static void atk_selection_interface_init(AtkSelectionIface* iface) |
|
800 { |
|
801 iface->add_selection = webkit_accessible_selection_add_selection; |
|
802 iface->clear_selection = webkit_accessible_selection_clear_selection; |
|
803 iface->ref_selection = webkit_accessible_selection_ref_selection; |
|
804 iface->get_selection_count = webkit_accessible_selection_get_selection_count; |
|
805 iface->is_child_selected = webkit_accessible_selection_is_child_selected; |
|
806 iface->remove_selection = webkit_accessible_selection_remove_selection; |
|
807 iface->select_all_selection = webkit_accessible_selection_select_all_selection; |
|
808 } |
|
809 |
|
810 // Text |
|
811 |
|
812 static gchar* utf8Substr(const gchar* string, gint start, gint end) |
|
813 { |
|
814 ASSERT(string); |
|
815 glong strLen = g_utf8_strlen(string, -1); |
|
816 if (start > strLen || end > strLen) |
|
817 return 0; |
|
818 gchar* startPtr = g_utf8_offset_to_pointer(string, start); |
|
819 gsize lenInBytes = g_utf8_offset_to_pointer(string, end) - startPtr + 1; |
|
820 gchar* output = static_cast<gchar*>(g_malloc0(lenInBytes + 1)); |
|
821 return g_utf8_strncpy(output, startPtr, end - start + 1); |
|
822 } |
|
823 |
|
824 // This function is not completely general, is it's tied to the |
|
825 // internals of WebCore's text presentation. |
|
826 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to) |
|
827 { |
|
828 CString stringUTF8 = UTF8Encoding().encode(characters, length, QuestionMarksForUnencodables); |
|
829 gchar* utf8String = utf8Substr(stringUTF8.data(), from, to); |
|
830 if (!g_utf8_validate(utf8String, -1, 0)) { |
|
831 g_free(utf8String); |
|
832 return 0; |
|
833 } |
|
834 gsize len = strlen(utf8String); |
|
835 GString* ret = g_string_new_len(0, len); |
|
836 gchar* ptr = utf8String; |
|
837 |
|
838 // WebCore introduces line breaks in the text that do not reflect |
|
839 // the layout you see on the screen, replace them with spaces |
|
840 while (len > 0) { |
|
841 gint index, start; |
|
842 pango_find_paragraph_boundary(ptr, len, &index, &start); |
|
843 g_string_append_len(ret, ptr, index); |
|
844 if (index == start) |
|
845 break; |
|
846 g_string_append_c(ret, ' '); |
|
847 ptr += start; |
|
848 len -= start; |
|
849 } |
|
850 |
|
851 g_free(utf8String); |
|
852 return g_string_free(ret, FALSE); |
|
853 } |
|
854 |
|
855 gchar* textForObject(AccessibilityRenderObject* accObject) |
|
856 { |
|
857 GString* str = g_string_new(0); |
|
858 |
|
859 // For text controls, we can get the text line by line. |
|
860 if (accObject->isTextControl()) { |
|
861 unsigned textLength = accObject->textLength(); |
|
862 int lineNumber = 0; |
|
863 PlainTextRange range = accObject->doAXRangeForLine(lineNumber); |
|
864 while (range.length) { |
|
865 // When a line of text wraps in a text area, the final space is removed. |
|
866 if (range.start + range.length < textLength) |
|
867 range.length -= 1; |
|
868 String lineText = accObject->doAXStringForRange(range); |
|
869 g_string_append(str, lineText.utf8().data()); |
|
870 g_string_append(str, "\n"); |
|
871 range = accObject->doAXRangeForLine(++lineNumber); |
|
872 } |
|
873 } else if (accObject->renderer()) { |
|
874 // For RenderBlocks, piece together the text from the RenderText objects they contain. |
|
875 for (RenderObject* obj = accObject->renderer()->firstChild(); obj; obj = obj->nextSibling()) { |
|
876 if (obj->isBR()) { |
|
877 g_string_append(str, "\n"); |
|
878 continue; |
|
879 } |
|
880 |
|
881 RenderText* renderText; |
|
882 if (obj->isText()) |
|
883 renderText = toRenderText(obj); |
|
884 else if (obj->firstChild() && obj->firstChild()->isText()) { |
|
885 // Handle RenderInlines (and any other similiar RenderObjects). |
|
886 renderText = toRenderText(obj->firstChild()); |
|
887 } else |
|
888 continue; |
|
889 |
|
890 InlineTextBox* box = renderText->firstTextBox(); |
|
891 while (box) { |
|
892 gchar* text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end()); |
|
893 g_string_append(str, text); |
|
894 // Newline chars in the source result in separate text boxes, so check |
|
895 // before adding a newline in the layout. See bug 25415 comment #78. |
|
896 // If the next sibling is a BR, we'll add the newline when we examine that child. |
|
897 if (!box->nextOnLineExists() && (!obj->nextSibling() || !obj->nextSibling()->isBR())) |
|
898 g_string_append(str, "\n"); |
|
899 box = box->nextTextBox(); |
|
900 } |
|
901 } |
|
902 } |
|
903 return g_string_free(str, FALSE); |
|
904 } |
|
905 |
|
906 static gchar* webkit_accessible_text_get_text(AtkText* text, gint startOffset, gint endOffset) |
|
907 { |
|
908 AccessibilityObject* coreObject = core(text); |
|
909 String ret; |
|
910 unsigned start = startOffset; |
|
911 if (endOffset == -1) { |
|
912 endOffset = coreObject->stringValue().length(); |
|
913 if (!endOffset) |
|
914 endOffset = coreObject->textUnderElement().length(); |
|
915 } |
|
916 int length = endOffset - startOffset; |
|
917 |
|
918 if (coreObject->isTextControl()) |
|
919 ret = coreObject->doAXStringForRange(PlainTextRange(start, length)); |
|
920 else |
|
921 ret = coreObject->textUnderElement().substring(start, length); |
|
922 |
|
923 if (!ret.length()) { |
|
924 // This can happen at least with anonymous RenderBlocks (e.g. body text amongst paragraphs) |
|
925 ret = String(textForObject(static_cast<AccessibilityRenderObject*>(coreObject))); |
|
926 if (!endOffset) |
|
927 endOffset = ret.length(); |
|
928 ret = ret.substring(start, endOffset - startOffset); |
|
929 } |
|
930 |
|
931 return g_strdup(ret.utf8().data()); |
|
932 } |
|
933 |
|
934 static GailTextUtil* getGailTextUtilForAtk(AtkText* textObject) |
|
935 { |
|
936 gpointer data = g_object_get_data(G_OBJECT(textObject), "webkit-accessible-gail-text-util"); |
|
937 if (data) |
|
938 return static_cast<GailTextUtil*>(data); |
|
939 |
|
940 GailTextUtil* gailTextUtil = gail_text_util_new(); |
|
941 gail_text_util_text_setup(gailTextUtil, webkit_accessible_text_get_text(textObject, 0, -1)); |
|
942 g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-gail-text-util", gailTextUtil, g_object_unref); |
|
943 return gailTextUtil; |
|
944 } |
|
945 |
|
946 static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) |
|
947 { |
|
948 AccessibilityObject* coreObject = core(textObject); |
|
949 |
|
950 HostWindow* hostWindow = coreObject->document()->view()->hostWindow(); |
|
951 if (!hostWindow) |
|
952 return 0; |
|
953 PlatformPageClient webView = hostWindow->platformPageClient(); |
|
954 if (!webView) |
|
955 return 0; |
|
956 |
|
957 AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); |
|
958 if (!accObject) |
|
959 return 0; |
|
960 |
|
961 // Create a string with the layout as it appears on the screen |
|
962 PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), textForObject(accObject)); |
|
963 g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref); |
|
964 return layout; |
|
965 } |
|
966 |
|
967 static gchar* webkit_accessible_text_get_text_after_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) |
|
968 { |
|
969 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AFTER_OFFSET, boundaryType, offset, startOffset, endOffset); |
|
970 } |
|
971 |
|
972 static gchar* webkit_accessible_text_get_text_at_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) |
|
973 { |
|
974 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_AT_OFFSET, boundaryType, offset, startOffset, endOffset); |
|
975 } |
|
976 |
|
977 static gchar* webkit_accessible_text_get_text_before_offset(AtkText* text, gint offset, AtkTextBoundary boundaryType, gint* startOffset, gint* endOffset) |
|
978 { |
|
979 return gail_text_util_get_text(getGailTextUtilForAtk(text), getPangoLayoutForAtk(text), GAIL_BEFORE_OFFSET, boundaryType, offset, startOffset, endOffset); |
|
980 } |
|
981 |
|
982 static gunichar webkit_accessible_text_get_character_at_offset(AtkText* text, gint offset) |
|
983 { |
|
984 notImplemented(); |
|
985 return 0; |
|
986 } |
|
987 |
|
988 static gint webkit_accessible_text_get_caret_offset(AtkText* text) |
|
989 { |
|
990 // coreObject is the unignored object whose offset the caller is requesting. |
|
991 // focusedObject is the object with the caret. It is likely ignored -- unless it's a link. |
|
992 AccessibilityObject* coreObject = core(text); |
|
993 Node* focusedNode = coreObject->selection().end().node(); |
|
994 |
|
995 if (!focusedNode) |
|
996 return 0; |
|
997 |
|
998 RenderObject* focusedRenderer = focusedNode->renderer(); |
|
999 AccessibilityObject* focusedObject = coreObject->document()->axObjectCache()->getOrCreate(focusedRenderer); |
|
1000 |
|
1001 int offset; |
|
1002 // Don't ignore links if the offset is being requested for a link. |
|
1003 objectAndOffsetUnignored(focusedObject, offset, !coreObject->isLink()); |
|
1004 |
|
1005 // TODO: Verify this for RTL text. |
|
1006 return offset; |
|
1007 } |
|
1008 |
|
1009 static AtkAttributeSet* getAttributeSetForAccessibilityObject(const AccessibilityObject* object) |
|
1010 { |
|
1011 if (!object->isAccessibilityRenderObject()) |
|
1012 return 0; |
|
1013 |
|
1014 RenderObject* renderer = static_cast<const AccessibilityRenderObject*>(object)->renderer(); |
|
1015 RenderStyle* style = renderer->style(); |
|
1016 |
|
1017 AtkAttributeSet* result = 0; |
|
1018 GOwnPtr<gchar> buffer(g_strdup_printf("%i", style->fontSize())); |
|
1019 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_SIZE), buffer.get()); |
|
1020 |
|
1021 Color bgColor = style->visitedDependentColor(CSSPropertyBackgroundColor); |
|
1022 if (bgColor.isValid()) { |
|
1023 buffer.set(g_strdup_printf("%i,%i,%i", |
|
1024 bgColor.red(), bgColor.green(), bgColor.blue())); |
|
1025 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_BG_COLOR), buffer.get()); |
|
1026 } |
|
1027 |
|
1028 Color fgColor = style->visitedDependentColor(CSSPropertyColor); |
|
1029 if (fgColor.isValid()) { |
|
1030 buffer.set(g_strdup_printf("%i,%i,%i", |
|
1031 fgColor.red(), fgColor.green(), fgColor.blue())); |
|
1032 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FG_COLOR), buffer.get()); |
|
1033 } |
|
1034 |
|
1035 int baselinePosition; |
|
1036 bool includeRise = true; |
|
1037 switch (style->verticalAlign()) { |
|
1038 case SUB: |
|
1039 baselinePosition = -1 * renderer->baselinePosition(true); |
|
1040 break; |
|
1041 case SUPER: |
|
1042 baselinePosition = renderer->baselinePosition(true); |
|
1043 break; |
|
1044 case BASELINE: |
|
1045 baselinePosition = 0; |
|
1046 break; |
|
1047 default: |
|
1048 includeRise = false; |
|
1049 break; |
|
1050 } |
|
1051 |
|
1052 if (includeRise) { |
|
1053 buffer.set(g_strdup_printf("%i", baselinePosition)); |
|
1054 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_RISE), buffer.get()); |
|
1055 } |
|
1056 |
|
1057 int indentation = style->textIndent().calcValue(object->size().width()); |
|
1058 if (indentation != undefinedLength) { |
|
1059 buffer.set(g_strdup_printf("%i", indentation)); |
|
1060 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INDENT), buffer.get()); |
|
1061 } |
|
1062 |
|
1063 String fontFamilyName = style->font().family().family().string(); |
|
1064 if (fontFamilyName.left(8) == "-webkit-") |
|
1065 fontFamilyName = fontFamilyName.substring(8); |
|
1066 |
|
1067 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_FAMILY_NAME), fontFamilyName.utf8().data()); |
|
1068 |
|
1069 int fontWeight = -1; |
|
1070 switch (style->font().weight()) { |
|
1071 case FontWeight100: |
|
1072 fontWeight = 100; |
|
1073 break; |
|
1074 case FontWeight200: |
|
1075 fontWeight = 200; |
|
1076 break; |
|
1077 case FontWeight300: |
|
1078 fontWeight = 300; |
|
1079 break; |
|
1080 case FontWeight400: |
|
1081 fontWeight = 400; |
|
1082 break; |
|
1083 case FontWeight500: |
|
1084 fontWeight = 500; |
|
1085 break; |
|
1086 case FontWeight600: |
|
1087 fontWeight = 600; |
|
1088 break; |
|
1089 case FontWeight700: |
|
1090 fontWeight = 700; |
|
1091 break; |
|
1092 case FontWeight800: |
|
1093 fontWeight = 800; |
|
1094 break; |
|
1095 case FontWeight900: |
|
1096 fontWeight = 900; |
|
1097 } |
|
1098 if (fontWeight > 0) { |
|
1099 buffer.set(g_strdup_printf("%i", fontWeight)); |
|
1100 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_WEIGHT), buffer.get()); |
|
1101 } |
|
1102 |
|
1103 switch (style->textAlign()) { |
|
1104 case TAAUTO: |
|
1105 break; |
|
1106 case LEFT: |
|
1107 case WEBKIT_LEFT: |
|
1108 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "left"); |
|
1109 break; |
|
1110 case RIGHT: |
|
1111 case WEBKIT_RIGHT: |
|
1112 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "right"); |
|
1113 break; |
|
1114 case CENTER: |
|
1115 case WEBKIT_CENTER: |
|
1116 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "center"); |
|
1117 break; |
|
1118 case JUSTIFY: |
|
1119 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_JUSTIFICATION), "fill"); |
|
1120 } |
|
1121 |
|
1122 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_UNDERLINE), (style->textDecoration() & UNDERLINE) ? "single" : "none"); |
|
1123 |
|
1124 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STYLE), style->font().italic() ? "italic" : "normal"); |
|
1125 |
|
1126 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_STRIKETHROUGH), (style->textDecoration() & LINE_THROUGH) ? "true" : "false"); |
|
1127 |
|
1128 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_INVISIBLE), (style->visibility() == HIDDEN) ? "true" : "false"); |
|
1129 |
|
1130 result = addAttributeToSet(result, atk_text_attribute_get_name(ATK_TEXT_ATTR_EDITABLE), object->isReadOnly() ? "false" : "true"); |
|
1131 |
|
1132 return result; |
|
1133 } |
|
1134 |
|
1135 static gint compareAttribute(const AtkAttribute* a, const AtkAttribute* b) |
|
1136 { |
|
1137 return g_strcmp0(a->name, b->name) || g_strcmp0(a->value, b->value); |
|
1138 } |
|
1139 |
|
1140 // Returns an AtkAttributeSet with the elements of a1 which are either |
|
1141 // not present or different in a2. Neither a1 nor a2 should be used |
|
1142 // after calling this function. |
|
1143 static AtkAttributeSet* attributeSetDifference(AtkAttributeSet* a1, AtkAttributeSet* a2) |
|
1144 { |
|
1145 if (!a2) |
|
1146 return a1; |
|
1147 |
|
1148 AtkAttributeSet* i = a1; |
|
1149 AtkAttributeSet* found; |
|
1150 AtkAttributeSet* toDelete = 0; |
|
1151 |
|
1152 while (i) { |
|
1153 found = g_slist_find_custom(a2, i->data, (GCompareFunc)compareAttribute); |
|
1154 if (found) { |
|
1155 AtkAttributeSet* t = i->next; |
|
1156 toDelete = g_slist_prepend(toDelete, i->data); |
|
1157 a1 = g_slist_delete_link(a1, i); |
|
1158 i = t; |
|
1159 } else |
|
1160 i = i->next; |
|
1161 } |
|
1162 |
|
1163 atk_attribute_set_free(a2); |
|
1164 atk_attribute_set_free(toDelete); |
|
1165 return a1; |
|
1166 } |
|
1167 |
|
1168 static guint accessibilityObjectLength(const AccessibilityObject* object) |
|
1169 { |
|
1170 GOwnPtr<gchar> text(webkit_accessible_text_get_text(ATK_TEXT(object->wrapper()), 0, -1)); |
|
1171 return g_utf8_strlen(text.get(), -1); |
|
1172 } |
|
1173 |
|
1174 static const AccessibilityObject* getAccessibilityObjectForOffset(const AccessibilityObject* object, guint offset, gint* startOffset, gint* endOffset) |
|
1175 { |
|
1176 const AccessibilityObject* result; |
|
1177 guint length = accessibilityObjectLength(object); |
|
1178 if (length > offset) { |
|
1179 *startOffset = 0; |
|
1180 *endOffset = length; |
|
1181 result = object; |
|
1182 } else { |
|
1183 *startOffset = -1; |
|
1184 *endOffset = -1; |
|
1185 result = 0; |
|
1186 } |
|
1187 |
|
1188 if (!object->firstChild()) |
|
1189 return result; |
|
1190 |
|
1191 AccessibilityObject* child = object->firstChild(); |
|
1192 guint currentOffset = 0; |
|
1193 guint childPosition = 0; |
|
1194 while (child && currentOffset <= offset) { |
|
1195 guint childLength = accessibilityObjectLength(child); |
|
1196 currentOffset = childLength + childPosition; |
|
1197 if (currentOffset > offset) { |
|
1198 gint childStartOffset; |
|
1199 gint childEndOffset; |
|
1200 const AccessibilityObject* grandChild = getAccessibilityObjectForOffset(child, offset-childPosition, &childStartOffset, &childEndOffset); |
|
1201 if (childStartOffset >= 0) { |
|
1202 *startOffset = childStartOffset + childPosition; |
|
1203 *endOffset = childEndOffset + childPosition; |
|
1204 result = grandChild; |
|
1205 } |
|
1206 } else { |
|
1207 childPosition += childLength; |
|
1208 child = child->nextSibling(); |
|
1209 } |
|
1210 } |
|
1211 return result; |
|
1212 } |
|
1213 |
|
1214 static AtkAttributeSet* getRunAttributesFromAccesibilityObject(const AccessibilityObject* element, gint offset, gint* startOffset, gint* endOffset) |
|
1215 { |
|
1216 const AccessibilityObject *child = getAccessibilityObjectForOffset(element, offset, startOffset, endOffset); |
|
1217 if (!child) { |
|
1218 *startOffset = -1; |
|
1219 *endOffset = -1; |
|
1220 return 0; |
|
1221 } |
|
1222 |
|
1223 AtkAttributeSet* defaultAttributes = getAttributeSetForAccessibilityObject(element); |
|
1224 AtkAttributeSet* childAttributes = getAttributeSetForAccessibilityObject(child); |
|
1225 |
|
1226 return attributeSetDifference(childAttributes, defaultAttributes); |
|
1227 } |
|
1228 |
|
1229 static AtkAttributeSet* webkit_accessible_text_get_run_attributes(AtkText* text, gint offset, gint* startOffset, gint* endOffset) |
|
1230 { |
|
1231 AccessibilityObject* coreObject = core(text); |
|
1232 AtkAttributeSet* result; |
|
1233 |
|
1234 if (!coreObject) { |
|
1235 *startOffset = 0; |
|
1236 *endOffset = atk_text_get_character_count(text); |
|
1237 return 0; |
|
1238 } |
|
1239 |
|
1240 if (offset == -1) |
|
1241 offset = atk_text_get_caret_offset(text); |
|
1242 |
|
1243 result = getRunAttributesFromAccesibilityObject(coreObject, offset, startOffset, endOffset); |
|
1244 |
|
1245 if (*startOffset < 0) { |
|
1246 *startOffset = offset; |
|
1247 *endOffset = offset; |
|
1248 } |
|
1249 |
|
1250 return result; |
|
1251 } |
|
1252 |
|
1253 static AtkAttributeSet* webkit_accessible_text_get_default_attributes(AtkText* text) |
|
1254 { |
|
1255 AccessibilityObject* coreObject = core(text); |
|
1256 if (!coreObject || !coreObject->isAccessibilityRenderObject()) |
|
1257 return 0; |
|
1258 |
|
1259 return getAttributeSetForAccessibilityObject(coreObject); |
|
1260 } |
|
1261 |
|
1262 static void webkit_accessible_text_get_character_extents(AtkText* text, gint offset, gint* x, gint* y, gint* width, gint* height, AtkCoordType coords) |
|
1263 { |
|
1264 IntRect extents = core(text)->doAXBoundsForRange(PlainTextRange(offset, 1)); |
|
1265 // FIXME: Use the AtkCoordType |
|
1266 // Requires WebCore::ScrollView::contentsToScreen() to be implemented |
|
1267 |
|
1268 #if 0 |
|
1269 switch(coords) { |
|
1270 case ATK_XY_SCREEN: |
|
1271 extents = core(text)->document()->view()->contentsToScreen(extents); |
|
1272 break; |
|
1273 case ATK_XY_WINDOW: |
|
1274 // No-op |
|
1275 break; |
|
1276 } |
|
1277 #endif |
|
1278 |
|
1279 *x = extents.x(); |
|
1280 *y = extents.y(); |
|
1281 *width = extents.width(); |
|
1282 *height = extents.height(); |
|
1283 } |
|
1284 |
|
1285 static gint webkit_accessible_text_get_character_count(AtkText* text) |
|
1286 { |
|
1287 AccessibilityObject* coreObject = core(text); |
|
1288 |
|
1289 if (coreObject->isTextControl()) |
|
1290 return coreObject->textLength(); |
|
1291 else |
|
1292 return coreObject->textUnderElement().length(); |
|
1293 } |
|
1294 |
|
1295 static gint webkit_accessible_text_get_offset_at_point(AtkText* text, gint x, gint y, AtkCoordType coords) |
|
1296 { |
|
1297 // FIXME: Use the AtkCoordType |
|
1298 // TODO: Is it correct to ignore range.length? |
|
1299 IntPoint pos(x, y); |
|
1300 PlainTextRange range = core(text)->doAXRangeForPosition(pos); |
|
1301 return range.start; |
|
1302 } |
|
1303 |
|
1304 static bool selectionBelongsToObject(AccessibilityObject* coreObject, VisibleSelection& selection) |
|
1305 { |
|
1306 if (!coreObject->isAccessibilityRenderObject()) |
|
1307 return false; |
|
1308 |
|
1309 Node* node = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); |
|
1310 return node == selection.base().containerNode(); |
|
1311 } |
|
1312 |
|
1313 static gint webkit_accessible_text_get_n_selections(AtkText* text) |
|
1314 { |
|
1315 AccessibilityObject* coreObject = core(text); |
|
1316 VisibleSelection selection = coreObject->selection(); |
|
1317 |
|
1318 // We don't support multiple selections for now, so there's only |
|
1319 // two possibilities |
|
1320 // Also, we don't want to do anything if the selection does not |
|
1321 // belong to the currently selected object. We have to check since |
|
1322 // there's no way to get the selection for a given object, only |
|
1323 // the global one (the API is a bit confusing) |
|
1324 return !selectionBelongsToObject(coreObject, selection) || selection.isNone() ? 0 : 1; |
|
1325 } |
|
1326 |
|
1327 static gchar* webkit_accessible_text_get_selection(AtkText* text, gint selection_num, gint* start_offset, gint* end_offset) |
|
1328 { |
|
1329 AccessibilityObject* coreObject = core(text); |
|
1330 VisibleSelection selection = coreObject->selection(); |
|
1331 |
|
1332 // WebCore does not support multiple selection, so anything but 0 does not make sense for now. |
|
1333 // Also, we don't want to do anything if the selection does not |
|
1334 // belong to the currently selected object. We have to check since |
|
1335 // there's no way to get the selection for a given object, only |
|
1336 // the global one (the API is a bit confusing) |
|
1337 if (selection_num != 0 || !selectionBelongsToObject(coreObject, selection)) { |
|
1338 *start_offset = *end_offset = 0; |
|
1339 return 0; |
|
1340 } |
|
1341 |
|
1342 *start_offset = selection.start().offsetInContainerNode(); |
|
1343 *end_offset = selection.end().offsetInContainerNode(); |
|
1344 |
|
1345 return webkit_accessible_text_get_text(text, *start_offset, *end_offset); |
|
1346 } |
|
1347 |
|
1348 static gboolean webkit_accessible_text_add_selection(AtkText* text, gint start_offset, gint end_offset) |
|
1349 { |
|
1350 notImplemented(); |
|
1351 return FALSE; |
|
1352 } |
|
1353 |
|
1354 static gboolean webkit_accessible_text_remove_selection(AtkText* text, gint selection_num) |
|
1355 { |
|
1356 notImplemented(); |
|
1357 return FALSE; |
|
1358 } |
|
1359 |
|
1360 static gboolean webkit_accessible_text_set_selection(AtkText* text, gint selection_num, gint start_offset, gint end_offset) |
|
1361 { |
|
1362 notImplemented(); |
|
1363 return FALSE; |
|
1364 } |
|
1365 |
|
1366 static gboolean webkit_accessible_text_set_caret_offset(AtkText* text, gint offset) |
|
1367 { |
|
1368 AccessibilityObject* coreObject = core(text); |
|
1369 |
|
1370 // FIXME: We need to reimplement visiblePositionRangeForRange here |
|
1371 // because the actual function checks the offset is within the |
|
1372 // boundaries of text().length(), but text() only works for text |
|
1373 // controls... |
|
1374 VisiblePosition startPosition = coreObject->visiblePositionForIndex(offset); |
|
1375 startPosition.setAffinity(DOWNSTREAM); |
|
1376 VisiblePosition endPosition = coreObject->visiblePositionForIndex(offset); |
|
1377 VisiblePositionRange range = VisiblePositionRange(startPosition, endPosition); |
|
1378 |
|
1379 coreObject->setSelectedVisiblePositionRange(range); |
|
1380 return TRUE; |
|
1381 } |
|
1382 |
|
1383 static void atk_text_interface_init(AtkTextIface* iface) |
|
1384 { |
|
1385 iface->get_text = webkit_accessible_text_get_text; |
|
1386 iface->get_text_after_offset = webkit_accessible_text_get_text_after_offset; |
|
1387 iface->get_text_at_offset = webkit_accessible_text_get_text_at_offset; |
|
1388 iface->get_character_at_offset = webkit_accessible_text_get_character_at_offset; |
|
1389 iface->get_text_before_offset = webkit_accessible_text_get_text_before_offset; |
|
1390 iface->get_caret_offset = webkit_accessible_text_get_caret_offset; |
|
1391 iface->get_run_attributes = webkit_accessible_text_get_run_attributes; |
|
1392 iface->get_default_attributes = webkit_accessible_text_get_default_attributes; |
|
1393 iface->get_character_extents = webkit_accessible_text_get_character_extents; |
|
1394 iface->get_character_count = webkit_accessible_text_get_character_count; |
|
1395 iface->get_offset_at_point = webkit_accessible_text_get_offset_at_point; |
|
1396 iface->get_n_selections = webkit_accessible_text_get_n_selections; |
|
1397 iface->get_selection = webkit_accessible_text_get_selection; |
|
1398 |
|
1399 // set methods |
|
1400 iface->add_selection = webkit_accessible_text_add_selection; |
|
1401 iface->remove_selection = webkit_accessible_text_remove_selection; |
|
1402 iface->set_selection = webkit_accessible_text_set_selection; |
|
1403 iface->set_caret_offset = webkit_accessible_text_set_caret_offset; |
|
1404 } |
|
1405 |
|
1406 // EditableText |
|
1407 |
|
1408 static gboolean webkit_accessible_editable_text_set_run_attributes(AtkEditableText* text, AtkAttributeSet* attrib_set, gint start_offset, gint end_offset) |
|
1409 { |
|
1410 notImplemented(); |
|
1411 return FALSE; |
|
1412 } |
|
1413 |
|
1414 static void webkit_accessible_editable_text_set_text_contents(AtkEditableText* text, const gchar* string) |
|
1415 { |
|
1416 // FIXME: string nullcheck? |
|
1417 core(text)->setValue(String::fromUTF8(string)); |
|
1418 } |
|
1419 |
|
1420 static void webkit_accessible_editable_text_insert_text(AtkEditableText* text, const gchar* string, gint length, gint* position) |
|
1421 { |
|
1422 // FIXME: string nullcheck? |
|
1423 |
|
1424 AccessibilityObject* coreObject = core(text); |
|
1425 // FIXME: Not implemented in WebCore |
|
1426 //coreObject->setSelectedTextRange(PlainTextRange(*position, 0)); |
|
1427 //coreObject->setSelectedText(String::fromUTF8(string)); |
|
1428 |
|
1429 if (!coreObject->document() || !coreObject->document()->frame()) |
|
1430 return; |
|
1431 coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(*position, 0))); |
|
1432 coreObject->setFocused(true); |
|
1433 // FIXME: We should set position to the actual inserted text length, which may be less than that requested. |
|
1434 if (coreObject->document()->frame()->editor()->insertTextWithoutSendingTextEvent(String::fromUTF8(string), false, 0)) |
|
1435 *position += length; |
|
1436 } |
|
1437 |
|
1438 static void webkit_accessible_editable_text_copy_text(AtkEditableText* text, gint start_pos, gint end_pos) |
|
1439 { |
|
1440 notImplemented(); |
|
1441 } |
|
1442 |
|
1443 static void webkit_accessible_editable_text_cut_text(AtkEditableText* text, gint start_pos, gint end_pos) |
|
1444 { |
|
1445 notImplemented(); |
|
1446 } |
|
1447 |
|
1448 static void webkit_accessible_editable_text_delete_text(AtkEditableText* text, gint start_pos, gint end_pos) |
|
1449 { |
|
1450 AccessibilityObject* coreObject = core(text); |
|
1451 // FIXME: Not implemented in WebCore |
|
1452 //coreObject->setSelectedTextRange(PlainTextRange(start_pos, end_pos - start_pos)); |
|
1453 //coreObject->setSelectedText(String()); |
|
1454 |
|
1455 if (!coreObject->document() || !coreObject->document()->frame()) |
|
1456 return; |
|
1457 coreObject->setSelectedVisiblePositionRange(coreObject->visiblePositionRangeForRange(PlainTextRange(start_pos, end_pos - start_pos))); |
|
1458 coreObject->setFocused(true); |
|
1459 coreObject->document()->frame()->editor()->performDelete(); |
|
1460 } |
|
1461 |
|
1462 static void webkit_accessible_editable_text_paste_text(AtkEditableText* text, gint position) |
|
1463 { |
|
1464 notImplemented(); |
|
1465 } |
|
1466 |
|
1467 static void atk_editable_text_interface_init(AtkEditableTextIface* iface) |
|
1468 { |
|
1469 iface->set_run_attributes = webkit_accessible_editable_text_set_run_attributes; |
|
1470 iface->set_text_contents = webkit_accessible_editable_text_set_text_contents; |
|
1471 iface->insert_text = webkit_accessible_editable_text_insert_text; |
|
1472 iface->copy_text = webkit_accessible_editable_text_copy_text; |
|
1473 iface->cut_text = webkit_accessible_editable_text_cut_text; |
|
1474 iface->delete_text = webkit_accessible_editable_text_delete_text; |
|
1475 iface->paste_text = webkit_accessible_editable_text_paste_text; |
|
1476 } |
|
1477 |
|
1478 static void contentsToAtk(AccessibilityObject* coreObject, AtkCoordType coordType, IntRect rect, gint* x, gint* y, gint* width = 0, gint* height = 0) |
|
1479 { |
|
1480 FrameView* frameView = coreObject->documentFrameView(); |
|
1481 |
|
1482 if (frameView) { |
|
1483 switch (coordType) { |
|
1484 case ATK_XY_WINDOW: |
|
1485 rect = frameView->contentsToWindow(rect); |
|
1486 break; |
|
1487 case ATK_XY_SCREEN: |
|
1488 rect = frameView->contentsToScreen(rect); |
|
1489 break; |
|
1490 } |
|
1491 } |
|
1492 |
|
1493 if (x) |
|
1494 *x = rect.x(); |
|
1495 if (y) |
|
1496 *y = rect.y(); |
|
1497 if (width) |
|
1498 *width = rect.width(); |
|
1499 if (height) |
|
1500 *height = rect.height(); |
|
1501 } |
|
1502 |
|
1503 static IntPoint atkToContents(AccessibilityObject* coreObject, AtkCoordType coordType, gint x, gint y) |
|
1504 { |
|
1505 IntPoint pos(x, y); |
|
1506 |
|
1507 FrameView* frameView = coreObject->documentFrameView(); |
|
1508 if (frameView) { |
|
1509 switch (coordType) { |
|
1510 case ATK_XY_SCREEN: |
|
1511 return frameView->screenToContents(pos); |
|
1512 case ATK_XY_WINDOW: |
|
1513 return frameView->windowToContents(pos); |
|
1514 } |
|
1515 } |
|
1516 |
|
1517 return pos; |
|
1518 } |
|
1519 |
|
1520 static AtkObject* webkit_accessible_component_ref_accessible_at_point(AtkComponent* component, gint x, gint y, AtkCoordType coordType) |
|
1521 { |
|
1522 IntPoint pos = atkToContents(core(component), coordType, x, y); |
|
1523 AccessibilityObject* target = core(component)->doAccessibilityHitTest(pos); |
|
1524 if (!target) |
|
1525 return 0; |
|
1526 g_object_ref(target->wrapper()); |
|
1527 return target->wrapper(); |
|
1528 } |
|
1529 |
|
1530 static void webkit_accessible_component_get_extents(AtkComponent* component, gint* x, gint* y, gint* width, gint* height, AtkCoordType coordType) |
|
1531 { |
|
1532 IntRect rect = core(component)->elementRect(); |
|
1533 contentsToAtk(core(component), coordType, rect, x, y, width, height); |
|
1534 } |
|
1535 |
|
1536 static gboolean webkit_accessible_component_grab_focus(AtkComponent* component) |
|
1537 { |
|
1538 core(component)->setFocused(true); |
|
1539 return core(component)->isFocused(); |
|
1540 } |
|
1541 |
|
1542 static void atk_component_interface_init(AtkComponentIface* iface) |
|
1543 { |
|
1544 iface->ref_accessible_at_point = webkit_accessible_component_ref_accessible_at_point; |
|
1545 iface->get_extents = webkit_accessible_component_get_extents; |
|
1546 iface->grab_focus = webkit_accessible_component_grab_focus; |
|
1547 } |
|
1548 |
|
1549 // Image |
|
1550 |
|
1551 static void webkit_accessible_image_get_image_position(AtkImage* image, gint* x, gint* y, AtkCoordType coordType) |
|
1552 { |
|
1553 IntRect rect = core(image)->elementRect(); |
|
1554 contentsToAtk(core(image), coordType, rect, x, y); |
|
1555 } |
|
1556 |
|
1557 static const gchar* webkit_accessible_image_get_image_description(AtkImage* image) |
|
1558 { |
|
1559 return returnString(core(image)->accessibilityDescription()); |
|
1560 } |
|
1561 |
|
1562 static void webkit_accessible_image_get_image_size(AtkImage* image, gint* width, gint* height) |
|
1563 { |
|
1564 IntSize size = core(image)->size(); |
|
1565 |
|
1566 if (width) |
|
1567 *width = size.width(); |
|
1568 if (height) |
|
1569 *height = size.height(); |
|
1570 } |
|
1571 |
|
1572 static void atk_image_interface_init(AtkImageIface* iface) |
|
1573 { |
|
1574 iface->get_image_position = webkit_accessible_image_get_image_position; |
|
1575 iface->get_image_description = webkit_accessible_image_get_image_description; |
|
1576 iface->get_image_size = webkit_accessible_image_get_image_size; |
|
1577 } |
|
1578 |
|
1579 // Table |
|
1580 |
|
1581 static AccessibilityTableCell* cell(AtkTable* table, guint row, guint column) |
|
1582 { |
|
1583 AccessibilityObject* accTable = core(table); |
|
1584 if (accTable->isAccessibilityRenderObject()) |
|
1585 return static_cast<AccessibilityTable*>(accTable)->cellForColumnAndRow(column, row); |
|
1586 return 0; |
|
1587 } |
|
1588 |
|
1589 static gint cellIndex(AccessibilityTableCell* axCell, AccessibilityTable* axTable) |
|
1590 { |
|
1591 // Calculate the cell's index as if we had a traditional Gtk+ table in |
|
1592 // which cells are all direct children of the table, arranged row-first. |
|
1593 AccessibilityObject::AccessibilityChildrenVector allCells; |
|
1594 axTable->cells(allCells); |
|
1595 AccessibilityObject::AccessibilityChildrenVector::iterator position; |
|
1596 position = std::find(allCells.begin(), allCells.end(), axCell); |
|
1597 if (position == allCells.end()) |
|
1598 return -1; |
|
1599 return position - allCells.begin(); |
|
1600 } |
|
1601 |
|
1602 static AccessibilityTableCell* cellAtIndex(AtkTable* table, gint index) |
|
1603 { |
|
1604 AccessibilityObject* accTable = core(table); |
|
1605 if (accTable->isAccessibilityRenderObject()) { |
|
1606 AccessibilityObject::AccessibilityChildrenVector allCells; |
|
1607 static_cast<AccessibilityTable*>(accTable)->cells(allCells); |
|
1608 if (0 <= index && static_cast<unsigned>(index) < allCells.size()) { |
|
1609 AccessibilityObject* accCell = allCells.at(index).get(); |
|
1610 return static_cast<AccessibilityTableCell*>(accCell); |
|
1611 } |
|
1612 } |
|
1613 return 0; |
|
1614 } |
|
1615 |
|
1616 static AtkObject* webkit_accessible_table_ref_at(AtkTable* table, gint row, gint column) |
|
1617 { |
|
1618 AccessibilityTableCell* axCell = cell(table, row, column); |
|
1619 if (!axCell) |
|
1620 return 0; |
|
1621 return axCell->wrapper(); |
|
1622 } |
|
1623 |
|
1624 static gint webkit_accessible_table_get_index_at(AtkTable* table, gint row, gint column) |
|
1625 { |
|
1626 AccessibilityTableCell* axCell = cell(table, row, column); |
|
1627 AccessibilityTable* axTable = static_cast<AccessibilityTable*>(core(table)); |
|
1628 return cellIndex(axCell, axTable); |
|
1629 } |
|
1630 |
|
1631 static gint webkit_accessible_table_get_column_at_index(AtkTable* table, gint index) |
|
1632 { |
|
1633 AccessibilityTableCell* axCell = cellAtIndex(table, index); |
|
1634 if (axCell){ |
|
1635 pair<int, int> columnRange; |
|
1636 axCell->columnIndexRange(columnRange); |
|
1637 return columnRange.first; |
|
1638 } |
|
1639 return -1; |
|
1640 } |
|
1641 |
|
1642 static gint webkit_accessible_table_get_row_at_index(AtkTable* table, gint index) |
|
1643 { |
|
1644 AccessibilityTableCell* axCell = cellAtIndex(table, index); |
|
1645 if (axCell){ |
|
1646 pair<int, int> rowRange; |
|
1647 axCell->rowIndexRange(rowRange); |
|
1648 return rowRange.first; |
|
1649 } |
|
1650 return -1; |
|
1651 } |
|
1652 |
|
1653 static gint webkit_accessible_table_get_n_columns(AtkTable* table) |
|
1654 { |
|
1655 AccessibilityObject* accTable = core(table); |
|
1656 if (accTable->isAccessibilityRenderObject()) |
|
1657 return static_cast<AccessibilityTable*>(accTable)->columnCount(); |
|
1658 return 0; |
|
1659 } |
|
1660 |
|
1661 static gint webkit_accessible_table_get_n_rows(AtkTable* table) |
|
1662 { |
|
1663 AccessibilityObject* accTable = core(table); |
|
1664 if (accTable->isAccessibilityRenderObject()) |
|
1665 return static_cast<AccessibilityTable*>(accTable)->rowCount(); |
|
1666 return 0; |
|
1667 } |
|
1668 |
|
1669 static gint webkit_accessible_table_get_column_extent_at(AtkTable* table, gint row, gint column) |
|
1670 { |
|
1671 AccessibilityTableCell* axCell = cell(table, row, column); |
|
1672 if (axCell) { |
|
1673 pair<int, int> columnRange; |
|
1674 axCell->columnIndexRange(columnRange); |
|
1675 return columnRange.second; |
|
1676 } |
|
1677 return 0; |
|
1678 } |
|
1679 |
|
1680 static gint webkit_accessible_table_get_row_extent_at(AtkTable* table, gint row, gint column) |
|
1681 { |
|
1682 AccessibilityTableCell* axCell = cell(table, row, column); |
|
1683 if (axCell) { |
|
1684 pair<int, int> rowRange; |
|
1685 axCell->rowIndexRange(rowRange); |
|
1686 return rowRange.second; |
|
1687 } |
|
1688 return 0; |
|
1689 } |
|
1690 |
|
1691 static AtkObject* webkit_accessible_table_get_column_header(AtkTable* table, gint column) |
|
1692 { |
|
1693 AccessibilityObject* accTable = core(table); |
|
1694 if (accTable->isAccessibilityRenderObject()) { |
|
1695 AccessibilityObject::AccessibilityChildrenVector allColumnHeaders; |
|
1696 static_cast<AccessibilityTable*>(accTable)->columnHeaders(allColumnHeaders); |
|
1697 unsigned columnCount = allColumnHeaders.size(); |
|
1698 for (unsigned k = 0; k < columnCount; ++k) { |
|
1699 pair<int, int> columnRange; |
|
1700 AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allColumnHeaders.at(k).get()); |
|
1701 cell->columnIndexRange(columnRange); |
|
1702 if (columnRange.first <= column && column < columnRange.first + columnRange.second) |
|
1703 return allColumnHeaders[k]->wrapper(); |
|
1704 } |
|
1705 } |
|
1706 return 0; |
|
1707 } |
|
1708 |
|
1709 static AtkObject* webkit_accessible_table_get_row_header(AtkTable* table, gint row) |
|
1710 { |
|
1711 AccessibilityObject* accTable = core(table); |
|
1712 if (accTable->isAccessibilityRenderObject()) { |
|
1713 AccessibilityObject::AccessibilityChildrenVector allRowHeaders; |
|
1714 static_cast<AccessibilityTable*>(accTable)->rowHeaders(allRowHeaders); |
|
1715 unsigned rowCount = allRowHeaders.size(); |
|
1716 for (unsigned k = 0; k < rowCount; ++k) { |
|
1717 pair<int, int> rowRange; |
|
1718 AccessibilityTableCell* cell = static_cast<AccessibilityTableCell*>(allRowHeaders.at(k).get()); |
|
1719 cell->rowIndexRange(rowRange); |
|
1720 if (rowRange.first <= row && row < rowRange.first + rowRange.second) |
|
1721 return allRowHeaders[k]->wrapper(); |
|
1722 } |
|
1723 } |
|
1724 return 0; |
|
1725 } |
|
1726 |
|
1727 static AtkObject* webkit_accessible_table_get_caption(AtkTable* table) |
|
1728 { |
|
1729 AccessibilityObject* accTable = core(table); |
|
1730 if (accTable->isAccessibilityRenderObject()) { |
|
1731 Node* node = static_cast<AccessibilityRenderObject*>(accTable)->renderer()->node(); |
|
1732 if (node && node->hasTagName(HTMLNames::tableTag)) { |
|
1733 HTMLTableCaptionElement* caption = static_cast<HTMLTableElement*>(node)->caption(); |
|
1734 if (caption) |
|
1735 return AccessibilityObject::firstAccessibleObjectFromNode(caption->renderer()->node())->wrapper(); |
|
1736 } |
|
1737 } |
|
1738 return 0; |
|
1739 } |
|
1740 |
|
1741 static const gchar* webkit_accessible_table_get_column_description(AtkTable* table, gint column) |
|
1742 { |
|
1743 AtkObject* columnHeader = atk_table_get_column_header(table, column); |
|
1744 if (columnHeader && ATK_IS_TEXT(columnHeader)) |
|
1745 return webkit_accessible_text_get_text(ATK_TEXT(columnHeader), 0, -1); |
|
1746 |
|
1747 return 0; |
|
1748 } |
|
1749 |
|
1750 static const gchar* webkit_accessible_table_get_row_description(AtkTable* table, gint row) |
|
1751 { |
|
1752 AtkObject* rowHeader = atk_table_get_row_header(table, row); |
|
1753 if (rowHeader && ATK_IS_TEXT(rowHeader)) |
|
1754 return webkit_accessible_text_get_text(ATK_TEXT(rowHeader), 0, -1); |
|
1755 |
|
1756 return 0; |
|
1757 } |
|
1758 |
|
1759 static void atk_table_interface_init(AtkTableIface* iface) |
|
1760 { |
|
1761 iface->ref_at = webkit_accessible_table_ref_at; |
|
1762 iface->get_index_at = webkit_accessible_table_get_index_at; |
|
1763 iface->get_column_at_index = webkit_accessible_table_get_column_at_index; |
|
1764 iface->get_row_at_index = webkit_accessible_table_get_row_at_index; |
|
1765 iface->get_n_columns = webkit_accessible_table_get_n_columns; |
|
1766 iface->get_n_rows = webkit_accessible_table_get_n_rows; |
|
1767 iface->get_column_extent_at = webkit_accessible_table_get_column_extent_at; |
|
1768 iface->get_row_extent_at = webkit_accessible_table_get_row_extent_at; |
|
1769 iface->get_column_header = webkit_accessible_table_get_column_header; |
|
1770 iface->get_row_header = webkit_accessible_table_get_row_header; |
|
1771 iface->get_caption = webkit_accessible_table_get_caption; |
|
1772 iface->get_column_description = webkit_accessible_table_get_column_description; |
|
1773 iface->get_row_description = webkit_accessible_table_get_row_description; |
|
1774 } |
|
1775 |
|
1776 static const gchar* documentAttributeValue(AtkDocument* document, const gchar* attribute) |
|
1777 { |
|
1778 Document* coreDocument = core(document)->document(); |
|
1779 if (!coreDocument) |
|
1780 return 0; |
|
1781 |
|
1782 String value = String(); |
|
1783 if (!g_ascii_strcasecmp(attribute, "DocType") && coreDocument->doctype()) |
|
1784 value = coreDocument->doctype()->name(); |
|
1785 else if (!g_ascii_strcasecmp(attribute, "Encoding")) |
|
1786 value = coreDocument->charset(); |
|
1787 else if (!g_ascii_strcasecmp(attribute, "URI")) |
|
1788 value = coreDocument->documentURI(); |
|
1789 if (!value.isEmpty()) |
|
1790 return returnString(value); |
|
1791 |
|
1792 return 0; |
|
1793 } |
|
1794 |
|
1795 static const gchar* webkit_accessible_document_get_attribute_value(AtkDocument* document, const gchar* attribute) |
|
1796 { |
|
1797 return documentAttributeValue(document, attribute); |
|
1798 } |
|
1799 |
|
1800 static AtkAttributeSet* webkit_accessible_document_get_attributes(AtkDocument* document) |
|
1801 { |
|
1802 AtkAttributeSet* attributeSet = 0; |
|
1803 const gchar* attributes [] = {"DocType", "Encoding", "URI"}; |
|
1804 |
|
1805 for (unsigned i = 0; i < G_N_ELEMENTS(attributes); i++) { |
|
1806 const gchar* value = documentAttributeValue(document, attributes[i]); |
|
1807 if (value) |
|
1808 attributeSet = addAttributeToSet(attributeSet, attributes[i], value); |
|
1809 } |
|
1810 |
|
1811 return attributeSet; |
|
1812 } |
|
1813 |
|
1814 static const gchar* webkit_accessible_document_get_locale(AtkDocument* document) |
|
1815 { |
|
1816 |
|
1817 // TODO: Should we fall back on lang xml:lang when the following comes up empty? |
|
1818 String language = core(document)->language(); |
|
1819 if (!language.isEmpty()) |
|
1820 return returnString(language); |
|
1821 |
|
1822 return 0; |
|
1823 } |
|
1824 |
|
1825 static void atk_document_interface_init(AtkDocumentIface* iface) |
|
1826 { |
|
1827 iface->get_document_attribute_value = webkit_accessible_document_get_attribute_value; |
|
1828 iface->get_document_attributes = webkit_accessible_document_get_attributes; |
|
1829 iface->get_document_locale = webkit_accessible_document_get_locale; |
|
1830 } |
|
1831 |
|
1832 static const GInterfaceInfo AtkInterfacesInitFunctions[] = { |
|
1833 {(GInterfaceInitFunc)atk_action_interface_init, |
|
1834 (GInterfaceFinalizeFunc) 0, 0}, |
|
1835 {(GInterfaceInitFunc)atk_selection_interface_init, |
|
1836 (GInterfaceFinalizeFunc) 0, 0}, |
|
1837 {(GInterfaceInitFunc)atk_editable_text_interface_init, |
|
1838 (GInterfaceFinalizeFunc) 0, 0}, |
|
1839 {(GInterfaceInitFunc)atk_text_interface_init, |
|
1840 (GInterfaceFinalizeFunc) 0, 0}, |
|
1841 {(GInterfaceInitFunc)atk_component_interface_init, |
|
1842 (GInterfaceFinalizeFunc) 0, 0}, |
|
1843 {(GInterfaceInitFunc)atk_image_interface_init, |
|
1844 (GInterfaceFinalizeFunc) 0, 0}, |
|
1845 {(GInterfaceInitFunc)atk_table_interface_init, |
|
1846 (GInterfaceFinalizeFunc) 0, 0}, |
|
1847 {(GInterfaceInitFunc)atk_document_interface_init, |
|
1848 (GInterfaceFinalizeFunc) 0, 0} |
|
1849 }; |
|
1850 |
|
1851 enum WAIType { |
|
1852 WAI_ACTION, |
|
1853 WAI_SELECTION, |
|
1854 WAI_EDITABLE_TEXT, |
|
1855 WAI_TEXT, |
|
1856 WAI_COMPONENT, |
|
1857 WAI_IMAGE, |
|
1858 WAI_TABLE, |
|
1859 WAI_DOCUMENT |
|
1860 }; |
|
1861 |
|
1862 static GType GetAtkInterfaceTypeFromWAIType(WAIType type) |
|
1863 { |
|
1864 switch (type) { |
|
1865 case WAI_ACTION: |
|
1866 return ATK_TYPE_ACTION; |
|
1867 case WAI_SELECTION: |
|
1868 return ATK_TYPE_SELECTION; |
|
1869 case WAI_EDITABLE_TEXT: |
|
1870 return ATK_TYPE_EDITABLE_TEXT; |
|
1871 case WAI_TEXT: |
|
1872 return ATK_TYPE_TEXT; |
|
1873 case WAI_COMPONENT: |
|
1874 return ATK_TYPE_COMPONENT; |
|
1875 case WAI_IMAGE: |
|
1876 return ATK_TYPE_IMAGE; |
|
1877 case WAI_TABLE: |
|
1878 return ATK_TYPE_TABLE; |
|
1879 case WAI_DOCUMENT: |
|
1880 return ATK_TYPE_DOCUMENT; |
|
1881 } |
|
1882 |
|
1883 return G_TYPE_INVALID; |
|
1884 } |
|
1885 |
|
1886 static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) |
|
1887 { |
|
1888 guint16 interfaceMask = 0; |
|
1889 |
|
1890 // Component interface is always supported |
|
1891 interfaceMask |= 1 << WAI_COMPONENT; |
|
1892 |
|
1893 // Action |
|
1894 if (!coreObject->actionVerb().isEmpty()) |
|
1895 interfaceMask |= 1 << WAI_ACTION; |
|
1896 |
|
1897 // Selection |
|
1898 if (coreObject->isListBox()) |
|
1899 interfaceMask |= 1 << WAI_SELECTION; |
|
1900 |
|
1901 // Text & Editable Text |
|
1902 AccessibilityRole role = coreObject->roleValue(); |
|
1903 |
|
1904 if (role == StaticTextRole) |
|
1905 interfaceMask |= 1 << WAI_TEXT; |
|
1906 else if (coreObject->isAccessibilityRenderObject()) |
|
1907 if (coreObject->isTextControl()) { |
|
1908 interfaceMask |= 1 << WAI_TEXT; |
|
1909 if (!coreObject->isReadOnly()) |
|
1910 interfaceMask |= 1 << WAI_EDITABLE_TEXT; |
|
1911 } else if (role != TableRole && static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->childrenInline()) |
|
1912 interfaceMask |= 1 << WAI_TEXT; |
|
1913 |
|
1914 // Image |
|
1915 if (coreObject->isImage()) |
|
1916 interfaceMask |= 1 << WAI_IMAGE; |
|
1917 |
|
1918 // Table |
|
1919 if (role == TableRole) |
|
1920 interfaceMask |= 1 << WAI_TABLE; |
|
1921 |
|
1922 // Document |
|
1923 if (role == WebAreaRole) |
|
1924 interfaceMask |= 1 << WAI_DOCUMENT; |
|
1925 |
|
1926 return interfaceMask; |
|
1927 } |
|
1928 |
|
1929 static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask) |
|
1930 { |
|
1931 #define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */ |
|
1932 static char name[WAI_TYPE_NAME_LEN + 1]; |
|
1933 |
|
1934 g_sprintf(name, "WAIType%x", interfaceMask); |
|
1935 name[WAI_TYPE_NAME_LEN] = '\0'; |
|
1936 |
|
1937 return name; |
|
1938 } |
|
1939 |
|
1940 static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject) |
|
1941 { |
|
1942 static const GTypeInfo typeInfo = { |
|
1943 sizeof(WebKitAccessibleClass), |
|
1944 (GBaseInitFunc) 0, |
|
1945 (GBaseFinalizeFunc) 0, |
|
1946 (GClassInitFunc) 0, |
|
1947 (GClassFinalizeFunc) 0, |
|
1948 0, /* class data */ |
|
1949 sizeof(WebKitAccessible), /* instance size */ |
|
1950 0, /* nb preallocs */ |
|
1951 (GInstanceInitFunc) 0, |
|
1952 0 /* value table */ |
|
1953 }; |
|
1954 |
|
1955 guint16 interfaceMask = getInterfaceMaskFromObject(coreObject); |
|
1956 const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask); |
|
1957 GType type = g_type_from_name(atkTypeName); |
|
1958 if (type) |
|
1959 return type; |
|
1960 |
|
1961 type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, |
|
1962 atkTypeName, |
|
1963 &typeInfo, GTypeFlags(0)); |
|
1964 for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) { |
|
1965 if (interfaceMask & (1 << i)) |
|
1966 g_type_add_interface_static(type, |
|
1967 GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)), |
|
1968 &AtkInterfacesInitFunctions[i]); |
|
1969 } |
|
1970 |
|
1971 return type; |
|
1972 } |
|
1973 |
|
1974 WebKitAccessible* webkit_accessible_new(AccessibilityObject* coreObject) |
|
1975 { |
|
1976 GType type = getAccessibilityTypeFromObject(coreObject); |
|
1977 AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0)); |
|
1978 |
|
1979 atk_object_initialize(object, coreObject); |
|
1980 |
|
1981 return WEBKIT_ACCESSIBLE(object); |
|
1982 } |
|
1983 |
|
1984 AccessibilityObject* webkit_accessible_get_accessibility_object(WebKitAccessible* accessible) |
|
1985 { |
|
1986 return accessible->m_object; |
|
1987 } |
|
1988 |
|
1989 void webkit_accessible_detach(WebKitAccessible* accessible) |
|
1990 { |
|
1991 ASSERT(accessible->m_object); |
|
1992 |
|
1993 // We replace the WebCore AccessibilityObject with a fallback object that |
|
1994 // provides default implementations to avoid repetitive null-checking after |
|
1995 // detachment. |
|
1996 accessible->m_object = fallbackObject(); |
|
1997 } |
|
1998 |
|
1999 AtkObject* webkit_accessible_get_focused_element(WebKitAccessible* accessible) |
|
2000 { |
|
2001 if (!accessible->m_object) |
|
2002 return 0; |
|
2003 |
|
2004 RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement(); |
|
2005 if (!focusedObj) |
|
2006 return 0; |
|
2007 |
|
2008 return focusedObj->wrapper(); |
|
2009 } |
|
2010 |
|
2011 AccessibilityObject* objectAndOffsetUnignored(AccessibilityObject* coreObject, int& offset, bool ignoreLinks) |
|
2012 { |
|
2013 Node* endNode = static_cast<AccessibilityRenderObject*>(coreObject)->renderer()->node(); |
|
2014 int endOffset = coreObject->selection().end().computeOffsetInContainerNode(); |
|
2015 // Indication that something bogus has transpired. |
|
2016 offset = -1; |
|
2017 |
|
2018 AccessibilityObject* realObject = coreObject; |
|
2019 if (realObject->accessibilityIsIgnored()) |
|
2020 realObject = realObject->parentObjectUnignored(); |
|
2021 |
|
2022 if (ignoreLinks && realObject->isLink()) |
|
2023 realObject = realObject->parentObjectUnignored(); |
|
2024 |
|
2025 Node* node = static_cast<AccessibilityRenderObject*>(realObject)->renderer()->node(); |
|
2026 if (node) { |
|
2027 RefPtr<Range> range = rangeOfContents(node); |
|
2028 if (range->ownerDocument() == node->document()) { |
|
2029 ExceptionCode ec = 0; |
|
2030 range->setEndBefore(endNode, ec); |
|
2031 if (range->boundaryPointsValid()) |
|
2032 offset = range->text().length() + endOffset; |
|
2033 } |
|
2034 } |
|
2035 return realObject; |
|
2036 } |
|
2037 |
|
2038 #endif // HAVE(ACCESSIBILITY) |