1/* 2 * Copyright (C) 2008 Nuanti Ltd. 3 * Copyright (C) 2009 Jan Alonzo 4 * Copyright (C) 2009, 2010, 2011, 2012 Igalia S.L. 5 * Copyright (C) 2013 Samsung Electronics 6 * 7 * Portions from Mozilla a11y, copyright as follows: 8 * 9 * The Original Code is mozilla.org code. 10 * 11 * The Initial Developer of the Original Code is 12 * Sun Microsystems, Inc. 13 * Portions created by the Initial Developer are Copyright (C) 2002 14 * the Initial Developer. All Rights Reserved. 15 * 16 * This library is free software; you can redistribute it and/or 17 * modify it under the terms of the GNU Library General Public 18 * License as published by the Free Software Foundation; either 19 * version 2 of the License, or (at your option) any later version. 20 * 21 * This library is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 24 * Library General Public License for more details. 25 * 26 * You should have received a copy of the GNU Library General Public License 27 * along with this library; see the file COPYING.LIB. If not, write to 28 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 29 * Boston, MA 02110-1301, USA. 30 */ 31 32#include "config.h" 33#include "WebKitAccessibleWrapperAtk.h" 34 35#if HAVE(ACCESSIBILITY) 36 37#include "AXObjectCache.h" 38#include "Document.h" 39#include "Frame.h" 40#include "FrameView.h" 41#include "HTMLNames.h" 42#include "HTMLTableElement.h" 43#include "HostWindow.h" 44#include "RenderObject.h" 45#include "Settings.h" 46#include "TextIterator.h" 47#include "VisibleUnits.h" 48#include "WebKitAccessibleHyperlink.h" 49#include "WebKitAccessibleInterfaceAction.h" 50#include "WebKitAccessibleInterfaceComponent.h" 51#include "WebKitAccessibleInterfaceDocument.h" 52#include "WebKitAccessibleInterfaceEditableText.h" 53#include "WebKitAccessibleInterfaceHyperlinkImpl.h" 54#include "WebKitAccessibleInterfaceHypertext.h" 55#include "WebKitAccessibleInterfaceImage.h" 56#include "WebKitAccessibleInterfaceSelection.h" 57#include "WebKitAccessibleInterfaceTable.h" 58#include "WebKitAccessibleInterfaceText.h" 59#include "WebKitAccessibleInterfaceValue.h" 60#include "WebKitAccessibleUtil.h" 61#include "htmlediting.h" 62#include <glib/gprintf.h> 63#include <wtf/text/CString.h> 64 65#if PLATFORM(GTK) 66#include <gtk/gtk.h> 67#endif 68 69using namespace WebCore; 70 71struct _WebKitAccessiblePrivate { 72 // Cached data for AtkObject. 73 CString accessibleName; 74 CString accessibleDescription; 75 76 // Cached data for AtkAction. 77 CString actionName; 78 CString actionKeyBinding; 79 80 // Cached data for AtkDocument. 81 CString documentLocale; 82 CString documentType; 83 CString documentEncoding; 84 CString documentURI; 85 86 // Cached data for AtkImage. 87 CString imageDescription; 88}; 89 90#define WEBKIT_ACCESSIBLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), WEBKIT_TYPE_ACCESSIBLE, WebKitAccessiblePrivate)) 91 92static AccessibilityObject* fallbackObject() 93{ 94 // FIXME: An AXObjectCache with a Document is meaningless. 95 static AXObjectCache* fallbackCache = new AXObjectCache(0); 96 static AccessibilityObject* object = 0; 97 if (!object) { 98 // FIXME: using fallbackCache->getOrCreate(ListBoxOptionRole) is a hack 99 object = fallbackCache->getOrCreate(ListBoxOptionRole); 100 object->ref(); 101 } 102 103 return object; 104} 105 106static AccessibilityObject* core(WebKitAccessible* accessible) 107{ 108 if (!accessible) 109 return 0; 110 111 return accessible->m_object; 112} 113 114static AccessibilityObject* core(AtkObject* object) 115{ 116 if (!WEBKIT_IS_ACCESSIBLE(object)) 117 return 0; 118 119 return core(WEBKIT_ACCESSIBLE(object)); 120} 121 122static const gchar* webkitAccessibleGetName(AtkObject* object) 123{ 124 AccessibilityObject* coreObject = core(object); 125 if (!coreObject->isAccessibilityRenderObject()) 126 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue()); 127 128 if (coreObject->isFieldset()) { 129 AccessibilityObject* label = coreObject->titleUIElement(); 130 if (label) { 131 AtkObject* atkObject = label->wrapper(); 132 if (ATK_IS_TEXT(atkObject)) 133 return atk_text_get_text(ATK_TEXT(atkObject), 0, -1); 134 } 135 } 136 137 if (coreObject->isControl()) { 138 AccessibilityObject* label = coreObject->correspondingLabelForControlElement(); 139 if (label) { 140 AtkObject* atkObject = label->wrapper(); 141 if (ATK_IS_TEXT(atkObject)) 142 return atk_text_get_text(ATK_TEXT(atkObject), 0, -1); 143 } 144 145 // Try text under the node. 146 String textUnder = coreObject->textUnderElement(); 147 if (textUnder.length()) 148 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, textUnder); 149 } 150 151 if (coreObject->isImage() || coreObject->isInputImage()) { 152 Node* node = coreObject->node(); 153 if (node && node->isHTMLElement()) { 154 // Get the attribute rather than altText String so as not to fall back on title. 155 String alt = toHTMLElement(node)->getAttribute(HTMLNames::altAttr); 156 if (!alt.isEmpty()) 157 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, alt); 158 } 159 } 160 161 // Fallback for the webArea object: just return the document's title. 162 if (coreObject->isWebArea()) { 163 Document* document = coreObject->document(); 164 if (document) 165 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, document->title()); 166 } 167 168 // Nothing worked so far, try with the AccessibilityObject's 169 // title() before going ahead with stringValue(). 170 String axTitle = accessibilityTitle(coreObject); 171 if (!axTitle.isEmpty()) 172 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, axTitle); 173 174 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleName, coreObject->stringValue()); 175} 176 177static const gchar* webkitAccessibleGetDescription(AtkObject* object) 178{ 179 AccessibilityObject* coreObject = core(object); 180 Node* node = 0; 181 if (coreObject->isAccessibilityRenderObject()) 182 node = coreObject->node(); 183 if (!node || !node->isHTMLElement() || coreObject->ariaRoleAttribute() != UnknownRole) 184 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject)); 185 186 // atk_table_get_summary returns an AtkObject. We have no summary object, so expose summary here. 187 if (coreObject->roleValue() == TableRole) { 188 String summary = static_cast<HTMLTableElement*>(node)->summary(); 189 if (!summary.isEmpty()) 190 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, summary); 191 } 192 193 // The title attribute should be reliably available as the object's descripton. 194 // We do not want to fall back on other attributes in its absence. See bug 25524. 195 String title = toHTMLElement(node)->title(); 196 if (!title.isEmpty()) 197 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, title); 198 199 return cacheAndReturnAtkProperty(object, AtkCachedAccessibleDescription, accessibilityDescription(coreObject)); 200} 201 202static void setAtkRelationSetFromCoreObject(AccessibilityObject* coreObject, AtkRelationSet* relationSet) 203{ 204 if (coreObject->isFieldset()) { 205 AccessibilityObject* label = coreObject->titleUIElement(); 206 if (label) 207 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); 208 return; 209 } 210 211 if (coreObject->roleValue() == LegendRole) { 212 for (AccessibilityObject* parent = coreObject->parentObjectUnignored(); parent; parent = parent->parentObjectUnignored()) { 213 if (parent->isFieldset()) { 214 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, parent->wrapper()); 215 break; 216 } 217 } 218 return; 219 } 220 221 if (coreObject->isControl()) { 222 AccessibilityObject* label = coreObject->correspondingLabelForControlElement(); 223 if (label) 224 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABELLED_BY, label->wrapper()); 225 } else { 226 AccessibilityObject* control = coreObject->correspondingControlForLabelElement(); 227 if (control) 228 atk_relation_set_add_relation_by_type(relationSet, ATK_RELATION_LABEL_FOR, control->wrapper()); 229 } 230} 231 232static gpointer webkitAccessibleParentClass = 0; 233 234static bool isRootObject(AccessibilityObject* coreObject) 235{ 236 // The root accessible object in WebCore is always an object with 237 // the ScrolledArea role with one child with the WebArea role. 238 if (!coreObject || !coreObject->isScrollView()) 239 return false; 240 241 AccessibilityObject* firstChild = coreObject->firstChild(); 242 if (!firstChild || !firstChild->isWebArea()) 243 return false; 244 245 return true; 246} 247 248static AtkObject* atkParentOfRootObject(AtkObject* object) 249{ 250 AccessibilityObject* coreObject = core(object); 251 AccessibilityObject* coreParent = coreObject->parentObjectUnignored(); 252 253 // The top level object claims to not have a parent. This makes it 254 // impossible for assistive technologies to ascend the accessible 255 // hierarchy all the way to the application. (Bug 30489) 256 if (!coreParent && isRootObject(coreObject)) { 257 Document* document = coreObject->document(); 258 if (!document) 259 return 0; 260 261#if PLATFORM(GTK) 262 HostWindow* hostWindow = document->view()->hostWindow(); 263 if (hostWindow) { 264 PlatformPageClient scrollView = hostWindow->platformPageClient(); 265 if (scrollView) { 266 GtkWidget* scrollViewParent = gtk_widget_get_parent(scrollView); 267 if (scrollViewParent) 268 return gtk_widget_get_accessible(scrollViewParent); 269 } 270 } 271#endif // PLATFORM(GTK) 272 } 273 274 if (!coreParent) 275 return 0; 276 277 return coreParent->wrapper(); 278} 279 280static AtkObject* webkitAccessibleGetParent(AtkObject* object) 281{ 282 // Check first if the parent has been already set. 283 AtkObject* accessibleParent = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->get_parent(object); 284 if (accessibleParent) 285 return accessibleParent; 286 287 // Parent not set yet, so try to find it in the hierarchy. 288 AccessibilityObject* coreObject = core(object); 289 if (!coreObject) 290 return 0; 291 292 AccessibilityObject* coreParent = coreObject->parentObjectUnignored(); 293 294 if (!coreParent && isRootObject(coreObject)) 295 return atkParentOfRootObject(object); 296 297 if (!coreParent) 298 return 0; 299 300 // We don't expose table rows to Assistive technologies, but we 301 // need to have them anyway in the hierarchy from WebCore to 302 // properly perform coordinates calculations when requested. 303 if (coreParent->isTableRow() && coreObject->isTableCell()) 304 coreParent = coreParent->parentObjectUnignored(); 305 306 return coreParent->wrapper(); 307} 308 309static gint getNChildrenForTable(AccessibilityObject* coreObject) 310{ 311 AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children(); 312 size_t tableChildrenCount = tableChildren.size(); 313 size_t cellsCount = 0; 314 315 // Look for the actual index of the cell inside the table. 316 for (unsigned i = 0; i < tableChildrenCount; ++i) { 317 if (tableChildren[i]->isTableRow()) { 318 AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children(); 319 cellsCount += rowChildren.size(); 320 } else 321 cellsCount++; 322 } 323 324 return cellsCount; 325} 326 327static gint webkitAccessibleGetNChildren(AtkObject* object) 328{ 329 AccessibilityObject* coreObject = core(object); 330 331 // Tables should be treated in a different way because rows should 332 // be bypassed when exposing the accessible hierarchy. 333 if (coreObject->isAccessibilityTable()) 334 return getNChildrenForTable(coreObject); 335 336 return coreObject->children().size(); 337} 338 339static AccessibilityObject* getChildForTable(AccessibilityObject* coreObject, gint index) 340{ 341 AccessibilityObject::AccessibilityChildrenVector tableChildren = coreObject->children(); 342 size_t tableChildrenCount = tableChildren.size(); 343 size_t cellsCount = 0; 344 345 // Look for the actual index of the cell inside the table. 346 size_t current = static_cast<size_t>(index); 347 for (unsigned i = 0; i < tableChildrenCount; ++i) { 348 if (tableChildren[i]->isTableRow()) { 349 AccessibilityObject::AccessibilityChildrenVector rowChildren = tableChildren[i]->children(); 350 size_t rowChildrenCount = rowChildren.size(); 351 if (current < cellsCount + rowChildrenCount) 352 return rowChildren.at(current - cellsCount).get(); 353 cellsCount += rowChildrenCount; 354 } else if (cellsCount == current) 355 return tableChildren[i].get(); 356 else 357 cellsCount++; 358 } 359 360 // Shouldn't reach if the child was found. 361 return 0; 362} 363 364static AtkObject* webkitAccessibleRefChild(AtkObject* object, gint index) 365{ 366 if (index < 0) 367 return 0; 368 369 AccessibilityObject* coreObject = core(object); 370 AccessibilityObject* coreChild = 0; 371 372 // Tables are special cases because rows should be bypassed, but 373 // still taking their cells into account. 374 if (coreObject->isAccessibilityTable()) 375 coreChild = getChildForTable(coreObject, index); 376 else { 377 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); 378 if (static_cast<unsigned>(index) >= children.size()) 379 return 0; 380 coreChild = children.at(index).get(); 381 } 382 383 if (!coreChild) 384 return 0; 385 386 AtkObject* child = coreChild->wrapper(); 387 atk_object_set_parent(child, object); 388 g_object_ref(child); 389 390 return child; 391} 392 393static gint getIndexInParentForCellInRow(AccessibilityObject* coreObject) 394{ 395 AccessibilityObject* parent = coreObject->parentObjectUnignored(); 396 if (!parent) 397 return -1; 398 399 AccessibilityObject* grandParent = parent->parentObjectUnignored(); 400 if (!grandParent) 401 return -1; 402 403 AccessibilityObject::AccessibilityChildrenVector rows = grandParent->children(); 404 size_t rowsCount = rows.size(); 405 size_t previousCellsCount = 0; 406 407 // Look for the actual index of the cell inside the table. 408 for (unsigned i = 0; i < rowsCount; ++i) { 409 if (!rows[i]->isTableRow()) 410 continue; 411 412 AccessibilityObject::AccessibilityChildrenVector cells = rows[i]->children(); 413 size_t cellsCount = cells.size(); 414 415 if (rows[i] == parent) { 416 for (unsigned j = 0; j < cellsCount; ++j) { 417 if (cells[j] == coreObject) 418 return previousCellsCount + j; 419 } 420 } 421 422 previousCellsCount += cellsCount; 423 } 424 425 return -1; 426} 427 428static gint webkitAccessibleGetIndexInParent(AtkObject* object) 429{ 430 AccessibilityObject* coreObject = core(object); 431 AccessibilityObject* parent = coreObject->parentObjectUnignored(); 432 433 if (!parent && isRootObject(coreObject)) { 434 AtkObject* atkParent = atkParentOfRootObject(object); 435 if (!atkParent) 436 return -1; 437 438 unsigned count = atk_object_get_n_accessible_children(atkParent); 439 for (unsigned i = 0; i < count; ++i) { 440 AtkObject* child = atk_object_ref_accessible_child(atkParent, i); 441 bool childIsObject = child == object; 442 g_object_unref(child); 443 if (childIsObject) 444 return i; 445 } 446 } 447 448 // Need to calculate the index of the cell in the table, as 449 // rows won't be exposed to assistive technologies. 450 if (parent && parent->isTableRow() && coreObject->isTableCell()) 451 return getIndexInParentForCellInRow(coreObject); 452 453 if (!parent) 454 return -1; 455 456 size_t index = parent->children().find(coreObject); 457 return (index == WTF::notFound) ? -1 : index; 458} 459 460static AtkAttributeSet* webkitAccessibleGetAttributes(AtkObject* object) 461{ 462 AtkAttributeSet* attributeSet = 0; 463#if PLATFORM(GTK) 464 attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitGtk"); 465#elif PLATFORM(EFL) 466 attributeSet = addToAtkAttributeSet(attributeSet, "toolkit", "WebKitEfl"); 467#endif 468 469 AccessibilityObject* coreObject = core(object); 470 if (!coreObject) 471 return attributeSet; 472 473 // Hack needed for WebKit2 tests because obtaining an element by its ID 474 // cannot be done from the UIProcess. Assistive technologies have no need 475 // for this information. 476 Node* node = coreObject->node(); 477 if (node && node->isElementNode()) { 478 String id = toElement(node)->getIdAttribute().string(); 479 if (!id.isEmpty()) 480 attributeSet = addToAtkAttributeSet(attributeSet, "html-id", id.utf8().data()); 481 } 482 483 int headingLevel = coreObject->headingLevel(); 484 if (headingLevel) { 485 String value = String::number(headingLevel); 486 attributeSet = addToAtkAttributeSet(attributeSet, "level", value.utf8().data()); 487 } 488 489 // Set the 'layout-guess' attribute to help Assistive 490 // Technologies know when an exposed table is not data table. 491 if (coreObject->isAccessibilityTable() && !coreObject->isDataTable()) 492 attributeSet = addToAtkAttributeSet(attributeSet, "layout-guess", "true"); 493 494 String placeholder = coreObject->placeholderValue(); 495 if (!placeholder.isEmpty()) 496 attributeSet = addToAtkAttributeSet(attributeSet, "placeholder-text", placeholder.utf8().data()); 497 498 return attributeSet; 499} 500 501static AtkRole atkRole(AccessibilityRole role) 502{ 503 switch (role) { 504 case UnknownRole: 505 return ATK_ROLE_UNKNOWN; 506 case ButtonRole: 507 return ATK_ROLE_PUSH_BUTTON; 508 case ToggleButtonRole: 509 return ATK_ROLE_TOGGLE_BUTTON; 510 case RadioButtonRole: 511 return ATK_ROLE_RADIO_BUTTON; 512 case CheckBoxRole: 513 return ATK_ROLE_CHECK_BOX; 514 case SliderRole: 515 return ATK_ROLE_SLIDER; 516 case TabGroupRole: 517 case TabListRole: 518 return ATK_ROLE_PAGE_TAB_LIST; 519 case TextFieldRole: 520 case TextAreaRole: 521 return ATK_ROLE_ENTRY; 522 case StaticTextRole: 523 return ATK_ROLE_TEXT; 524 case OutlineRole: 525 return ATK_ROLE_TREE; 526 case MenuBarRole: 527 return ATK_ROLE_MENU_BAR; 528 case MenuListPopupRole: 529 case MenuRole: 530 return ATK_ROLE_MENU; 531 case MenuListOptionRole: 532 case MenuItemRole: 533 return ATK_ROLE_MENU_ITEM; 534 case ColumnRole: 535 // return ATK_ROLE_TABLE_COLUMN_HEADER; // Is this right? 536 return ATK_ROLE_UNKNOWN; // Matches Mozilla 537 case RowRole: 538 // return ATK_ROLE_TABLE_ROW_HEADER; // Is this right? 539 return ATK_ROLE_LIST_ITEM; // Matches Mozilla 540 case ToolbarRole: 541 return ATK_ROLE_TOOL_BAR; 542 case BusyIndicatorRole: 543 return ATK_ROLE_PROGRESS_BAR; // Is this right? 544 case ProgressIndicatorRole: 545 // return ATK_ROLE_SPIN_BUTTON; // Some confusion about this role in AccessibilityRenderObject.cpp 546 return ATK_ROLE_PROGRESS_BAR; 547 case WindowRole: 548 return ATK_ROLE_WINDOW; 549 case PopUpButtonRole: 550 case ComboBoxRole: 551 return ATK_ROLE_COMBO_BOX; 552 case SplitGroupRole: 553 return ATK_ROLE_SPLIT_PANE; 554 case SplitterRole: 555 return ATK_ROLE_UNKNOWN; 556 case ColorWellRole: 557 return ATK_ROLE_COLOR_CHOOSER; 558 case ListRole: 559 return ATK_ROLE_LIST; 560 case ScrollBarRole: 561 return ATK_ROLE_SCROLL_BAR; 562 case ScrollAreaRole: 563 return ATK_ROLE_SCROLL_PANE; 564 case GridRole: // Is this right? 565 case TableRole: 566 return ATK_ROLE_TABLE; 567 case ApplicationRole: 568 return ATK_ROLE_APPLICATION; 569 case GroupRole: 570 case RadioGroupRole: 571 case TabPanelRole: 572 return ATK_ROLE_PANEL; 573 case RowHeaderRole: // Row headers are cells after all. 574 case ColumnHeaderRole: // Column headers are cells after all. 575 case CellRole: 576 return ATK_ROLE_TABLE_CELL; 577 case LinkRole: 578 case WebCoreLinkRole: 579 case ImageMapLinkRole: 580 return ATK_ROLE_LINK; 581 case ImageMapRole: 582 case ImageRole: 583 return ATK_ROLE_IMAGE; 584 case ListMarkerRole: 585 return ATK_ROLE_TEXT; 586 case WebAreaRole: 587 // return ATK_ROLE_HTML_CONTAINER; // Is this right? 588 return ATK_ROLE_DOCUMENT_FRAME; 589 case HeadingRole: 590 return ATK_ROLE_HEADING; 591 case ListBoxRole: 592 return ATK_ROLE_LIST; 593 case ListItemRole: 594 case ListBoxOptionRole: 595 return ATK_ROLE_LIST_ITEM; 596 case ParagraphRole: 597 return ATK_ROLE_PARAGRAPH; 598 case LabelRole: 599 case LegendRole: 600 return ATK_ROLE_LABEL; 601 case DivRole: 602 return ATK_ROLE_SECTION; 603 case FormRole: 604 return ATK_ROLE_FORM; 605 case CanvasRole: 606 return ATK_ROLE_CANVAS; 607 case HorizontalRuleRole: 608 return ATK_ROLE_SEPARATOR; 609 case SpinButtonRole: 610 return ATK_ROLE_SPIN_BUTTON; 611 case TabRole: 612 return ATK_ROLE_PAGE_TAB; 613 default: 614 return ATK_ROLE_UNKNOWN; 615 } 616} 617 618static AtkRole webkitAccessibleGetRole(AtkObject* object) 619{ 620 AccessibilityObject* coreObject = core(object); 621 622 if (!coreObject) 623 return ATK_ROLE_UNKNOWN; 624 625 // Note: Why doesn't WebCore have a password field for this 626 if (coreObject->isPasswordField()) 627 return ATK_ROLE_PASSWORD_TEXT; 628 629 return atkRole(coreObject->roleValue()); 630} 631 632static bool isTextWithCaret(AccessibilityObject* coreObject) 633{ 634 if (!coreObject || !coreObject->isAccessibilityRenderObject()) 635 return false; 636 637 Document* document = coreObject->document(); 638 if (!document) 639 return false; 640 641 Frame* frame = document->frame(); 642 if (!frame) 643 return false; 644 645 Settings* settings = frame->settings(); 646 if (!settings || !settings->caretBrowsingEnabled()) 647 return false; 648 649 // Check text objects and paragraphs only. 650 AtkObject* axObject = coreObject->wrapper(); 651 AtkRole role = axObject ? atk_object_get_role(axObject) : ATK_ROLE_INVALID; 652 if (role != ATK_ROLE_TEXT && role != ATK_ROLE_PARAGRAPH) 653 return false; 654 655 // Finally, check whether the caret is set in the current object. 656 VisibleSelection selection = coreObject->selection(); 657 if (!selection.isCaret()) 658 return false; 659 660 return selectionBelongsToObject(coreObject, selection); 661} 662 663static void setAtkStateSetFromCoreObject(AccessibilityObject* coreObject, AtkStateSet* stateSet) 664{ 665 AccessibilityObject* parent = coreObject->parentObject(); 666 bool isListBoxOption = parent && parent->isListBox(); 667 668 // Please keep the state list in alphabetical order 669 if (coreObject->isChecked()) 670 atk_state_set_add_state(stateSet, ATK_STATE_CHECKED); 671 672 // FIXME: isReadOnly does not seem to do the right thing for 673 // controls, so check explicitly for them. In addition, because 674 // isReadOnly is false for listBoxOptions, we need to add one 675 // more check so that we do not present them as being "editable". 676 if ((!coreObject->isReadOnly() 677 || (coreObject->isControl() && coreObject->canSetValueAttribute())) 678 && !isListBoxOption) 679 atk_state_set_add_state(stateSet, ATK_STATE_EDITABLE); 680 681 // FIXME: Put both ENABLED and SENSITIVE together here for now 682 if (coreObject->isEnabled()) { 683 atk_state_set_add_state(stateSet, ATK_STATE_ENABLED); 684 atk_state_set_add_state(stateSet, ATK_STATE_SENSITIVE); 685 } 686 687 if (coreObject->canSetExpandedAttribute()) 688 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDABLE); 689 690 if (coreObject->isExpanded()) 691 atk_state_set_add_state(stateSet, ATK_STATE_EXPANDED); 692 693 if (coreObject->canSetFocusAttribute()) 694 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); 695 696 if (coreObject->isFocused() || isTextWithCaret(coreObject)) 697 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); 698 699 if (coreObject->orientation() == AccessibilityOrientationHorizontal) 700 atk_state_set_add_state(stateSet, ATK_STATE_HORIZONTAL); 701 else if (coreObject->orientation() == AccessibilityOrientationVertical) 702 atk_state_set_add_state(stateSet, ATK_STATE_VERTICAL); 703 704 if (coreObject->isIndeterminate()) 705 atk_state_set_add_state(stateSet, ATK_STATE_INDETERMINATE); 706 707 if (coreObject->isMultiSelectable()) 708 atk_state_set_add_state(stateSet, ATK_STATE_MULTISELECTABLE); 709 710 // TODO: ATK_STATE_OPAQUE 711 712 if (coreObject->isPressed()) 713 atk_state_set_add_state(stateSet, ATK_STATE_PRESSED); 714 715 // TODO: ATK_STATE_SELECTABLE_TEXT 716 717 if (coreObject->canSetSelectedAttribute()) { 718 atk_state_set_add_state(stateSet, ATK_STATE_SELECTABLE); 719 // Items in focusable lists have both STATE_SELECT{ABLE,ED} 720 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on 721 // the former. 722 if (isListBoxOption) 723 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); 724 } 725 726 if (coreObject->isSelected()) { 727 atk_state_set_add_state(stateSet, ATK_STATE_SELECTED); 728 // Items in focusable lists have both STATE_SELECT{ABLE,ED} 729 // and STATE_FOCUS{ABLE,ED}. We'll fake the latter based on the 730 // former. 731 if (isListBoxOption) 732 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSED); 733 } 734 735 // FIXME: Group both SHOWING and VISIBLE here for now 736 // Not sure how to handle this in WebKit, see bug 737 // http://bugzilla.gnome.org/show_bug.cgi?id=509650 for other 738 // issues with SHOWING vs VISIBLE. 739 if (!coreObject->isOffScreen()) { 740 atk_state_set_add_state(stateSet, ATK_STATE_SHOWING); 741 atk_state_set_add_state(stateSet, ATK_STATE_VISIBLE); 742 } 743 744 // Mutually exclusive, so we group these two 745 if (coreObject->roleValue() == TextFieldRole) 746 atk_state_set_add_state(stateSet, ATK_STATE_SINGLE_LINE); 747 else if (coreObject->roleValue() == TextAreaRole) 748 atk_state_set_add_state(stateSet, ATK_STATE_MULTI_LINE); 749 750 // TODO: ATK_STATE_SENSITIVE 751 752 if (coreObject->isVisited()) 753 atk_state_set_add_state(stateSet, ATK_STATE_VISITED); 754} 755 756static AtkStateSet* webkitAccessibleRefStateSet(AtkObject* object) 757{ 758 AtkStateSet* stateSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_state_set(object); 759 AccessibilityObject* coreObject = core(object); 760 761 if (coreObject == fallbackObject()) { 762 atk_state_set_add_state(stateSet, ATK_STATE_DEFUNCT); 763 return stateSet; 764 } 765 766 // Text objects must be focusable. 767 AtkRole role = atk_object_get_role(object); 768 if (role == ATK_ROLE_TEXT || role == ATK_ROLE_PARAGRAPH) 769 atk_state_set_add_state(stateSet, ATK_STATE_FOCUSABLE); 770 771 setAtkStateSetFromCoreObject(coreObject, stateSet); 772 return stateSet; 773} 774 775static AtkRelationSet* webkitAccessibleRefRelationSet(AtkObject* object) 776{ 777 AtkRelationSet* relationSet = ATK_OBJECT_CLASS(webkitAccessibleParentClass)->ref_relation_set(object); 778 AccessibilityObject* coreObject = core(object); 779 780 setAtkRelationSetFromCoreObject(coreObject, relationSet); 781 782 return relationSet; 783} 784 785static void webkitAccessibleInit(AtkObject* object, gpointer data) 786{ 787 if (ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize) 788 ATK_OBJECT_CLASS(webkitAccessibleParentClass)->initialize(object, data); 789 790 WebKitAccessible* accessible = WEBKIT_ACCESSIBLE(object); 791 accessible->m_object = reinterpret_cast<AccessibilityObject*>(data); 792 accessible->priv = WEBKIT_ACCESSIBLE_GET_PRIVATE(accessible); 793} 794 795static const gchar* webkitAccessibleGetObjectLocale(AtkObject* object) 796{ 797 if (ATK_IS_DOCUMENT(object)) { 798 AccessibilityObject* coreObject = core(object); 799 if (!coreObject) 800 return 0; 801 802 // TODO: Should we fall back on lang xml:lang when the following comes up empty? 803 String language = coreObject->language(); 804 if (!language.isEmpty()) 805 return cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, language); 806 807 } else if (ATK_IS_TEXT(object)) { 808 const gchar* locale = 0; 809 810 AtkAttributeSet* textAttributes = atk_text_get_default_attributes(ATK_TEXT(object)); 811 for (GSList* attributes = textAttributes; attributes; attributes = attributes->next) { 812 AtkAttribute* atkAttribute = static_cast<AtkAttribute*>(attributes->data); 813 if (!strcmp(atkAttribute->name, atk_text_attribute_get_name(ATK_TEXT_ATTR_LANGUAGE))) { 814 locale = cacheAndReturnAtkProperty(object, AtkCachedDocumentLocale, String::fromUTF8(atkAttribute->value)); 815 break; 816 } 817 } 818 819 atk_attribute_set_free(textAttributes); 820 821 return locale; 822 } 823 824 return 0; 825} 826 827static void webkitAccessibleFinalize(GObject* object) 828{ 829 G_OBJECT_CLASS(webkitAccessibleParentClass)->finalize(object); 830} 831 832static void webkitAccessibleClassInit(AtkObjectClass* klass) 833{ 834 GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); 835 836 webkitAccessibleParentClass = g_type_class_peek_parent(klass); 837 838 gobjectClass->finalize = webkitAccessibleFinalize; 839 840 klass->initialize = webkitAccessibleInit; 841 klass->get_name = webkitAccessibleGetName; 842 klass->get_description = webkitAccessibleGetDescription; 843 klass->get_parent = webkitAccessibleGetParent; 844 klass->get_n_children = webkitAccessibleGetNChildren; 845 klass->ref_child = webkitAccessibleRefChild; 846 klass->get_role = webkitAccessibleGetRole; 847 klass->ref_state_set = webkitAccessibleRefStateSet; 848 klass->get_index_in_parent = webkitAccessibleGetIndexInParent; 849 klass->get_attributes = webkitAccessibleGetAttributes; 850 klass->ref_relation_set = webkitAccessibleRefRelationSet; 851 klass->get_object_locale = webkitAccessibleGetObjectLocale; 852 853 g_type_class_add_private(klass, sizeof(WebKitAccessiblePrivate)); 854} 855 856GType 857webkitAccessibleGetType(void) 858{ 859 static volatile gsize typeVolatile = 0; 860 861 if (g_once_init_enter(&typeVolatile)) { 862 static const GTypeInfo tinfo = { 863 sizeof(WebKitAccessibleClass), 864 (GBaseInitFunc) 0, 865 (GBaseFinalizeFunc) 0, 866 (GClassInitFunc) webkitAccessibleClassInit, 867 (GClassFinalizeFunc) 0, 868 0, /* class data */ 869 sizeof(WebKitAccessible), /* instance size */ 870 0, /* nb preallocs */ 871 (GInstanceInitFunc) 0, 872 0 /* value table */ 873 }; 874 875 GType type = g_type_register_static(ATK_TYPE_OBJECT, "WebKitAccessible", &tinfo, GTypeFlags(0)); 876 g_once_init_leave(&typeVolatile, type); 877 } 878 879 return typeVolatile; 880} 881 882static const GInterfaceInfo AtkInterfacesInitFunctions[] = { 883 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleActionInterfaceInit), 0, 0}, 884 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleSelectionInterfaceInit), 0, 0}, 885 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleEditableTextInterfaceInit), 0, 0}, 886 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTextInterfaceInit), 0, 0}, 887 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleComponentInterfaceInit), 0, 0}, 888 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleImageInterfaceInit), 0, 0}, 889 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleTableInterfaceInit), 0, 0}, 890 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHypertextInterfaceInit), 0, 0}, 891 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleHyperlinkImplInterfaceInit), 0, 0}, 892 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleDocumentInterfaceInit), 0, 0}, 893 {reinterpret_cast<GInterfaceInitFunc>(webkitAccessibleValueInterfaceInit), 0, 0} 894}; 895 896enum WAIType { 897 WAI_ACTION, 898 WAI_SELECTION, 899 WAI_EDITABLE_TEXT, 900 WAI_TEXT, 901 WAI_COMPONENT, 902 WAI_IMAGE, 903 WAI_TABLE, 904 WAI_HYPERTEXT, 905 WAI_HYPERLINK, 906 WAI_DOCUMENT, 907 WAI_VALUE, 908}; 909 910static GType GetAtkInterfaceTypeFromWAIType(WAIType type) 911{ 912 switch (type) { 913 case WAI_ACTION: 914 return ATK_TYPE_ACTION; 915 case WAI_SELECTION: 916 return ATK_TYPE_SELECTION; 917 case WAI_EDITABLE_TEXT: 918 return ATK_TYPE_EDITABLE_TEXT; 919 case WAI_TEXT: 920 return ATK_TYPE_TEXT; 921 case WAI_COMPONENT: 922 return ATK_TYPE_COMPONENT; 923 case WAI_IMAGE: 924 return ATK_TYPE_IMAGE; 925 case WAI_TABLE: 926 return ATK_TYPE_TABLE; 927 case WAI_HYPERTEXT: 928 return ATK_TYPE_HYPERTEXT; 929 case WAI_HYPERLINK: 930 return ATK_TYPE_HYPERLINK_IMPL; 931 case WAI_DOCUMENT: 932 return ATK_TYPE_DOCUMENT; 933 case WAI_VALUE: 934 return ATK_TYPE_VALUE; 935 } 936 937 return G_TYPE_INVALID; 938} 939 940static bool roleIsTextType(AccessibilityRole role) 941{ 942 return role == ParagraphRole || role == HeadingRole || role == DivRole || role == CellRole || role == ListItemRole; 943} 944 945static guint16 getInterfaceMaskFromObject(AccessibilityObject* coreObject) 946{ 947 guint16 interfaceMask = 0; 948 949 // Component interface is always supported 950 interfaceMask |= 1 << WAI_COMPONENT; 951 952 AccessibilityRole role = coreObject->roleValue(); 953 954 // Action 955 // As the implementation of the AtkAction interface is a very 956 // basic one (just relays in executing the default action for each 957 // object, and only supports having one action per object), it is 958 // better just to implement this interface for every instance of 959 // the WebKitAccessible class and let WebCore decide what to do. 960 interfaceMask |= 1 << WAI_ACTION; 961 962 // Selection 963 if (coreObject->isListBox() || coreObject->isMenuList()) 964 interfaceMask |= 1 << WAI_SELECTION; 965 966 // Get renderer if available. 967 RenderObject* renderer = 0; 968 if (coreObject->isAccessibilityRenderObject()) 969 renderer = coreObject->renderer(); 970 971 // Hyperlink (links and embedded objects). 972 if (coreObject->isLink() || (renderer && renderer->isReplaced())) 973 interfaceMask |= 1 << WAI_HYPERLINK; 974 975 // Text & Editable Text 976 if (role == StaticTextRole || coreObject->isMenuListOption()) 977 interfaceMask |= 1 << WAI_TEXT; 978 else { 979 if (coreObject->isTextControl()) { 980 interfaceMask |= 1 << WAI_TEXT; 981 if (!coreObject->isReadOnly()) 982 interfaceMask |= 1 << WAI_EDITABLE_TEXT; 983 } else { 984 if (role != TableRole) { 985 interfaceMask |= 1 << WAI_HYPERTEXT; 986 if ((renderer && renderer->childrenInline()) || roleIsTextType(role)) 987 interfaceMask |= 1 << WAI_TEXT; 988 } 989 990 // Add the TEXT interface for list items whose 991 // first accessible child has a text renderer 992 if (role == ListItemRole) { 993 AccessibilityObject::AccessibilityChildrenVector children = coreObject->children(); 994 if (children.size()) { 995 AccessibilityObject* axRenderChild = children.at(0).get(); 996 interfaceMask |= getInterfaceMaskFromObject(axRenderChild); 997 } 998 } 999 } 1000 } 1001 1002 // Image 1003 if (coreObject->isImage()) 1004 interfaceMask |= 1 << WAI_IMAGE; 1005 1006 // Table 1007 if (role == TableRole) 1008 interfaceMask |= 1 << WAI_TABLE; 1009 1010 // Document 1011 if (role == WebAreaRole) 1012 interfaceMask |= 1 << WAI_DOCUMENT; 1013 1014 // Value 1015 if (role == SliderRole || role == SpinButtonRole || role == ScrollBarRole) 1016 interfaceMask |= 1 << WAI_VALUE; 1017 1018 return interfaceMask; 1019} 1020 1021static const char* getUniqueAccessibilityTypeName(guint16 interfaceMask) 1022{ 1023#define WAI_TYPE_NAME_LEN (30) /* Enough for prefix + 5 hex characters (max) */ 1024 static char name[WAI_TYPE_NAME_LEN + 1]; 1025 1026 g_sprintf(name, "WAIType%x", interfaceMask); 1027 name[WAI_TYPE_NAME_LEN] = '\0'; 1028 1029 return name; 1030} 1031 1032static GType getAccessibilityTypeFromObject(AccessibilityObject* coreObject) 1033{ 1034 static const GTypeInfo typeInfo = { 1035 sizeof(WebKitAccessibleClass), 1036 (GBaseInitFunc) 0, 1037 (GBaseFinalizeFunc) 0, 1038 (GClassInitFunc) 0, 1039 (GClassFinalizeFunc) 0, 1040 0, /* class data */ 1041 sizeof(WebKitAccessible), /* instance size */ 1042 0, /* nb preallocs */ 1043 (GInstanceInitFunc) 0, 1044 0 /* value table */ 1045 }; 1046 1047 guint16 interfaceMask = getInterfaceMaskFromObject(coreObject); 1048 const char* atkTypeName = getUniqueAccessibilityTypeName(interfaceMask); 1049 GType type = g_type_from_name(atkTypeName); 1050 if (type) 1051 return type; 1052 1053 type = g_type_register_static(WEBKIT_TYPE_ACCESSIBLE, atkTypeName, &typeInfo, GTypeFlags(0)); 1054 for (guint i = 0; i < G_N_ELEMENTS(AtkInterfacesInitFunctions); i++) { 1055 if (interfaceMask & (1 << i)) 1056 g_type_add_interface_static(type, 1057 GetAtkInterfaceTypeFromWAIType(static_cast<WAIType>(i)), 1058 &AtkInterfacesInitFunctions[i]); 1059 } 1060 1061 return type; 1062} 1063 1064WebKitAccessible* webkitAccessibleNew(AccessibilityObject* coreObject) 1065{ 1066 GType type = getAccessibilityTypeFromObject(coreObject); 1067 AtkObject* object = static_cast<AtkObject*>(g_object_new(type, 0)); 1068 1069 atk_object_initialize(object, coreObject); 1070 1071 return WEBKIT_ACCESSIBLE(object); 1072} 1073 1074AccessibilityObject* webkitAccessibleGetAccessibilityObject(WebKitAccessible* accessible) 1075{ 1076 return accessible->m_object; 1077} 1078 1079void webkitAccessibleDetach(WebKitAccessible* accessible) 1080{ 1081 ASSERT(accessible->m_object); 1082 1083 if (core(accessible)->roleValue() == WebAreaRole) 1084 g_signal_emit_by_name(accessible, "state-change", "defunct", true); 1085 1086 // We replace the WebCore AccessibilityObject with a fallback object that 1087 // provides default implementations to avoid repetitive null-checking after 1088 // detachment. 1089 accessible->m_object = fallbackObject(); 1090} 1091 1092AtkObject* webkitAccessibleGetFocusedElement(WebKitAccessible* accessible) 1093{ 1094 if (!accessible->m_object) 1095 return 0; 1096 1097 RefPtr<AccessibilityObject> focusedObj = accessible->m_object->focusedUIElement(); 1098 if (!focusedObj) 1099 return 0; 1100 1101 return focusedObj->wrapper(); 1102} 1103 1104AccessibilityObject* objectFocusedAndCaretOffsetUnignored(AccessibilityObject* referenceObject, int& offset) 1105{ 1106 // Indication that something bogus has transpired. 1107 offset = -1; 1108 1109 Document* document = referenceObject->document(); 1110 if (!document) 1111 return 0; 1112 1113 Node* focusedNode = referenceObject->selection().end().containerNode(); 1114 if (!focusedNode) 1115 return 0; 1116 1117 RenderObject* focusedRenderer = focusedNode->renderer(); 1118 if (!focusedRenderer) 1119 return 0; 1120 1121 AccessibilityObject* focusedObject = document->axObjectCache()->getOrCreate(focusedRenderer); 1122 if (!focusedObject) 1123 return 0; 1124 1125 // Look for the actual (not ignoring accessibility) selected object. 1126 AccessibilityObject* firstUnignoredParent = focusedObject; 1127 if (firstUnignoredParent->accessibilityIsIgnored()) 1128 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored(); 1129 if (!firstUnignoredParent) 1130 return 0; 1131 1132 // Don't ignore links if the offset is being requested for a link. 1133 if (!referenceObject->isLink() && firstUnignoredParent->isLink()) 1134 firstUnignoredParent = firstUnignoredParent->parentObjectUnignored(); 1135 if (!firstUnignoredParent) 1136 return 0; 1137 1138 // The reference object must either coincide with the focused 1139 // object being considered, or be a descendant of it. 1140 if (referenceObject->isDescendantOfObject(firstUnignoredParent)) 1141 referenceObject = firstUnignoredParent; 1142 1143 Node* startNode = 0; 1144 if (firstUnignoredParent != referenceObject || firstUnignoredParent->isTextControl()) { 1145 // We need to use the first child's node of the reference 1146 // object as the start point to calculate the caret offset 1147 // because we want it to be relative to the object of 1148 // reference, not just to the focused object (which could have 1149 // previous siblings which should be taken into account too). 1150 AccessibilityObject* axFirstChild = referenceObject->firstChild(); 1151 if (axFirstChild) 1152 startNode = axFirstChild->node(); 1153 } 1154 // Getting the Position of a PseudoElement now triggers an assertion. 1155 // This can occur when clicking on empty space in a render block. 1156 if (!startNode || startNode->isPseudoElement()) 1157 startNode = firstUnignoredParent->node(); 1158 1159 // Check if the node for the first parent object not ignoring 1160 // accessibility is null again before using it. This might happen 1161 // with certain kind of accessibility objects, such as the root 1162 // one (the scroller containing the webArea object). 1163 if (!startNode) 1164 return 0; 1165 1166 VisiblePosition startPosition = VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM); 1167 VisiblePosition endPosition = firstUnignoredParent->selection().visibleEnd(); 1168 1169 if (startPosition == endPosition) 1170 offset = 0; 1171 else if (!isStartOfLine(endPosition)) { 1172 RefPtr<Range> range = makeRange(startPosition, endPosition.previous()); 1173 offset = TextIterator::rangeLength(range.get(), true) + 1; 1174 } else { 1175 RefPtr<Range> range = makeRange(startPosition, endPosition); 1176 offset = TextIterator::rangeLength(range.get(), true); 1177 } 1178 1179 return firstUnignoredParent; 1180} 1181 1182const char* cacheAndReturnAtkProperty(AtkObject* object, AtkCachedProperty property, String value) 1183{ 1184 WebKitAccessiblePrivate* priv = WEBKIT_ACCESSIBLE(object)->priv; 1185 CString* propertyPtr = 0; 1186 1187 switch (property) { 1188 case AtkCachedAccessibleName: 1189 propertyPtr = &priv->accessibleName; 1190 break; 1191 1192 case AtkCachedAccessibleDescription: 1193 propertyPtr = &priv->accessibleDescription; 1194 break; 1195 1196 case AtkCachedActionName: 1197 propertyPtr = &priv->actionName; 1198 break; 1199 1200 case AtkCachedActionKeyBinding: 1201 propertyPtr = &priv->actionKeyBinding; 1202 break; 1203 1204 case AtkCachedDocumentLocale: 1205 propertyPtr = &priv->documentLocale; 1206 break; 1207 1208 case AtkCachedDocumentType: 1209 propertyPtr = &priv->documentType; 1210 break; 1211 1212 case AtkCachedDocumentEncoding: 1213 propertyPtr = &priv->documentEncoding; 1214 break; 1215 1216 case AtkCachedDocumentURI: 1217 propertyPtr = &priv->documentURI; 1218 break; 1219 1220 case AtkCachedImageDescription: 1221 propertyPtr = &priv->imageDescription; 1222 break; 1223 1224 default: 1225 ASSERT_NOT_REACHED(); 1226 } 1227 1228 // Don't invalidate old memory if not stricly needed, since other 1229 // callers might be still holding on to it. 1230 if (*propertyPtr != value.utf8()) 1231 *propertyPtr = value.utf8(); 1232 1233 return (*propertyPtr).data(); 1234} 1235 1236#endif // HAVE(ACCESSIBILITY) 1237