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