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